crypto

package
v0.1.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: MIT Imports: 24 Imported by: 0

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

View Source
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.

View Source
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).

View Source
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

View Source
var (
	ErrJWSEncode = errors.New("crypto/jws: encode")
	ErrJWSDecode = errors.New("crypto/jws: decode")
	ErrJWSVerify = errors.New("crypto/jws: verify")
)

Sentinel errors.

View Source
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.

AllowedSignatureAlgorithms is the allowlist of signature algorithms we accept on CSRs and certificates. Weaker algorithms (MD5, SHA-1) are rejected even if technically parseable.

View Source
var ErrInvalidP1363Length = errors.New("crypto: invalid P1363 signature length")

ErrInvalidP1363Length is returned when a P1363 signature is not exactly 2*coordinateSize bytes.

View Source
var ErrJWK = errors.New("crypto/jwk: invalid JWK")

ErrJWK classifies JWK parse/validation failures.

Functions

func Canonicalize

func Canonicalize(jsonBytes []byte) ([]byte, error)

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

func CheckKeyStrength(pub any) error

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

func CoordinateBytes(pub *ecdsa.PublicKey) int

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

func DERToP1363(derSig []byte, coordBytes int) ([]byte, error)

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

func DecodeDIDKey(did string) (any, string, error)

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

func DecodeMultibase(encoded string) (any, error)

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

func EncodeMultibase(pub any) (string, error)

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

func OpaqueKeyIDFromHash(keyhash uint32) string

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

func P1363ToDER(p1363Sig []byte) ([]byte, error)

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

func P1363ToScalars(p1363Sig []byte) (*big.Int, *big.Int, error)

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

func ParseVerificationLine(line string) (any, error)

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

func PublicKeyPEM(pub any) (string, error)

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

func PublicKeyToVerificationLine(originName string, pub *ecdsa.PublicKey) (string, error)

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

func SPKIKeyHash4(pub crypto.PublicKey) ([]byte, error)

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

func SPKIKeyIDHex4(pub crypto.PublicKey) (string, error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL