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: 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.
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 (remote flows use a signature). 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/IEC 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. 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. Services map these to 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/IEC 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/IEC 18013-5 §9.1.3.4]. Exactly one of deviceSignature (supported) or deviceMac (unsupported for remote flows) 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/IEC 18013-5 §9.1.2.4]. deviceKey is a COSE_Key, kept raw and parsed by parseCOSEKey; 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/IEC 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 (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/IEC 18013-5 §8.3.2.1.2.3] Table: 0 = OK); go-mdoc rejects the whole response rather than verifying whatever documents did decode (no partial trust of a response the wallet itself flagged as broken).
[ISO/IEC 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/IEC 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/IEC 18013-5 §8.3.2.1.2.2].
type DocumentError ¶
DocumentError — [ISO/IEC 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 never consults this registry, so an unknown doctype still verifies cryptographically — Report is descriptive metadata only, consumed by eudi-verifier-core 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
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.
type Errors ¶
Errors — [ISO/IEC 18013-5 §8.3.2.1.2.2]: NameSpace => (DataElementIdentifier => ErrorCode).
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/IEC 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 and eudi-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/IEC 18013-5 §9.1.2.4]. issuerAuth is a COSE_Sign1 kept raw and delegated to go-eudi-crypto.
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/IEC 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/IEC 18013-5 §9.1.2.4]. status is the IETF Token Status List extension (draft-ietf-oauth-status-list); kept raw, parsed lazily.
type SessionTranscript ¶
type SessionTranscript struct {
// contains filtered or unexported fields
}
SessionTranscript is the [ISO/IEC 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 and consumed by device-authentication transcript binding.
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): 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 production 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 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 go-statuslist. Raw preserves the full status map for forward-compatibility (e.g. identifier_list).
Note: only the type existed here originally so VerifiedDocument.Status type-checked against the locked public interface; parseMSOStatus below now populates it and verify.go wires VerifiedDocument.Status.
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/IEC 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 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 (fail closed). verifyDocument performs MSO (issuer) authentication, issuer-data integrity, device authentication, and status-reference extraction, in that order.
[ISO/IEC 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) and is enforced by device-auth transcript binding (verifyDeviceAuth): an empty/mismatched transcript fails closed.