PostQ Vault is live
Hybrid signing as a product — with BYOK to AWS KMS and Azure Key Vault. Three integration tiers, one signing API.
Two weeks ago we shipped the PostQ Hybrid Signer: a single API endpoint that produces a signature which is botha NIST FIPS 204 ML-DSA signature and a classical Ed25519 signature, AND-verified. It worked, it shipped, the SDKs and CLI all spoke it — but it lived as a raw API. No dashboard, no audit, no rotation, no story for the security team that’s going to ask “where is the master key?”
Today we’re shipping the answer: PostQ Vault.
What Vault is
Vault is the same hybrid signing engine, wrapped in the surface a real customer needs to put it into production:
- A dashboard at
/vaultfor creating, listing, and inspecting hybrid keys. - A sign / verify playgroundon each key’s detail page so you can prove a payload round-trips before you wire an SDK call.
- Key rotation— a one-click successor that inherits the predecessor’s algorithm and provider config and keeps a verifiable lineage.
- Revocation with an irreversible tombstone.
- An immutable audit log of every create / sign / verify / rotate / revoke event, scoped to the org and visible in the UI.
- Six algorithms:
mldsa{44,65,87}paired with eithered25519orecdsa-p256.
The hard part: where does the master key live?
Every CISO conversation about a managed signing service ends in the same place. Sign-as-a-service is wonderful right up until somebody asks “wait, you’re holding our private keys?”
The honest first answer for Vault is: by default, yes — the private bytes are sealed with AES-256-GCM under a Key Encryption Key we hold (POSTQ_SIGNING_KEK), stored in Postgres, and only ever decrypted in-process. That’s the same model HashiCorp Vault, Doppler, and 1Password ship with by default, and it’s a defensible posture. But it’s not the only posture we want to support.
So Vault now ships three independent provider axes on every key:
hybrid_signing_keys
├── kek_provider env | aws-kms | azure-kv (wraps the per-key DEK)
├── key_provider postq-managed | aws-kms | azure-kv (holds the classical half)
└── pq_provider postq-managed | aws-cloudhsm | azure-managed-hsm (holds the ML-DSA half)The same signing endpoint works in every combination — the orchestrator looks up each key’s providers, fans out the appropriate KMS calls, concatenates the two halves, and returns one composite signature. The SDK and CLI surface are unchanged.
Three tiers, decreasing trust in PostQ
Tier 1 — KMS-wrapped KEK
You point Vault at a Customer Master Key in your AWS KMS or a key in your Azure Key Vault. Every per-key Data Encryption Key is generated via kms:GenerateDataKey (AWS) or wrapped via CryptographyClient.wrapKey with RSA-OAEP-256 (Azure). The wrapped DEK is stored next to the sealed private bytes; PostQ has to call kms:Decrypt / unwrapKeyin your tenant’s KMS every time it wants to sign. Revoke the IAM role and every Vault key in your org becomes ciphertext.
Tier 2 — BYOK classical signing
Now the classical half of the hybrid never leaves your KMS. You pick one of the new mldsa*+ecdsa-p256 algorithms and Vault calls kms:CreateKey (ECC_NIST_P256, SIGN_VERIFY) on the AWS side — or KeyClient.createEcKey (P-256) on the Azure side — and stores only the cloud key reference. At sign time PostQ does a network round trip to kms:Sign (ECDSA_SHA_256, MessageType=RAW) or CryptographyClient.sign(ES256), converts the DER blob to raw r‖s, concatenates it with the locally-computed ML-DSA half, and you get back the same composite signature shape as Tier 1.
Why ECDSA P-256 instead of Ed25519? Cloud KMS providers don’t offer Ed25519 application signing yet. Both AWS KMS and Azure Key Vault speak ECDSA P-256. So we added it as a first-class classical half. (The original mldsa*+ed25519family stays available for postq-managed keys — nothing was removed.)
Tier 3 — HSM-backed ML-DSA
The full picture: bothhalves of the hybrid live in your tenant’s HSM. Today the wire is in place but the implementation throws a clear error — “not yet available — awaiting cloud HSM ML-DSA support”— because neither AWS CloudHSM nor Azure Managed HSM has shipped FIPS 204 yet. The interface is stable; when they ship, we drop in the implementation and existing keys transparently get the upgrade path.
What it looks like
The SDK call is the same one we shipped two weeks ago — that was the whole point:
import { PostQ } from "@postq/sdk";
const postq = new PostQ({ apiKey: process.env.POSTQ_API_KEY! });
// Same call regardless of whether the key is fully managed,
// KEK-wrapped in your AWS account, or signed-by-AWS-KMS.
const sig = await postq.hybridSign({
keyId: "key_01h...",
payloadBase64: Buffer.from("hello vault").toString("base64"),
});
const ok = await postq.hybridVerify({
keyId: "key_01h...",
payloadBase64: Buffer.from("hello vault").toString("base64"),
signatureBase64: sig.signatureBase64,
});And the create-key call now takes three optional provider hints:
curl -X POST https://api.postq.dev/v1/hybrid-keys \
-H "Authorization: Bearer pq_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "prod-release-signer",
"algorithm": "mldsa65+ecdsa-p256",
"kekProvider": "aws-kms",
"keyProvider": "aws-kms",
"pqProvider": "postq-managed"
}'The KMS configuration itself lives at /v1/vault/settings(or point and click in Settings → Vault — KMS Integration). The Azure client secret is sealed with POSTQ_SIGNING_KEK before it touches Postgres and is never echoed back on read.
Why we built it this way
The whole point of hybrid signing is that you don’t have to pick a winner between “the algorithm I trust today” and “the algorithm a quantum computer can’t break tomorrow.” It would be deeply silly to then turn around and force you to pick a winner between “the KMS I trust today” and “the managed service I want to use tomorrow.” Vault is the same idea, applied one layer up.
What’s next
- GCP KMS as the third KEK / classical-key provider.
- Vault policies— enforce that signing keys in
prodmust use a Tier 2+ provider, fail closed otherwise. - Tier 3 implementation the day AWS CloudHSM or Azure Managed HSM ships ML-DSA.
Vault is on every account today — app.postq.dev/vault to spin up your first key.