User Guide

This user guide demonstrates building an application that can verify and/or share mDLs with the SpruceID Rust ISOmDL library.

See more examples and simulated device and reader test cases at the open source GitHub repository: https://github.com/spruceid/isomdl

Initialize a Session

Devices store verifiable documents and create sessions that allow Readers to request this verifiable data. To start a new session, the device uses the device module to call the SessionManagerInit::initialize method. This method provides the Reader with a list of available documents that can be presented, as well as any optional DeviceRetrievalMethods that can be used to transfer the data.

Device retrieval methods include WiFi, BLE (Bluetooth Low Energy), and NFC. During the session, more than one method can be made available.

Additionally, the device can optionally specify a remote server retrieval method for data transfer, using the ServerRetrievalMethods type.

Example

/// Creates a QR code containing `DeviceEngagement` data, 
/// which includes its public key.
fn initialize_session(docs: Documents, id: Uuid) -> Result<SessionData> {
    let device_retrieval_methods = DeviceRetrievalMethods::new(
        DeviceRetrievalMethod::BLE(BleOptions {
        peripheral_server_mode: None,
        central_client_mode: Some(CentralClientMode { id }),
    }));

    let session = device::SessionManagerInit::initialize(
        docs, 
        Some(device_retrieval_methods), None
        )
        .context("failed to initialize device")?;

    let (engaged_state, qr_code_uri) = session
        .qr_engagement()
        .context("could not generate qr engagement")?;
    Ok(SessionData {
        state: Arc::new(SessionManagerEngaged(engaged_state)),
        qr_code_uri,
    })
}

Session Data

Once the session data is established, the device can share the QR code URI created by the qr_engagement method with the reader. The Reader will use the QR code URI, along with a request element to establish a reader session.

Example

/// Establishes the reader session from the given QR code and create request for needed elements.
const AGE_OVER_21_ELEMENT: &str = "age_over_21";

fn establish_reader_session(qr_code_uri: String) -> Result<(reader::SessionManager, Vec<u8>)> {
    let requested_elements = Namespaces::new(
        NAMESPACE.into(),
        DataElements::new(AGE_OVER_21_ELEMENT.to_string(), false),
    );
    let (reader_sm, session_request, _ble_ident) =
        reader::SessionManager::establish_session(qr, requested_elements)
            .context("failed to establish reader session")?;
    Ok((reader_sm, session_request))
}  Ok((reader_sm, session_request))

The isomdl library currently only supports data elements belonging to the core namespace defined in the ISO/IEC 18013-5 specification, along with the namespace defined by AAMVA for mDLs issued by US states. If your needs exceed what is currently offered, we invite you to file an issue on our github repository.

Session Request

Once the Reader has established a session with the necessary request elements using the establish_session method, a CBOR-serialized byte payload called session_request is returned, which is then sent to the device for processing.

When the device receives the session request, it deserializes the raw CBOR bytes and processes the session establishment by calling the SessionManagerEngage::process_session_establishment method. After processing the session request, a SessionManager instance is returned. Session Manager

Session Manager

The SessionManager instance offers a prepare_response method, which processes the requested session items based on a set of permitted items. This method updates the internal state of the device session to "Signing."

When the SessionManager is in the Signing state, the get_next_signature_payload method is used to return the payloads that need to be signed. These payloads are signed using the SessionManager's private key.

Once a payload is signed, the submit_next_signature method is called to update the internal state of the SessionManager with the signed messages. After the signature is submitted, the device prepares an encrypted response using the shared key that was established during session setup. The internal state of the SessionManager is then set to "ReadyToRespond," along with the CBOR-serialized encrypted response.

Example

// Prepare response with required elements.
fn create_response(session_manager: Arc<SessionManager>) -> Result<Vec<u8>> {
    let permitted_items = [(
        DOC_TYPE.to_string(),
        [(NAMESPACE.to_string(), vec![AGE_OVER_21_ELEMENT.to_string()])]
            .into_iter()
            .collect(),
    )]
    .into_iter()
    .collect();
    session_manager
        .inner
        .lock()
        .unwrap()
        .prepare_response(&session_manager.items_requests, permitted_items);
    sign_pending_and_retrieve_response(session_manager.clone(), Some(1))?
        .ok_or_else(|| anyhow::anyhow!("cannot prepare response"))
}

Session Response

Once the response has been prepared, the SessionManager provides a retrieve_response method that can be used to return the documents that have been signed by the device. The response is sent to the reader and processed by the reader's session manager, using the handle_response method.

Example

/// Reader Processing mDL data.
fn reader_handle_device_response(
    reader_sm: &mut reader::SessionManager,
    response: Vec<u8>,
) -> Result<()> {
    let res = reader_sm.handle_response(&response)?;
    println!("{:?}", res);
    Ok(())
}

Based on the result of the handle_response method, the verifier of the mDL can decide to provide or deny access to the mDL holder or to continue with any other business logic.

Last updated