This a Kotlin multiplatform (JVM and iOS) library to decode/verify and encode/sign Visible Digital Seals (VDS) as specified in
- BSI TR-03137 Part 1
- ICAO Doc 9303 Part 13: Visible Digital Seals
- ICAO TR "VDS for Non-Electronic Documents"
It also supports encoding and decoding Seals defined in the new draft of ICAO Datastructure for Barcode. Since release 0.9.0 ICAO IDB barcode encoder/decoders are fully functional. VDS and ICD barcodes can be parsed by a generic interface. An example is given in the following chapter
VDS can be created with the help of this library or, if you want to try it out quickly, via the web Sealgen tool. There is also the Sealva Android app which scans, verifies and displays all VDS profiles defined in the above specifications.
Here is a quick overview how to use the generic parser and verifier. In release 0.9.0 a generic interface was introduced to handle VDS and IDB barcode via common function calls. When you have the decoded raw string from your favorite datamatrix decoder, just put used the VDS Tools like this:
import de.tsenger.vdstools.Verifier
import de.tsenger.vdstools.vds.DigitalSeal
import de.tsenger.vdstools.vds.Feature
//Example for VDS / IDB barcode type
val seal: Seal = Seal.fromString(rawString)
val mrz: String? = seal.getMessage("MRZ")?.valueStr
// get all available Messages / Features in a List
val messageList: List<Message> = seal.messageList
for (message in messageList) {
println("${message.name}, ${message.coding}, ${message.valueStr}")
}
// SignatureInfo contains all signature relevant data
val signatureInfo: SignatureInfo = seal.signatureInfo
// Get the VDS signer certificate reference
val signerCertRef: String = signatureInfo.signerCertificateReference
// Since X509 certificate handling is strongly platform-dependent,
// the Verfifier is given the plain publicKey (r|s) and the curve name.
val publicKeyBytes: ByteArray = byteArrayOf()
val verifier: Verifier =
Verifier(seal.signedBytes, signatureInfo.plainSignatureBytes, publicKeyBytes, "brainpoolP224r1")
val result: Verifier.Result = verifier.verify()
Here is an example on how to use the DateEncoder and Signer classes to build a VDS barcode:
val keystore: KeyStore = ...
// In this JVM example we use a BouncyCastle keystore to get the certificate (for the header information)
// and the private key for signing the seals data
val cert: X509Certificate = keystore.getCertificate(keyAlias)
val ecKey: ECPrivateKey = keystore.getKey(certAlias, keyStorePassword.toCharArray())
// initialize the Signer
val signer: Signer = Signer(ecKey.encoded, curveName)
// 1. Build a VdsHeader
val header = VdsHeader.Builder("ARRIVAL_ATTESTATION")
.setIssuingCountry("D<<")
.setSignerIdentifier("DETS")
.setCertificateReference("32")
.setIssuingDate(LocalDate.parse("2024-09-27"))
.setSigDate(LocalDate.parse("2024-09-27"))
.build()
// 2. Build a VdsMessage
val mrz = "MED<<MANNSENS<<MANNY<<<<<<<<<<<<<<<<6525845096USA7008038M2201018<<<<<<06"
val azr = "ABC123456DEF"
val vdsMessage = VdsMessage.Builder(header.vdsType)
.addDocumentFeature("MRZ", mrz)
.addDocumentFeature("AZR", azr)
.build()
// 3. Build a signed DigitalSeal
val digitalSeal = DigitalSeal(header, vdsMessage, signer)
// The encoded bytes can now be used to build a datamatrix (or other) code - which is not part of this library
val encodedSealBytes = digitalSeal.encoded
Here is an example on how to use the DateEncoder and Signer classes to build a IDB barcode:
val keystore: KeyStore = ...
// In this JVM example we use a BouncyCastle keystore to get the certificate (for the header information)
// and the private key for signing the seals data
val cert: X509Certificate = keystore.getCertificate(keyAlias)
val ecKey: ECPrivateKey = keystore.getKey(certAlias, keyStorePassword.toCharArray())
// initialize the Signer
val signer: Signer = Signer(ecKey.encoded, curveName)
// 1. Build a IdbHeader
val header = IdbHeader(
"D<<",
IdbSignatureAlgorithm.SHA256_WITH_ECDSA,
DataEncoder.buildCertificateReference(cert.encoded),
"2025-02-11"
)
// 2. Build a MessageGroup
val messageGroup = IdbMessageGroup.Builder()
.addMessage(0x02, vdsMessage.encoded)
.addMessage(0x80, readBinaryFromResource("face_image_gen.jp2"))
.addMessage(0x84, "2026-04-23")
.addMessage(0x86, 0x02)
.build()
// 3. Build a signed Icao Barcode
val signature = buildSignature(header.encoded + messageGroup.encoded)
val payload = IdbPayload(header, messageGroup, null, signature)
val icb = IcaoBarcode(isSigned = true, isZipped = false, barcodePayload = payload)
// The encoded raw string can now be used to build a datamatrix (or other) code - which is not part of this library
val encodedRawString = icb.rawString
Also have a look at the testcases for more usage inspiration. You will also find an example on how to generate a datamatrix image with the Zxing library in the jvmTests.
Online JavaDoc can be found here: https://javadoc.io/doc/de.tsenger/vdstools
The vdstools library is available on the Maven Central Repository and GitHub Packages to be easy to integrate in your projects.
To include this library to your Gradle build add this dependency:
dependencies {
implementation 'de.tsenger:vdstools:0.9.0-SNAPSHOT'
}
To include this library to your Maven build add this dependency:
<dependency>
<groupId>de.tsenger</groupId>
<artifactId>vdstools</artifactId>
<version>0.9.0-SNAPSHOT</version>
</dependency>