Tutorial: Hello mdoc¶
Create your first ISO 18013-5 mobile document credential in 10 minutes.
Time: 10 minutes
Level: Beginner
Sample: samples/SdJwt.Net.Samples/01-Beginner/05-HelloMdoc.cs
What you will learn¶
- How to create an mdoc issuer
- How to build and sign a mobile document credential
- How to understand the CBOR-based document structure
Simple explanation¶
mdoc is the mobile document format used by mobile driving licenses (ISO 18013-5). This tutorial creates your first mdoc credential using CBOR encoding instead of JSON.
How mdoc differs from SD-JWT:
Aspect SD-JWT mdoc Encoding JSON / JWS CBOR / COSE Selective disclosure Per-claim digests Per-element IssuerAuth Primary spec RFC 9901 ISO 18013-5 Primary use case Online identity, VCs Mobile driving license
Packages used¶
| Package | Purpose |
|---|---|
SdJwt.Net.Mdoc |
ISO 18013-5 mdoc issuance and verification |
Where this fits¶
flowchart LR
A["Create Keys"] --> B["Issue mdoc"]
B --> C["Hold / Store"]
C --> D["Present"]
D --> E["Verify"]
style A fill:#2a6478,color:#fff
style B fill:#2a6478,color:#fff
Prerequisites¶
- .NET 9.0 SDK installed
- Basic understanding of digital credentials
- Completed Hello SD-JWT
Step 1: Install the package¶
Add the mdoc package to your project:
dotnet add package SdJwt.Net.Mdoc
Step 2: Create cryptographic keys¶
Every mdoc system needs two keys: an issuer key (for signing) and a device key (for holder binding):
using System.Security.Cryptography;
using SdJwt.Net.Mdoc.Cose;
// Create issuer signing key (P-256 curve - ECDSA)
using var issuerEcdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var issuerKey = CoseKey.FromECDsa(issuerEcdsa);
// Create device key for holder binding
using var deviceEcdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var deviceKey = CoseKey.FromECDsa(deviceEcdsa);
Step 3: Create the issuer builder¶
Use MdocIssuerBuilder for fluent credential construction:
using SdJwt.Net.Mdoc.Issuer;
var builder = new MdocIssuerBuilder()
.WithDocType("org.iso.18013.5.1.mDL")
.WithIssuerKey(issuerKey)
.WithDeviceKey(deviceKey);
Step 4: Add data elements¶
Add claims using the mDL namespace helpers:
using SdJwt.Net.Mdoc.Namespaces;
builder
.AddMdlElement(MdlDataElement.FamilyName, "Doe")
.AddMdlElement(MdlDataElement.GivenName, "John")
.AddMdlElement(MdlDataElement.BirthDate, "1990-05-15")
.AddMdlElement(MdlDataElement.IssueDate, "2024-01-01")
.AddMdlElement(MdlDataElement.ExpiryDate, "2029-01-01")
.AddMdlElement(MdlDataElement.IssuingCountry, "US")
.AddMdlElement(MdlDataElement.IssuingAuthority, "State DMV")
.AddMdlElement(MdlDataElement.DocumentNumber, "DL123456789");
Step 5: Set validity and build¶
using SdJwt.Net.Mdoc.Cose;
var cryptoProvider = new DefaultCoseCryptoProvider();
builder.WithValidity(
validFrom: DateTimeOffset.UtcNow,
validUntil: DateTimeOffset.UtcNow.AddYears(5));
var mdoc = await builder.BuildAsync(cryptoProvider);
Console.WriteLine($"Created mdoc with DocType: {mdoc.DocType}");
Console.WriteLine($"Contains {mdoc.IssuerSigned.NameSpaces.Count} namespace(s)");
Understanding the output¶
The resulting Document contains:
- DocType: The credential type identifier (e.g.,
org.iso.18013.5.1.mDL) - IssuerSigned: Namespaced data elements with COSE signature
- DeviceSigned: Optional device-generated data (for presentations)
Structure:
Document
DocType: "org.iso.18013.5.1.mDL"
IssuerSigned
NameSpaces: {"org.iso.18013.5.1": [...items]}
IssuerAuth: COSE_Sign1 (contains MSO)
Complete example¶
using System.Security.Cryptography;
using SdJwt.Net.Mdoc.Cose;
using SdJwt.Net.Mdoc.Issuer;
using SdJwt.Net.Mdoc.Namespaces;
// Setup keys
using var issuerEcdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using var deviceEcdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var issuerKey = CoseKey.FromECDsa(issuerEcdsa);
var deviceKey = CoseKey.FromECDsa(deviceEcdsa);
var cryptoProvider = new DefaultCoseCryptoProvider();
// Build and issue mdoc
var mdoc = await new MdocIssuerBuilder()
.WithDocType("org.iso.18013.5.1.mDL")
.WithIssuerKey(issuerKey)
.WithDeviceKey(deviceKey)
.AddMdlElement(MdlDataElement.FamilyName, "Doe")
.AddMdlElement(MdlDataElement.GivenName, "John")
.AddMdlElement(MdlDataElement.BirthDate, "1990-05-15")
.AddMdlElement(MdlDataElement.DocumentNumber, "DL123456")
.WithValidity(
DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow.AddYears(5))
.BuildAsync(cryptoProvider);
Console.WriteLine($"Created mdoc: {mdoc.DocType}");
Run the sample¶
cd samples/SdJwt.Net.Samples
dotnet run -- 1.5
Expected output¶
mdoc created for docType: org.iso.18013.5.1.mDL
Namespace: org.iso.18013.5.1
Elements: family_name, given_name, birth_date
Demo vs production¶
This tutorial uses in-memory COSE keys. Production mdoc issuance requires an X.509 certificate chain (IACA) and hardware-backed key storage.
Common mistakes¶
- Using JSON claim names instead of mdoc element identifiers (mdoc uses nameSpaces and element identifiers, not flat JSON claims)
- Forgetting that mdoc uses CBOR/COSE, not JSON/JWS
Next steps¶
- mdoc Issuance - Complete credential issuance flows
- mdoc - Understanding ISO 18013-5
Key concepts¶
| Term | Description |
|---|---|
| mdoc | Mobile document format per ISO 18013-5 |
| CBOR | Concise Binary Object Representation |
| COSE | CBOR Object Signing and Encryption |
| MSO | Mobile Security Object (signed digests) |
| mDL | Mobile Driving License |
| Device Key | Holder's key for proof of possession |