ISO/IEC DIS 18013-5 mDL implementation in Rust.
It is intended to be used in creating apps for devices and readers that can interact with each other to exchange mDL
data.
This crate contains a CLI tool. Run the --help command to see what actions you can perform.
cargo run -- --helpFor example, you can get the namespaces and elements defined in an mDL:
cat test/stringified-mdl.txt | cargo run -- get-namespaces -Here are some examples on how to use the library. You can see more in tests and read about in the dedicated README.
This example demonstrates a simulated device and reader interaction.
The reader requests the age_over_21 element, and the device responds with that value.
sequenceDiagram
autonumber
Note over Device: Initialize session
Device ->> Device: Create QR Code Engagement
Device -) + Reader: Send QR Code
Reader ->> - Reader: Establish Session
Reader -) + Device: Request age_over_21
Device -)- Reader: Send age_over_21
Reader ->> Reader: Process age_over_21
Note over Device, Reader: Session finished
- Device initialization and engagement:
- The device creates a
QR codecontainingDeviceEngagementdata, which includes its public key. - Internally:
- The device initializes with the
mDLdata, private key, and public key.
- The device initializes with the
- The device creates a
- Reader processing QR code and requesting needed fields:
- The reader processes the
QR codeand creates a request for theage_over_21element. - Internally:
- Generates its private and public keys.
- Initiates a key exchange, and generates the session keys.
- The request is encrypted with the reader's session key.
- The reader processes the
- Device accepting request and responding:
- The device receives the request and creates a response with the
age_over_21element. - Internally:
- Initiates the key exchange, and generates the session keys.
- Decrypts the request with the reader's session key.
- Parse and validate it creating error response if needed.
- The response is encrypted with the device's session key.
- The device receives the request and creates a response with the
- Reader Processing mDL data:
- The reader processes the response and prints the value of the
age_over_21element.
- The reader processes the response and prints the value of the
There are several states through which the device goes during the interaction:
stateDiagram
state Device {
[*] --> SessionManagerInit: initialise
SessionManagerInit --> SessionManagerEngaged: qr_engagement
SessionManagerEngaged --> SessionManager: process_session_establishment
}
state SessionManagerInit {
[*] --> [*]
}
state SessionManagerEngaged {
[*] --> [*]
}
state Reader {
[*] --> [*]
}
state SessionManager {
[*] --> AwaitingRequest
AwaitingRequest --> Signing: prepare_response
Signing --> Signing: get_next_signature_payload
Signing --> ReadyToRespond: submit_next_signature
ReadyToRespond --> AwaitingRequest: retrieve_response
AwaitingRequest --> Signing: handle_request
}
User --> Device
SessionManagerInit --> Reader: qr_engagement
Reader --> SessionManagerEngaged: establish_session
ReadyToRespond --> Reader: handle_response
From the reader's perspective, the flow is simpler:
stateDiagram
state Device {
[*] --> [*]
}
state Reader {
SessionManager --> SessionManager: handle_response
}
User --> Device
Device --> Reader: qr_engagement
Reader --> Device: establish_session
Device --> Reader
Reader --> Device: new_request
You can see the full example in simulated_device_and_reader and a version that
uses State pattern, Arc and Mutex simulated_device_and_reader.
A reader asks for one or more credentials, each with its own doc type, and every document in the response is validated on its own — they are cryptographically independent, and ISO/IEC 18013-5 permits several, including two of the same doc type.
let (mut reader, request, _ble_ident) = reader::SessionManager::establish_session(
reader::Handover::QR(qr),
NonEmptyVec::new(ItemsRequest::mdl(namespaces)),
trust_anchors,
)?;
let outcome = reader
.handle_response(&response, &AnyDocType(MdocProfile::MDL), &fetcher)
.await;ResponseValidationOutcome splits the response four ways: documents passed every check,
failed were evaluated and did not, rejected were never asked for and so never validated,
and errors says why the response as a whole is not wholly good. Check warnings as well —
a warning is a check that could not be completed, so DocumentWarning::Revocation means
revocation status is unknown, not clear. See the rustdoc on those types for the full
contract, and end_to_end.rs for worked exchanges.
Annex B's certificate structure applies to any mdoc, but its OIDs are mDL-specific — the
DIS of the second edition says Annex B does not apply when reading other mdoc based
documents. MdocProfile supplies the right ones; MDL, AAMVA_MDL, ISO_23220 and
EUDI_PID ship. AnyDocType applies one profile to everything, a
BTreeMap<String, MdocProfile> selects per doc type and refuses what it does not cover.
A credential whose PKI differs by more than its OIDs needs a CertificateProfile
implementation instead — a chain with intermediate CAs, certificates carrying no mdoc key
purpose, revocation published somewhere other than a CRL. EuAgeVerificationProfile ships
as one such: EU age verification wants an ETSI EN 319 411-1 NCP signer trusted through a
Trusted List, and revokes out of band, so a clean outcome from it does not mean "not
revoked".
Worked examples of both: an_eudi_pid_presents_end_to_end and a_custom_profile_validates_through_the_reader_api, and a_custom_profile_can_express_eu_age_verification.