Skip to main content

POST /api/session/certificate/challenge

Certificate login, leg 1: present a certificate and receive the 89 bytes to sign.

Begins an X.509 certificate login (AM08). Send the caller's certificate, DER-encoded and then base64-encoded; receive a pending id and toBeSigned, the 89-byte channel-bound message to sign.

This route holds an open, half-authenticated key-server connection between the two legs, which is why the number of concurrent exchanges is bounded and why an unanswered exchange expires in 30 seconds. The pending id is SINGLE USE: it is consumed by the first answer, whether that answer succeeds or fails.

The Gateway never sees the private key. The key server resolves the principal by certificate digest, and only the signature over toBeSigned proves possession -- so there is no Gateway identity in this flow, and no 503 ..._NOT_CONFIGURED arm.

The construction (ADR-0107). toBeSigned is "TE-AM08-CHANNEL-BOUND-v1" || 0x00 || SHA-256("tls-exporter:" || exporter) || challenge, 89 bytes, where exporter is the RFC 9266 channel binding of THE GATEWAY'S TLS connection to the key server and challenge the key server's 32 random bytes (the last 32 bytes of the value). The Gateway is the key server's TLS peer and the relay by design; the identity the key server establishes records binding=tls-exporter for the Gateway's channel. The value carries a digest of the exporter, never the exporter, so the caller learns nothing that satisfies a binding check elsewhere.

Sign ALL 89 bytes under the scheme for your key type; nothing is negotiated. RSA: crypto.sign("sha256", toBeSigned, { key, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: 32 }) -- RSASSA-PSS, SHA-256, MGF1-SHA-256, salt EXACTLY 32 (the key server sets the salt length exactly; a salt of 20 is refused). EC: crypto.sign("sha256", toBeSigned, { key, dsaEncoding: "der" }) -- ECDSA over SHA-256, DER. A PKCS#1 v1.5 signature over toBeSigned, a signature over only the challenge, and the raw privateEncrypt primitive this route relayed before ADR-0107 are each answered SIGNATURE_REJECTED.

WARNING: a conforming signer checks, BEFORE signing, that the value is 89 bytes and begins with the label and a zero byte, and signs nothing else with this key. The label is what makes an AM08 signature unusable as any other signature the key could make -- an EC login key signs ECDSA-SHA256, which is also what an ES256 DPoP proof is -- so a signer that signs whatever it is handed is a signing oracle over bytes chosen upstream of it.

Authentication​

No session required. This operation is reachable without an Authorization header. See Authenticating.

Request​

FieldTypeRequiredDescription
certificatestringyesThe caller's X.509 certificate, DER-encoded then base64-encoded. NOT PEM -- base64 of PEM is base64 of base64, and is refused with a code saying so.

Example

{
"certificate": "..."
}

Responses​

200​

A challenge was issued and a key-server connection is being held for you.

FieldTypeRequiredDescription
pendingstringyesSingle use. Present it to POST /api/session/certificate.
toBeSignedstringyes89 bytes, base64: the channel-bound message (label, zero byte, digest of the Gateway's channel binding, the key server's 32-byte challenge). Sign ALL of these bytes under the scheme for your key type -- not a hash of them, and not only the challenge.
expiresInSecondsnumberyes
detailstringyesNames the scheme

Example

{
"pending": "...",
"toBeSigned": "...",
"expiresInSeconds": 0,
"detail": "..."
}

400​

The certificate could not be used. MISSING_CERTIFICATE, MALFORMED_CERTIFICATE, or CERTIFICATE_NOT_DER -- the last covering both a PEM block and bytes that do not begin with an ASN.1 SEQUENCE tag.

401​

SIGNATURE_REJECTED or CERTIFICATE_NOT_ENROLLED, relayed from the key server. Both can arrive on THIS leg rather than the second: the key server refuses an unenrolled or disabled certificate as soon as it sees it, before issuing any challenge. Or CERTIFICATE_REVOKED -- the key server holds revocation evidence naming this certificate (ADR 0115). It is decided with the enrolment check, before any challenge, so it arrives on THIS leg; detail is the key server's sentence naming the issuer and serial. Presenting the same certificate again will not help.

403​

PRINCIPAL_DISABLED -- the certificate's principal is disabled. Or REVOCATION_STATUS_UNAVAILABLE -- the key server holds no revocation evidence for this certificate's issuer and that issuer's revocation policy refuses to go without it -- or REVOCATION_EVIDENCE_STALE -- the evidence it holds is older than that policy allows (ADR 0115). Both are decided before any challenge, so both arrive on THIS leg. The certificate itself is not what was refused: an operator must import a current CRL for its issuer. detail is the key server's sentence naming the issuer and the policy.

500​

CERT_EXCHANGE_UNEXPECTED -- the key server completed the authentication without issuing a challenge, or the message this Gateway was asked to relay is not shaped like an AM08 channel-bound message. No session was created.

503​

CERT_EXCHANGE_LIMIT -- every concurrent exchange slot is in use. Each holds an open key-server connection, so the number is bounded on purpose. Nothing was sent to the key server. Carries Retry-After.

Error handling​

Every failure answers JSON carrying at least code and detail. Match on code — detail is written for a human debugging the call and its wording is not part of the contract. See the error model.

StatusMeaning
400The certificate could not be used. MISSING_CERTIFICATE, MALFORMED_CERTIFICATE, or CERTIFICATE_NOT_DER -- the last covering both a PEM block and bytes that do not begin with an ASN.1 SEQUENCE tag.
401SIGNATURE_REJECTED or CERTIFICATE_NOT_ENROLLED, relayed from the key server. Both can arrive on THIS leg rather than the second: the key server refuses an unenrolled or disabled certificate as soon as it sees it, before issuing any challenge. Or CERTIFICATE_REVOKED -- the key server holds revocation evidence naming this certificate (ADR 0115). It is decided with the enrolment check, before any challenge, so it arrives on THIS leg; detail is the key server's sentence naming the issuer and serial. Presenting the same certificate again will not help.
403PRINCIPAL_DISABLED -- the certificate's principal is disabled. Or REVOCATION_STATUS_UNAVAILABLE -- the key server holds no revocation evidence for this certificate's issuer and that issuer's revocation policy refuses to go without it -- or REVOCATION_EVIDENCE_STALE -- the evidence it holds is older than that policy allows (ADR 0115). Both are decided before any challenge, so both arrive on THIS leg. The certificate itself is not what was refused: an operator must import a current CRL for its issuer. detail is the key server's sentence naming the issuer and the policy.
500CERT_EXCHANGE_UNEXPECTED -- the key server completed the authentication without issuing a challenge, or the message this Gateway was asked to relay is not shaped like an AM08 channel-bound message. No session was created.
503CERT_EXCHANGE_LIMIT -- every concurrent exchange slot is in use. Each holds an open key-server connection, so the number is bounded on purpose. Nothing was sent to the key server. Carries Retry-After.