Skip to content

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