Device Request and Response
The ISO 18013-5 protocol uses DeviceRequest and DeviceResponse structures to exchange credential data. This guide explains their structure and how to work with them in the IDK.
DeviceRequest Structure
A DeviceRequest specifies what data the verifier is requesting:
DeviceRequest = {
version: "1.0",
docRequests: [DocRequest, ...]
}
DocRequest = {
docType: "org.iso.18013.5.1.mDL",
itemsRequest: {
nameSpaces: {
"org.iso.18013.5.1": {
"family_name": true,
"given_name": true,
...
}
}
},
readerAuth: ReaderAuthentication (optional)
}
Parsing Device Requests
- Android/Kotlin
- iOS/Swift
val deviceRequest: DeviceRequest = transferManager.receiveDeviceRequest()
// Iterate over document requests
for (docRequest in deviceRequest.docRequests) {
val docType = docRequest.docType // e.g., "org.iso.18013.5.1.mDL"
val itemsRequest = docRequest.itemsRequest
// Iterate over namespaces
for ((namespace, elements) in itemsRequest.nameSpaces) {
// Iterate over requested elements
for ((elementId, intentToRetain) in elements) {
println("Requested: $docType / $namespace / $elementId")
println(" Intent to retain: $intentToRetain")
}
}
}
let deviceRequest: DeviceRequest = try await transferManager.receiveDeviceRequest()
// Iterate over document requests
for docRequest in deviceRequest.docRequests {
let docType = docRequest.docType // e.g., "org.iso.18013.5.1.mDL"
let itemsRequest = docRequest.itemsRequest
// Iterate over namespaces
for (namespace, elements) in itemsRequest.nameSpaces {
// Iterate over requested elements
for (elementId, intentToRetain) in elements {
print("Requested: \(docType) / \(namespace) / \(elementId)")
print(" Intent to retain: \(intentToRetain)")
}
}
}
DeviceResponse Structure
A DeviceResponse contains the requested data with cryptographic proofs:
DeviceResponse = {
version: "1.0",
documents: [Document, ...],
status: 0
}
Document = {
docType: "org.iso.18013.5.1.mDL",
issuerSigned: IssuerSigned,
deviceSigned: DeviceSigned
}
IssuerSigned Data
Data signed by the credential issuer, containing the Mobile Security Object (MSO) and namespace data:
- Android/Kotlin
- iOS/Swift
val document = deviceResponse.documents?.first()
val issuerSigned: IssuerSigned = document?.issuerSigned!!
// Mobile Security Object
val mso: MobileSecurityObject = issuerSigned.MSO
// Device key info from MSO
val deviceKeyInfo: DeviceKeyInfoCbor = issuerSigned.deviceKeyInfo
// Get available namespaces
val namespaces: Set<NameSpace> = issuerSigned.getNameSpaces()
// Get signed items for a namespace
val items: List<IssuerSignedItem<Any>>? = issuerSigned.getIssuerSignedItems("org.iso.18013.5.1")
items?.forEach { item ->
println("Element: ${item.elementIdentifier}")
println("Value: ${item.elementValue}")
}
let document = deviceResponse.documents?.first
let issuerSigned: IssuerSigned = document?.issuerSigned!
// Mobile Security Object
let mso: MobileSecurityObject = issuerSigned.MSO
// Device key info from MSO
let deviceKeyInfo: DeviceKeyInfoCbor = issuerSigned.deviceKeyInfo
// Get available namespaces
let namespaces: Set<NameSpace> = issuerSigned.getNameSpaces()
// Get signed items for a namespace
let items: [IssuerSignedItem]? = issuerSigned.getIssuerSignedItems(ns: "org.iso.18013.5.1")
items?.forEach { item in
print("Element: \(item.elementIdentifier)")
print("Value: \(item.elementValue)")
}
DeviceSigned Data
Data signed by the holder's device during presentation:
- Android/Kotlin
- iOS/Swift
val deviceSigned: DeviceSigned = document?.deviceSigned!!
// Device namespaces (additional device-provided data)
val deviceNameSpaces: DeviceNameSpaces = deviceSigned.nameSpaces
// Device authentication (COSE_Sign1 or COSE_Mac0)
val deviceAuth: DeviceAuth = deviceSigned.deviceAuth
let deviceSigned: DeviceSigned = document?.deviceSigned!
// Device namespaces (additional device-provided data)
let deviceNameSpaces: DeviceNameSpaces = deviceSigned.nameSpaces
// Device authentication (COSE_Sign1 or COSE_Mac0)
let deviceAuth: DeviceAuth = deviceSigned.deviceAuth
Selective Disclosure
The limitDisclosures method allows filtering which elements to include in a response:
- Android/Kotlin
- iOS/Swift
val issuerSigned: IssuerSigned = document.issuerSigned
// Limit disclosures based on the request
val limitedIssuerSigned: IssuerSigned = issuerSigned.limitDisclosures(docRequest)
let issuerSigned: IssuerSigned = document.issuerSigned
// Limit disclosures based on the request
let limitedIssuerSigned: IssuerSigned = issuerSigned.limitDisclosures(docRequest: docRequest)
Common Data Elements
Standard mDL data elements in the org.iso.18013.5.1 namespace:
| Element ID | Type | Description |
|---|---|---|
family_name | string | Family name |
given_name | string | Given name(s) |
birth_date | full-date | Date of birth |
issue_date | full-date | Document issue date |
expiry_date | full-date | Document expiry date |
issuing_country | string | ISO 3166-1 alpha-2 code |
issuing_authority | string | Issuing authority name |
document_number | string | License number |
portrait | bytes | Photo of holder (JPEG) |
driving_privileges | array | License categories |
un_distinguishing_sign | string | UN country sign |
sex | integer | ISO/IEC 5218 code |
height | integer | Height in cm |
weight | integer | Weight in kg |
eye_colour | string | Eye color |
hair_colour | string | Hair color |
resident_address | string | Address |
age_over_18 | boolean | Age attestation |
age_over_21 | boolean | Age attestation |
nationality | string | Nationality |
Response Status Codes
DeviceResponse status codes:
| Code | Meaning | Description |
|---|---|---|
| 0 | OK | Request processed successfully |
| 10 | General error | Unspecified error |
| 11 | CBOR decoding error | Invalid CBOR in request |
| 20 | User cancelled | User declined to share |
- Android/Kotlin
- iOS/Swift
val response = deviceResponse
when (response.status?.value?.toInt()) {
0 -> {
// Success - process documents
val documents = response.documents
processDocuments(documents)
}
10 -> {
// General error
handleError("General error from holder")
}
20 -> {
// User cancelled
handleCancellation()
}
else -> {
handleError("Unknown status: ${response.status}")
}
}
let response = deviceResponse
switch response.status?.value {
case 0:
// Success - process documents
let documents = response.documents
processDocuments(documents: documents)
case 10:
// General error
handleError(message: "General error from holder")
case 20:
// User cancelled
handleCancellation()
default:
handleError(message: "Unknown status: \(response.status)")
}
Building Requests (Verifier Side)
When building a DeviceRequest as a verifier:
- Android/Kotlin
- iOS/Swift
// Build items request for mDL
val itemsRequest = ItemsRequest(
nameSpaces = mapOf(
"org.iso.18013.5.1" to mapOf(
"family_name" to false,
"given_name" to false,
"birth_date" to false,
"portrait" to false,
"age_over_18" to false
)
)
)
// Build doc request
val docRequest = DocRequest(
docType = "org.iso.18013.5.1.mDL",
itemsRequest = itemsRequest,
readerAuth = null // Optional reader authentication
)
// Build device request
val deviceRequest = DeviceRequest(
version = "1.0",
docRequests = listOf(docRequest)
)
// Build items request for mDL
let itemsRequest = ItemsRequest(
nameSpaces: [
"org.iso.18013.5.1": [
"family_name": false,
"given_name": false,
"birth_date": false,
"portrait": false,
"age_over_18": false
]
]
)
// Build doc request
let docRequest = DocRequest(
docType: "org.iso.18013.5.1.mDL",
itemsRequest: itemsRequest,
readerAuth: nil // Optional reader authentication
)
// Build device request
let deviceRequest = DeviceRequest(
version: "1.0",
docRequests: [docRequest]
)
Intent to Retain
The intentToRetain boolean in the request indicates whether the verifier intends to store the data element:
false- Verifier will not retain the data (e.g., for one-time verification)true- Verifier may store the data (e.g., for records)
Holders may choose to decline sharing elements where intentToRetain is true.