Documentation
¶
Overview ¶
Package mdoc implements ISO/IEC 18013-5 mdoc structures (CBOR/COSE) and the verification of a DeviceResponse for remote flows (ISO/IEC TS 18013-7 / OpenID4VP Annex B): MSO / issuer-data authentication, issuer-data integrity, and device authentication bound to a SessionTranscript. A minimal Issue / DevicePresent façade supports test wallets and a future issuer.
The package is framework-free (ADR-0004): it does no logging, takes a context.Context and an IssuerChainResolver callback for trust, and delegates every COSE/X.509 operation to go-eudi-crypto (ECCG-pinned policy, hard rule 4). It never names a COSE/JWS algorithm; the sole MSO digest-algorithm allow-list (digest.go) is flagged for migration into go-eudi-crypto.
deviceMac (session-encryption MAC) is out of scope for remote presentment (WP-03 Decisions).
Index ¶
- Variables
- func DevicePresent(ctx context.Context, deviceKey crypto.Signer, issuerSigned []byte, ...) ([]byte, error)
- func KnownDocType(docType string) bool
- type DeviceAuth
- type DeviceKeyInfo
- type DeviceResponse
- type DeviceSigned
- type DigestIDs
- type Document
- type DocumentError
- type DocumentReport
- type DocumentTemplate
- type ElementReport
- type Errors
- type Issuer
- type IssuerChainResolver
- type IssuerNameSpaces
- type IssuerSigned
- type IssuerSignedItem
- type MobileSecurityObject
- type Option
- type SessionTranscript
- type StatusRef
- type ValidityInfo
- type ValueDigests
- type VerifiedDocument
- type Verifier
- type VerifyInput
Constants ¶
This section is empty.
Variables ¶
var ( // ErrMalformed: CBOR structurally invalid or violates the hardened decode // options. Maps to err:credential:parse. ErrMalformed = errors.New("mdoc: malformed") // ErrUnsupported: a well-formed but unsupported construct (e.g. version). ErrUnsupported = errors.New("mdoc: unsupported") // ErrIssuerAuth: IssuerAuth (MSO) signature or chain resolution failed. // Maps to err:credential:issuer-untrusted. ErrIssuerAuth = errors.New("mdoc: issuer authentication failed") // ErrIntegrity: an IssuerSignedItem digest does not match MSO ValueDigests, // or an item is absent from ValueDigests. Maps to err:credential:integrity. ErrIntegrity = errors.New("mdoc: issuer-data integrity check failed") // ErrDeviceAuth: DeviceAuth signature invalid / not bound to the transcript. // Maps to err:credential:binding-failed. ErrDeviceAuth = errors.New("mdoc: device authentication failed") // ErrValidity: current time outside the MSO ValidityInfo window. // Maps to err:credential:expired. ErrValidity = errors.New("mdoc: outside validity window") // ErrDocTypeMismatch: MSO docType != Document docType, or != ExpectedDocType. ErrDocTypeMismatch = errors.New("mdoc: docType mismatch") // ErrDeviceMacUnsupported: deviceMac present; remote flows require a // signature (WP-03 Decisions). Maps to err:credential:binding-failed. ErrDeviceMacUnsupported = errors.New("mdoc: deviceMac not supported (use deviceSignature)") // ErrDigestAlg: MSO digestAlgorithm not in the SHA-2 allow-list. ErrDigestAlg = errors.New("mdoc: MSO digest algorithm not allowed") // ErrStatus: MSO status extension present but malformed. ErrStatus = errors.New("mdoc: malformed status extension") // ErrDeviceResponseStatus: DeviceResponse.status is non-zero, or // documentErrors is non-empty (ISO 18013-5 §8.3.2.1.2.3 Table: 0 = OK). // The wallet itself signaled a problem producing the response; treated as // untrustworthy as a whole rather than partially trusted (hard rule 7). ErrDeviceResponseStatus = errors.New("mdoc: DeviceResponse reports a non-OK status") )
Sentinel errors. All are wrapped with %w and carry only safe context (namespaces, element identifiers, digest IDs, doctypes) — never element values (hard rule 3). Services map these to conventions.md reason codes.
Functions ¶
func DevicePresent ¶
func DevicePresent(ctx context.Context, deviceKey crypto.Signer, issuerSigned []byte, disclose map[string][]string, st SessionTranscript) ([]byte, error)
DevicePresent produces a DeviceResponse from an issued IssuerSigned by disclosing only the requested elements and signing the DeviceAuthentication with the holder's device key, bound to st (ISO 18013-5 §9.1.3). This is the test-wallet counterpart to Verify.
func KnownDocType ¶
KnownDocType reports whether docType is in the registry (ARF 2.9 PID / ISO 18013-5 mDL). Unknown doctypes still verify; they are annotated, not rejected.
Types ¶
type DeviceAuth ¶
type DeviceAuth struct {
DeviceSignature cbor.RawMessage `cbor:"deviceSignature,omitempty"`
DeviceMac cbor.RawMessage `cbor:"deviceMac,omitempty"`
}
DeviceAuth — ISO 18013-5 §9.1.3.4. Exactly one of deviceSignature (supported) or deviceMac (unsupported for remote flows, WP-03 Decisions) is present.
type DeviceKeyInfo ¶
type DeviceKeyInfo struct {
DeviceKey cbor.RawMessage `cbor:"deviceKey"`
KeyAuthorizations cbor.RawMessage `cbor:"keyAuthorizations,omitempty"`
KeyInfo map[int]cbor.RawMessage `cbor:"keyInfo,omitempty"`
}
DeviceKeyInfo — ISO 18013-5 §9.1.2.4. deviceKey is a COSE_Key, kept raw and parsed by parseCOSEKey (T-03.3); curve is validated via ECCG policy.
type DeviceResponse ¶
type DeviceResponse struct {
Version string `cbor:"version"`
Documents []Document `cbor:"documents,omitempty"`
DocumentErrors []DocumentError `cbor:"documentErrors,omitempty"`
Status uint `cbor:"status"`
}
DeviceResponse — ISO 18013-5 §8.3.2.1.2.2. Text-keyed CBOR map.
func DecodeDeviceResponse ¶
func DecodeDeviceResponse(raw []byte) (*DeviceResponse, error)
DecodeDeviceResponse parses an ISO 18013-5 DeviceResponse from untrusted CBOR using the hardened decoder. Tolerant of unknown map members (CDDL forward-compatibility) but strict on the documented shape. Never panics on malformed input (hard rule 5; fuzzed by FuzzDecodeDeviceResponse).
A non-zero top-level status or a non-empty documentErrors is the wallet's own signal that something went wrong producing the response (ISO 18013-5 §8.3.2.1.2.3 Table: 0 = OK); go-mdoc rejects the whole response rather than verifying whatever documents did decode (hard rule 7 — no partial trust of a response the wallet itself flagged as broken).
ISO 18013-5 §8.3.2.1.2.2 DeviceResponse.
type DeviceSigned ¶
type DeviceSigned struct {
NameSpaces cbor.RawMessage `cbor:"nameSpaces"`
DeviceAuth DeviceAuth `cbor:"deviceAuth"`
}
DeviceSigned — ISO 18013-5 §8.3.2.1.2.2. nameSpaces is DeviceNameSpacesBytes (#6.24(bstr .cbor DeviceNameSpaces)); kept raw for the DeviceAuthentication.
type Document ¶
type Document struct {
DocType string `cbor:"docType"`
IssuerSigned IssuerSigned `cbor:"issuerSigned"`
DeviceSigned DeviceSigned `cbor:"deviceSigned"`
Errors Errors `cbor:"errors,omitempty"`
}
Document — ISO 18013-5 §8.3.2.1.2.2.
type DocumentError ¶
DocumentError — ISO 18013-5 §8.3.2.1.2.2: DocType => ErrorCode(int).
type DocumentReport ¶
type DocumentReport struct {
DocType string
KnownDocType bool
Elements []ElementReport // sorted by namespace, then identifier
}
DocumentReport annotates a VerifiedDocument for the verification report.
func Report ¶
func Report(vd VerifiedDocument) DocumentReport
Report annotates the disclosed namespaces of a verified document using the doctype/namespace registry and its value-type hooks. Pure and value-free: Verify (T-03.3–5) never consults this registry, so an unknown doctype still verifies cryptographically — Report is descriptive metadata only, consumed by verifier-core (WP-09) for its client report + redaction hints.
type DocumentTemplate ¶
type DocumentTemplate struct {
DocType string
Namespaces map[string]map[string]any
ValidityInfo ValidityInfo
DigestAlg string // "SHA-256" (default) | "SHA-384" | "SHA-512"
Status *StatusRef
}
DocumentTemplate describes an mdoc to issue. Minimal by design (test wallet + future issuer); the verifier never uses it.
type ElementReport ¶
type ElementReport struct {
Namespace string
Identifier string
Known bool // element registered for this doctype's namespace
PII bool // portrait/biometric — redact in logs/report (conventions.md)
TypeValid bool // value passed the registered type hook (true if no hook)
}
ElementReport annotates one disclosed element for the verification report. It carries identifiers and flags only — never a value (hard rule 3).
type Issuer ¶
type Issuer struct {
// contains filtered or unexported fields
}
Issuer signs MobileSecurityObjects with a document-signer key via go-eudi-crypto and stamps the issuer certificate chain (x5chain).
func NewIssuer ¶
func NewIssuer(kp eudicrypto.KeyProvider, keyID string, x5chain [][]byte) *Issuer
NewIssuer returns an Issuer using kp[keyID] as the document-signer key and x5chain (DER) as the issuer chain placed in the IssuerAuth unprotected header.
func (*Issuer) Issue ¶
func (i *Issuer) Issue(ctx context.Context, doc DocumentTemplate, deviceKey crypto.PublicKey) ([]byte, error)
Issue builds an IssuerSigned (ISO 18013-5 §9.1.2): one IssuerSignedItem per element (fresh 32-byte random salt), the ValueDigests over the exact IssuerSignedItemBytes, an MSO sealing the device key, and the COSE_Sign1 IssuerAuth. deviceKey must be an ECCG-allowed EC public key.
type IssuerChainResolver ¶
IssuerChainResolver resolves the issuer certificate chain (x5chain, DER) to the document-signer public key. It is the trust boundary: go-mdoc stays trust-agnostic (ADR-0004) and verifier-core wires this to go-eudi-trust.
type IssuerNameSpaces ¶
type IssuerNameSpaces map[string][]cbor.RawMessage
IssuerNameSpaces — NameSpace => [+ IssuerSignedItemBytes]. Each element is a #6.24(bstr .cbor IssuerSignedItem); kept raw so digests hash wire bytes.
type IssuerSigned ¶
type IssuerSigned struct {
NameSpaces IssuerNameSpaces `cbor:"nameSpaces,omitempty"`
IssuerAuth cbor.RawMessage `cbor:"issuerAuth"`
}
IssuerSigned — ISO 18013-5 §9.1.2.4. issuerAuth is a COSE_Sign1 kept raw and delegated to go-eudi-crypto (hard rule 4).
type IssuerSignedItem ¶
type IssuerSignedItem struct {
DigestID uint `cbor:"digestID"`
Random []byte `cbor:"random"`
ElementIdentifier string `cbor:"elementIdentifier"`
ElementValue cbor.RawMessage `cbor:"elementValue"` // any DataElementValue, kept raw
}
IssuerSignedItem — ISO 18013-5 §9.1.2.4.
type MobileSecurityObject ¶
type MobileSecurityObject struct {
Version string `cbor:"version"`
DigestAlgorithm string `cbor:"digestAlgorithm"` // "SHA-256"|"SHA-384"|"SHA-512"
ValueDigests ValueDigests `cbor:"valueDigests"`
DeviceKeyInfo DeviceKeyInfo `cbor:"deviceKeyInfo"`
DocType string `cbor:"docType"`
ValidityInfo ValidityInfo `cbor:"validityInfo"`
Status cbor.RawMessage `cbor:"status,omitempty"`
}
MobileSecurityObject — ISO 18013-5 §9.1.2.4. status is the IETF Token Status List extension (draft-ietf-oauth-status-list, T-03.8); kept raw, parsed lazily.
type SessionTranscript ¶
type SessionTranscript struct {
// contains filtered or unexported fields
}
SessionTranscript is the ISO 18013-5 §9.1.5.1 / ISO/IEC TS 18013-7 Annex B SessionTranscript = [DeviceEngagementBytes, EReaderKeyBytes, Handover], carried as its exact CBOR encoding. It is opaque to callers of Verify: built by go-oid4vp via the OID4VPHandover / OID4VPDCAPIHandover constructors (T-03.6) and consumed by device-authentication transcript binding (T-03.5).
func OID4VPDCAPIHandover ¶
func OID4VPDCAPIHandover(origin, nonce, jwkThumbprint string) SessionTranscript
OID4VPDCAPIHandover builds the SessionTranscript for the W3C Digital Credentials API flow: SessionTranscript = [null, null, OID4VPDCAPIHandover], OID4VPDCAPIHandover = ["OpenID4VPDCAPIHandover", SHA-256(CBOR(OpenID4VPDCAPIHandoverInfo))], OpenID4VPDCAPIHandoverInfo = [origin, nonce, jwkThumbprint] (OpenID4VP 1.0 Annex B.2.6.2; no client_id or response_uri in this variant — origin carries the RP identity signal instead). jwkThumbprint: see OID4VPHandover's doc comment (same FLAG applies).
func OID4VPHandover ¶
func OID4VPHandover(clientID, nonce, jwkThumbprint, responseURI string) SessionTranscript
OID4VPHandover builds the SessionTranscript for the redirect (non-DC-API) OpenID4VP flow: SessionTranscript = [null, null, OID4VPHandover], where OID4VPHandover = ["OpenID4VPHandover", SHA-256(CBOR(OpenID4VPHandoverInfo))] and OpenID4VPHandoverInfo = [client_id, nonce, jwkThumbprint, response_uri] (OpenID4VP 1.0 Annex B.2.6.1). jwkThumbprint is the RFC 7638 thumbprint of the RP's ephemeral encryption key when the response is JWE-encrypted (response_mode=direct_post.jwt); pass "" (encoded as CBOR null) when it is not. SHA-256 is fixed by the profile — a spec constant, not an ECCG-negotiable choice, so it is not routed through the algorithm allow-list.
FLAG (2026-07-06 EU cross-check, docs/mdoc-eu-gap-report.md): this replaces an earlier, unverified 3-hash-tuple construction that never matched any known implementation. Corrected to match the EU reference verifier (eudi-srv-verifier-endpoint-main, DocumentValidator.kt buildOpenId4VpHandover) after the OpenID4VP 1.0 Annex B text could not be fetched this session (WebFetch truncates before Annex B; no vendored copy in references/). The outer shape (identifier + single HandoverInfo hash) and inner element order/membership are well-corroborated against that production reference; jwkThumbprint's exact CBOR type (tstr assumed here) is NOT yet confirmed against primary spec text — re-verify before wiring in an encrypted-response (WP-08) deployment.
func (SessionTranscript) Bytes ¶
func (s SessionTranscript) Bytes() []byte
Bytes returns the exact CBOR encoding of the SessionTranscript array. Used for golden-vector byte-exactness (T-03.6) and by go-oid4vp when it needs the on-the-wire transcript.
type StatusRef ¶
StatusRef is the MSO status extension reference (IETF Token Status List, draft-ietf-oauth-status-list) surfaced to the caller. go-mdoc only extracts it; fetching and evaluating the status list is WP-04 (go-statuslist). Raw preserves the full status map for forward-compatibility (e.g. identifier_list).
T-03.3 note: only the type existed here originally so VerifiedDocument.Status type-checked against the WP-03 README "Locked public interface" (hard rule 11, binding from T-03.3 onward). T-03.8 adds parseMSOStatus below and wires VerifiedDocument.Status in verify.go.
type ValidityInfo ¶
type ValidityInfo struct {
Signed time.Time `cbor:"signed"`
ValidFrom time.Time `cbor:"validFrom"`
ValidUntil time.Time `cbor:"validUntil"`
ExpectedUpdate *time.Time `cbor:"expectedUpdate,omitempty"`
}
ValidityInfo — ISO 18013-5 §9.1.2.4. tdate (#6.0 tstr) fields; also the public output type embedded in VerifiedDocument (README target).
type VerifiedDocument ¶
type VerifiedDocument struct {
DocType string
Namespaces map[string]map[string]any
ValidityInfo ValidityInfo
DeviceKey crypto.PublicKey
Status *StatusRef
MSODigestAlg string
}
VerifiedDocument is the result for one Document: disclosed + digest-checked namespaces, the validity window, the device key, an optional status ref, and the MSO digest algorithm. Namespaces carry disclosed values forwarded to the caller (ADR-0005) and are never persisted.
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier holds the injected clock and hardened codec (package-level decMode).
func NewVerifier ¶
NewVerifier returns a Verifier defaulting to time.Now.
func (*Verifier) Verify ¶
func (v *Verifier) Verify(ctx context.Context, in VerifyInput) ([]VerifiedDocument, error)
Verify parses and verifies a DeviceResponse for remote flows. Fail closed: any failing check aborts with a precise sentinel (hard rule 7). verifyDocument performs MSO (issuer) authentication, issuer-data integrity, device authentication, and status-reference extraction, in that order.
ISO 18013-5 §8.3 / §9.1; ISO/IEC TS 18013-7 Annex B.
type VerifyInput ¶
type VerifyInput struct {
DeviceResponse []byte
SessionTranscript SessionTranscript
IssuerChainResolver IssuerChainResolver
ExpectedDocType string
}
VerifyInput bundles one DeviceResponse with the session binding and the trust callback. SessionTranscript is opaque here (built by go-oid4vp via the constructors in T-03.6) and is enforced by device-auth transcript binding (verifyDeviceAuth): an empty/mismatched transcript fails closed.