Documentation
¶
Overview ¶
Package crypto provides shared cryptographic operations used by the RA and TL: X.509 parsing, CSR validation, JWS signing/verification, JSON Canonicalization (RFC 8785), and COSE_Sign1 receipt encoding.
Every function validates its inputs and returns typed errors. Nothing in this package panics on malformed input.
Index ¶
- Constants
- Variables
- func Canonicalize(jsonBytes []byte) ([]byte, error)
- func CertificateFingerprint(cert *x509.Certificate) string
- func CheckCertificateValidity(cert *x509.Certificate, now time.Time) error
- func CheckKeyStrength(pub any) error
- func CheckSignatureAlgorithm(alg x509.SignatureAlgorithm) error
- func CoordinateBytes(pub *ecdsa.PublicKey) int
- func DERToP1363(derSig []byte, coordBytes int) ([]byte, error)
- func DecodeDIDKey(did string) (any, string, error)
- func DecodeMultibase(encoded string) (any, error)
- func EncodeMultibase(pub any) (string, error)
- func MatchCertificateToFQDN(cert *x509.Certificate, expectedFQDN string) error
- func OpaqueKeyIDFromHash(keyhash uint32) string
- func P1363ToDER(p1363Sig []byte) ([]byte, error)
- func P1363ToScalars(p1363Sig []byte) (*big.Int, *big.Int, error)
- func ParseCSRPEM(pemData string) (*x509.CertificateRequest, error)
- func ParseCertificateChainPEM(pemData string) ([]*x509.Certificate, error)
- func ParseCertificatePEM(pemData string) (*x509.Certificate, error)
- func ParseJWK(raw json.RawMessage) (any, error)
- func ParseVerificationLine(line string) (any, error)
- func PublicKeyPEM(pub any) (string, error)
- func PublicKeyToJWK(pub any) (json.RawMessage, error)
- func PublicKeyToVerificationLine(originName string, pub *ecdsa.PublicKey) (string, error)
- func SPKIKeyHash4(pub crypto.PublicKey) ([]byte, error)
- func SPKIKeyIDHex4(pub crypto.PublicKey) (string, error)
- func SignDetachedJWS(ctx context.Context, km port.KeyManager, keyID string, ...) (string, error)
- func SignStandardJWS(ctx context.Context, km port.KeyManager, keyID string, ...) (string, error)
- func ValidateIdentityCSR(csrPEM, expectedAnsName string) (*x509.CertificateRequest, error)
- func ValidateServerCSR(csrPEM, expectedFQDN string) (*x509.CertificateRequest, error)
- func VerifyChain(leaf *x509.Certificate, chain []*x509.Certificate, roots *x509.CertPool) error
- type IdentityProofInput
- type JWSProtectedHeader
- func DecodeHeader(jwsCompact string) (*JWSProtectedHeader, error)
- func DecodeStandardJWS(jwsCompact string) (*JWSProtectedHeader, string, error)
- func VerifyDetachedJWS(ctx context.Context, km port.KeyManager, jwsCompact string, payload []byte) (*JWSProtectedHeader, error)
- func VerifyDetachedWithPEM(jwsCompact string, payload []byte, publicKeyPEM string) (*JWSProtectedHeader, error)
- func VerifyStandardJWSWithPublicKey(pub any, jwsCompact string) (*JWSProtectedHeader, error)
- func VerifyWithPublicKey(pub any, jwsCompact string, payload []byte) (*JWSProtectedHeader, error)
Constants ¶
const ( AlgES256 = "ES256" // ECDSA P-256 + SHA-256 (primary) AlgRS256 = "RS256" // RSA PKCS#1v1.5 + SHA-256 (interop only) AlgEdDSA = "EdDSA" // Ed25519 over the raw signing input (identity proofs) )
JWS algorithm identifiers we support. Matches the reference producer-key store + JWS verification chain.
const IdentityProofPurpose = "ans:identity-proof:v1"
IdentityProofPurpose is the domain-separation discriminator inside every identity proof signing input. An operator's identity keys also sign VCs, DIDComm, and application payloads; a purpose-tagged input can never verify as any of those (nor vice versa).
const (
MinRSAKeyBits = 2048
)
Minimum key strengths enforced on submitted CSRs and certificates. These match current NIST recommendations and CA/Browser Forum Baseline Requirements for publicly trusted certificates.
Variables ¶
var ( ErrJWSEncode = errors.New("crypto/jws: encode") ErrJWSDecode = errors.New("crypto/jws: decode") ErrJWSVerify = errors.New("crypto/jws: verify") )
Sentinel errors.
var ( ErrPEMParse = errors.New("crypto: could not parse PEM") ErrCertParse = errors.New("crypto: could not parse certificate") ErrCSRParse = errors.New("crypto: could not parse CSR") ErrWeakAlgorithm = errors.New("crypto: signature algorithm not allowed") ErrWeakKey = errors.New("crypto: key does not meet minimum strength") ErrInvalidSignature = errors.New("crypto: invalid signature on CSR or certificate") ErrFQDNMismatch = errors.New("crypto: certificate CN/SAN does not match expected FQDN") ErrCertNotYetValid = errors.New("crypto: certificate is not yet valid") ErrCertExpired = errors.New("crypto: certificate is expired") ErrChainInvalid = errors.New("crypto: certificate chain is invalid") )
Sentinel errors for inspection.
var AllowedSignatureAlgorithms = []x509.SignatureAlgorithm{ x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA, x509.SHA256WithRSAPSS, x509.SHA384WithRSAPSS, x509.SHA512WithRSAPSS, x509.ECDSAWithSHA256, x509.ECDSAWithSHA384, x509.ECDSAWithSHA512, }
AllowedSignatureAlgorithms is the allowlist of signature algorithms we accept on CSRs and certificates. Weaker algorithms (MD5, SHA-1) are rejected even if technically parseable.
var ErrInvalidP1363Length = errors.New("crypto: invalid P1363 signature length")
ErrInvalidP1363Length is returned when a P1363 signature is not exactly 2*coordinateSize bytes.
var ErrJWK = errors.New("crypto/jwk: invalid JWK")
ErrJWK classifies JWK parse/validation failures.
Functions ¶
func Canonicalize ¶
Canonicalize transforms arbitrary JSON input into RFC 8785 canonical form. Two semantically equal JSON values produce identical canonical byte sequences, which is what lets us sign JSON payloads deterministically.
func CertificateFingerprint ¶
func CertificateFingerprint(cert *x509.Certificate) string
CertificateFingerprint returns the lowercase hex SHA-256 fingerprint of the certificate's DER encoding.
func CheckCertificateValidity ¶
func CheckCertificateValidity(cert *x509.Certificate, now time.Time) error
CheckCertificateValidity returns nil if the certificate is currently within its validity period.
func CheckKeyStrength ¶
CheckKeyStrength verifies that the public key in the given certificate or CSR meets the minimum strength requirements.
func CheckSignatureAlgorithm ¶
func CheckSignatureAlgorithm(alg x509.SignatureAlgorithm) error
CheckSignatureAlgorithm returns nil if the given algorithm is on the allowlist of accepted algorithms, or ErrWeakAlgorithm otherwise.
func CoordinateBytes ¶
CoordinateBytes returns the zero-padded coordinate size (in bytes) for an ECDSA public key, which is the half-length of a P1363 signature under that key. P-256 → 32, P-384 → 48, P-521 → 66.
func DERToP1363 ¶
DERToP1363 converts an ASN.1 DER-encoded ECDSA signature to IEEE P1363 format (raw r || s, each scalar zero-padded to coordBytes). For P-256, coordBytes is 32 and the output is 64 bytes.
func DecodeDIDKey ¶ added in v0.1.6
DecodeDIDKey decodes a did:key identifier into its public key and the verification-method id the did:key method defines ("{did}#{method-specific-id}").
func DecodeMultibase ¶ added in v0.1.6
DecodeMultibase decodes a multibase-encoded public key (a did:key method-specific id or a DID document's publicKeyMultibase) into a verifying key. Only base58btc ('z' prefix) is accepted — the encoding both specs mandate. Supported multicodecs: ed25519-pub and p256-pub; others fail with the prefix named.
func EncodeMultibase ¶ added in v0.1.6
EncodeMultibase renders a supported public key in the did:key / Multikey form: 'z' + base58btc(multicodec varint || key bytes). The inverse of DecodeMultibase; used by tooling that mints did:key identifiers.
func MatchCertificateToFQDN ¶
func MatchCertificateToFQDN(cert *x509.Certificate, expectedFQDN string) error
MatchCertificateToFQDN returns nil if the certificate's CN or one of its DNS SANs exactly matches (case-insensitive) the expected FQDN.
func OpaqueKeyIDFromHash ¶
OpaqueKeyIDFromHash returns the 8-char hex rendering of a C2SP-style 4-byte keyhash (big-endian uint32) — the exact string the reference stamps into the `kid` header of checkpoint JWS signatures so that verifiers can link the signature line to the public key advertised at `/root-keys`. Mirrors reference `internal/key/utils.go:OpaqueKeyIDFromHash`.
func P1363ToDER ¶
P1363ToDER converts an IEEE P1363 ECDSA signature (raw r || s) to ASN.1 DER form. Used on the verify path when the verifier needs to hand bytes to a DER-expecting API (not currently hit by our stack since ecdsa.Verify takes big.Int scalars directly, but kept for completeness and symmetry).
func P1363ToScalars ¶
P1363ToScalars parses a P1363 signature into its r and s scalars. Callers that want to use ecdsa.Verify(pub, digest, r, s) directly skip the DER round-trip.
func ParseCSRPEM ¶
func ParseCSRPEM(pemData string) (*x509.CertificateRequest, error)
ParseCSRPEM parses a PEM-encoded certificate signing request and verifies its self-signature.
func ParseCertificateChainPEM ¶
func ParseCertificateChainPEM(pemData string) ([]*x509.Certificate, error)
ParseCertificateChainPEM parses a PEM bundle that may contain multiple certificates. Returns the certificates in document order.
func ParseCertificatePEM ¶
func ParseCertificatePEM(pemData string) (*x509.Certificate, error)
ParseCertificatePEM parses a single PEM-encoded X.509 certificate.
func ParseJWK ¶ added in v0.1.6
func ParseJWK(raw json.RawMessage) (any, error)
ParseJWK parses a JWK into a verifying public key:
kty=EC, crv=P-256 → *ecdsa.PublicKey (ES256) kty=OKP, crv=Ed25519 → ed25519.PublicKey (EdDSA) kty=RSA → *rsa.PublicKey (RS256; CheckKeyStrength enforced)
Everything else fails with a message naming exactly why.
func ParseVerificationLine ¶
ParseVerificationLine is the inverse of PublicKeyToVerificationLine — it parses a sumdb-note-style verification key string into the underlying ed25519 or ECDSA P-256 public key. The return type is `any` because the line carries either algorithm; callers should type-assert to the expected key type.
Accepts either the ed25519 form (`0x01 || 32B pub`) or the ECDSA P-256 form (`0x02 || SPKI-DER`). Any other first byte is rejected.
func PublicKeyPEM ¶
PublicKeyPEM marshals an ed25519 or ECDSA public key as a standard PEM-encoded SPKI block. Used for the `publicKeyPem` field on the checkpoint response, where the reference TL surfaces the primary signer's PEM alongside the signed note.
func PublicKeyToJWK ¶ added in v0.1.6
func PublicKeyToJWK(pub any) (json.RawMessage, error)
PublicKeyToJWK renders a supported public key as a JWK. Used by registrant-side tooling (the demo signer's embedded `jwk` header) and tests — never by sealing, which quotes the DID document's verification method verbatim instead of re-encoding anything.
func PublicKeyToVerificationLine ¶
PublicKeyToVerificationLine encodes an ECDSA P-256 public key in the sumdb-note-style verification format the reference TL serves at /root-keys:
<origin>+<keyhash-hex>+<base64(0x02 || SPKI-DER)>
Where `keyhash-hex` is the first 4 bytes of SHA-256(SPKI-DER) as a zero-padded 8-char hex string (big-endian uint32). The 0x02 byte marks the key as ECDSA; base-64 encoding uses std alphabet with padding (matching the reference). Callers concatenate the returned line with a newline and repeat per key for the full /root-keys body.
Byte-for-byte match with the reference TL's `key.PublicKeyToVerificationKey` helper.
func SPKIKeyHash4 ¶
SPKIKeyHash4 returns the C2SP 4-byte "opaque key hash":
SHA-256(SPKI-DER)[0:4] — interpreted big-endian as uint32
then written out as 4 big-endian bytes. This is the shape used as a COSE `kid` header parameter in SCITT receipts so verifiers who hold the /root-keys PEM can match the receipt's `kid` byte-for- byte without guessing a digest algorithm.
Distinct from the 8-hex-char `OpaqueKeyIDFromHash` form that the detached-JWS header uses (which is the same 4 bytes rendered as a hex string) — callers should pick the form appropriate to their wire format.
func SPKIKeyIDHex4 ¶
SPKIKeyIDHex4 returns the hex-encoded 4-byte opaque key ID — the string form the detached-JWS header uses as `kid`.
func SignDetachedJWS ¶
func SignDetachedJWS( ctx context.Context, km port.KeyManager, keyID string, header JWSProtectedHeader, payload []byte, ) (string, error)
SignDetachedJWS produces a detached JWS — compact-serialization `header..signature` with the payload segment empty. The payload passed in is JCS-canonicalized before being hashed, so verification is stable regardless of JSON whitespace or key ordering.
`keyID` is looked up in the KeyManager and must resolve to an ECDSA P-256 key (for ES256) or an RSA key (for RS256). If header.Alg is empty, it's auto-selected from the key's public-half type.
func SignStandardJWS ¶
func SignStandardJWS( ctx context.Context, km port.KeyManager, keyID string, header JWSProtectedHeader, payload any, ) (string, error)
SignStandardJWS produces a *standard* JWS — compact-serialization `header.payload.signature` with the payload embedded (not detached). Used by the TL checkpoint signer where the verifier must know what was signed without a separate out-of-band payload channel.
`payload` is any JSON-marshalable value; JCS is applied before signing so byte output is stable regardless of Go map iteration order. Matches the reference TL's `KMSSigner.SignStandard`.
func ValidateIdentityCSR ¶
func ValidateIdentityCSR(csrPEM, expectedAnsName string) (*x509.CertificateRequest, error)
ValidateIdentityCSR verifies an identity CSR before it is signed. It checks: PEM parsing, self-signature, algorithm allowlist, key strength, and that a URI SAN matches the expected ANS name.
func ValidateServerCSR ¶
func ValidateServerCSR(csrPEM, expectedFQDN string) (*x509.CertificateRequest, error)
ValidateServerCSR verifies a CSR intended for server-certificate issuance. Unlike the identity CSR (URI SAN = ANS name), server CSRs carry the agent's FQDN as a DNS SAN — the standard CA/Browser Forum shape for TLS server certs. Checks: PEM parsing, self-signature, algorithm allowlist, key strength, and that a DNS SAN (or CN as a fallback) matches the expected FQDN.
Matches the reference RA's server-CSR validation path, which also requires the FQDN to appear as a DNS SAN because most public CAs have deprecated CN-only matching since CA/B Forum BR 2017.
func VerifyChain ¶
func VerifyChain(leaf *x509.Certificate, chain []*x509.Certificate, roots *x509.CertPool) error
VerifyChain verifies leaf against the given intermediates with an optional custom root pool. Pass nil for roots to use the system roots. In local-dev, a nil roots pool plus a self-signed leaf will fail — callers should use SkipVerifyChain in that case.
Types ¶
type IdentityProofInput ¶ added in v0.1.6
type IdentityProofInput struct {
Identifier string `json:"identifier"`
IdentityID string `json:"identityId"`
Nonce string `json:"nonce"`
Purpose string `json:"purpose"`
RaID string `json:"raId"`
Scheme string `json:"scheme"`
}
IdentityProofInput is the ONE signing input in the identity proof system — the JSON object whose RFC 8785 (JCS) canonical bytes the registrant signs to prove key control. It binds the proof to:
- this identifier (anti-substitution),
- this identity object (identityId — anti-cross-use),
- this challenge round (nonce — anti-replay),
- this protocol (purpose — anti-cross-protocol-confusion),
- this RA deployment (raId — a signature minted against staging can never replay against production),
- this scheme.
The RA serves the encoded form (SigningInput) in the 202 response; clients sign it verbatim and never need a JCS implementation — the RA checks payload-equality before verifying any signature, so canonicalization-mismatch interop failures are structurally impossible.
func (IdentityProofInput) Canonical ¶ added in v0.1.6
func (in IdentityProofInput) Canonical() ([]byte, error)
Canonical returns the JCS-canonical bytes of the proof input — the exact bytes a compact JWS over this input must carry as its payload.
func (IdentityProofInput) SigningInput ¶ added in v0.1.6
func (in IdentityProofInput) SigningInput() (string, error)
SigningInput returns the base64url (unpadded) encoding of the canonical bytes — the string served in the 202 challenge response. A compact JWS's payload segment MUST equal this string verbatim.
type JWSProtectedHeader ¶
type JWSProtectedHeader struct {
// Alg is the JWS algorithm identifier. Required.
Alg string `json:"alg"`
// Kid identifies the signing key within the KeyManager. Required.
Kid string `json:"kid"`
// Typ is an optional type hint (e.g., "JWT").
Typ string `json:"typ,omitempty"`
// Timestamp is the unix-seconds timestamp the signer produced the
// signature. Matches the reference header's `timestamp` field.
// Zero means "do not include"; callers set this explicitly so the
// signing timestamp is observable by verifiers.
Timestamp int64 `json:"timestamp,omitempty"`
// RAID identifies the Registration Authority producing the signature.
// The TL uses this together with Kid to look up the producer key
// when verifying.
RAID string `json:"raid,omitempty"`
// Jwk optionally embeds the signer's public key (RFC 7515 §4.1.3).
// Identity control proofs set it so the noop DID resolver can
// synthesize a document from the submitted proofs; the web
// resolver ignores it — the authoritatively resolved document is
// always the key source. Never set on producer/TL signatures.
Jwk json.RawMessage `json:"jwk,omitempty"`
// Crit (RFC 7515 §4.1.11) names header parameters the signer
// requires verifiers to understand. This implementation supports
// no critical extensions, so decode REJECTS any JWS bearing a
// non-empty crit — required behavior for a conforming verifier,
// and the design's third-party verification recipe (§5.5) states
// the same rule. Never emitted on the signing path.
Crit []string `json:"crit,omitempty"`
}
JWSProtectedHeader is the JWS protected header shape used by the TL event envelope and the RA producer attestations. Field names and order follow the reference TL's JWS conventions so outputs are byte-for-byte compatible with jose-compliant tooling.
We support exactly two signing algorithms, matching the reference:
- AlgES256 — ECDSA P-256 + SHA-256 (primary; producer + TL attestation)
- AlgRS256 — RSA PKCS#1v1.5 + SHA-256 (interop only; never emitted by ans-ra, but a producer-key store may hold RSA keys)
ECDSA signatures are emitted in IEEE P1363 form (raw r || s) per RFC 7518 §3.4. This is the format every standards-compliant JWS library expects; ASN.1 DER is the format Go's crypto.Signer returns by default, so sign paths convert at the boundary.
func DecodeHeader ¶
func DecodeHeader(jwsCompact string) (*JWSProtectedHeader, error)
DecodeHeader parses the base64url-encoded header segment of a detached JWS and returns the typed header. Useful when a caller needs to route on `kid` or `raid` before it has a public key in hand (e.g., to look the key up in a trust store).
func DecodeStandardJWS ¶ added in v0.1.6
func DecodeStandardJWS(jwsCompact string) (*JWSProtectedHeader, string, error)
DecodeStandardJWS splits a standard (non-detached) compact JWS and returns its decoded protected header plus the raw base64url payload segment, without verifying anything. The identity verify-control path uses it to (a) read kid/alg/jwk for key selection and (b) enforce payload-equality against the served signingInput BEFORE any signature verification — clients sign the served bytes verbatim and never canonicalize.
func VerifyDetachedJWS ¶
func VerifyDetachedJWS( ctx context.Context, km port.KeyManager, jwsCompact string, payload []byte, ) (*JWSProtectedHeader, error)
VerifyDetachedJWS validates a detached JWS against the given payload using the KeyManager's public key for the header's `kid`. Returns the decoded header on success so callers can inspect raid/timestamp/etc.
func VerifyDetachedWithPEM ¶
func VerifyDetachedWithPEM(jwsCompact string, payload []byte, publicKeyPEM string) (*JWSProtectedHeader, error)
VerifyDetachedWithPEM validates a detached JWS against the given payload using a PEM-encoded public key. This is the entry point the Transparency Log uses to check producer signatures: the public key comes from the producer-key trust store, not from the local KeyManager.
Implementation uses go-jose/v4's ParseDetached to guarantee we interoperate with every other standards-compliant JWS library — if our output parses here, it'll parse anywhere.
func VerifyStandardJWSWithPublicKey ¶
func VerifyStandardJWSWithPublicKey(pub any, jwsCompact string) (*JWSProtectedHeader, error)
VerifyStandardJWSWithPublicKey verifies a non-detached (standard) compact JWS of the form `b64(header).b64(payload).b64(sig)` against the given public key. The signing input is the raw `b64(header).b64(payload)` prefix — the payload is NOT re-canonicalized (unlike the detached-form verifier), because in standard JWS the embedded payload bytes are the authoritative signed bytes.
Used by the TL checkpoint-read path to re-verify stored JWS signatures and surface a `valid` flag on CheckpointSignature. Mirrors the reference TL's `TesseraJWSSigner.VerifyTreeHead`.
func VerifyWithPublicKey ¶
func VerifyWithPublicKey(pub any, jwsCompact string, payload []byte) (*JWSProtectedHeader, error)
VerifyWithPublicKey verifies a detached JWS against a payload using the given public key directly (no KeyManager, no PEM). Exposed for the ans-verify CLI. Accepts *ecdsa.PublicKey or *rsa.PublicKey.