User Guide

User guides demonstrating building applications with Spruce's Rust ISO mDL library.

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

Visit the Rust library API documentation: https://docs.rs/isomdl

Initialize a Session

Devices store verifiable documents, creating sessions for Readers to request verifiable data. To establish a new session, the device will use the device module to access the SessionManagerInit::initialize method, providing the list of available documents to present to the reader, along with optional DeviceRetrievalMethods available.

Available device retrievable methods include WiFi, BLE and NFC options. More than one device retrieval method may be accessible for the session.

The device may optionally specify a remote server retrieval method for the data, using the ServerRetrievalMethods type.

Example

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

    let session = device::SessionManagerInit::initialize(docs, Some(drms), 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.
fn establish_reader_session(qr: 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))
}

Session Request

Once the reader has established a new session with the request elements, using the establish_session method, a CBOR serialized bytes payload session_request is returned, which is sent to the device for processing.

Once the device has received the session request, it will deserialize the raw CBOR bytes and process the established session using the SessionManagerEngage::process_session_establishment method.

Once the session request is processed using the session_establishment method, a SessionManager instance is returned.

Session Manager

The SessionManager instance provides a prepare_response method to process the session's item requests given a set of permitted items.

The prepare_response method will update 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 pending payloads to be signed. Payloads are signed using the SessionManager private key.

Once a payload is signed, the SessionManager provides a method called submit_next_signature that is used to update the internal SessionManager state's signed messages.

Once the signature has been submitted, the device prepares an encrypted response using the shared key established during the session setup. The internal state of the SessionManager is set to ReadyToRespond 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(())
}

Last updated