Skip to content

Enrollment

Enrollment is the process of binding a VDS credential (digital identity derived from a passport) to a mobile app’s ECDSA key pair. This creates the trust anchor used for all subsequent authentications.

During enrollment:

  1. The mobile app scans a passport (MRZ + NFC ICAO)
  2. The id3 Face SDK performs liveness detection and 1:1 face matching
  3. The BioSeal Issuance Service generates a VDS (Visible Digital Seal)
  4. The mobile app generates an ECDSA P-256 key pair
  5. The app sends the VDS data + public key to the auth server
  6. The server verifies the VDS and stores the binding
PassportMobile AppAuth ServerMRZ + NFC ReadFace SDKLiveness + MatchBioSeal IssuanceVDS generatedPOST /enroll{ vdsData, publicKeyPem, deviceId, fcmToken }Verify{ enrolled: true, tokenId }
POST /enroll

Registers a mobile device with a VDS credential and ECDSA public key.

Rate limit: 5 requests per hour per IP.

FieldTypeRequiredDescription
vdsDatastringYesVDS data (Base64, Base45, or hex encoded MessagePack)
publicKeyPemstringYesECDSA P-256 public key in PEM format
deviceIdstringYesUnique device identifier
fcmTokenstringNoFirebase Cloud Messaging token for push notifications
{
"vdsData": "3q8DVkRT...",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMFkwEwYH...\n-----END PUBLIC KEY-----",
"deviceId": "device-uuid-12345",
"fcmToken": "fcm:APA91b..."
}
{
"enrolled": true,
"tokenId": "vds-uuid-from-seal",
"expiresAt": "2026-01-15T00:00:00Z"
}
StatusReason
400Invalid VDS data or public key format
409Token ID already revoked
429Rate limit exceeded

When the server receives VDS data, it performs these verification steps:

  1. Decode — Detect encoding (Base64, Base45, hex) and convert to binary
  2. Parse header — Extract marker (0xDE), version, IAC, cert reference, manifest ID, timestamp
  3. Validate timestamp — Must not be more than 5 minutes in the future
  4. Decode payload — MessagePack decode to extract identity fields
  5. Check expiration — VDS must not be expired
  6. Verify signature — ECDSA P-256 signature over SHA256(header || SHA256(payload))
  7. Walk PKI chain — Resolve trust from VDSIC Governance List down to signing certificate
FieldKeyDescription
Given namegnFirst name from passport
Family namefnLast name from passport
Date of birthdobISO date format
Nationalitynat3-letter country code
Document numberdocPassport/ID number
Token IDtidUnique VDS identifier (UUID)
ExpiryexpUnix timestamp
PhotophotoCBEFF biometric photo (JPEG, optional)

Each enrollment is assigned a trust level based on the VDS certificate chain:

LevelNameMeaning
1SelfVDS issued by non-verified CA
2AdminVDS verified by an operator
3eIDASFull PKI chain verified (VDSIC → CA)

Trust levels determine which services the user can access. Relying parties can require a minimum trust level.

Enrollment is idempotent — calling POST /enroll with the same tokenId updates the existing enrollment (new public key, device ID, FCM token) rather than creating a duplicate.

This allows:

  • Device replacement — User gets a new phone, re-enrolls with the same VDS
  • FCM token refresh — Update the push notification token without re-enrolling

Enrollments can be revoked via POST /revoke:

{
"tokenId": "vds-uuid",
"reason": "lost_device"
}

Revocation reasons:

  • lost_device — Mobile device lost or stolen
  • compromised — Key compromise suspected
  • expired_document — Underlying passport/ID expired
  • user_request — User-initiated revocation

Once revoked, the tokenId cannot be used for authentication. A new enrollment with the same VDS is possible (new key pair generated).

The enrollment data is stored in the following structure:

ColumnTypeDescription
idUUIDPrimary key
token_idVARCHAR(64)Unique VDS token identifier
public_key_pemTEXTECDSA P-256 public key (PEM)
device_idVARCHAR(255)Unique device identifier
fcm_tokenTEXTPush notification token (optional)
trust_levelSMALLINTTrust level (1, 2, or 3)
is_revokedBOOLEANWhether enrollment is revoked
revoked_atTIMESTAMPTZRevocation timestamp
revoke_reasonTEXTReason for revocation
created_atTIMESTAMPTZEnrollment creation time
updated_atTIMESTAMPTZLast update time