diff --git a/.gitignore b/.gitignore index 513c972..8e317d7 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ gradle-app.setting gradle.properties .vscode + +# walt.id +secret_* \ No newline at end of file diff --git a/README.md b/README.md index 0036e8d..1b30c41 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ This library implements the mdoc specification: [ISO/IEC 18013-5:2021](https://w **Maven / Gradle repository**: -`https://maven.walt.id/repository/waltid-ssi-kit/` +`https://maven.walt.id/repository/waltid/` **Maven** @@ -44,9 +44,9 @@ This library implements the mdoc specification: [ISO/IEC 18013-5:2021](https://w [...] - waltid-ssikit - waltid-ssikit - https://maven.walt.id/repository/waltid-ssi-kit/ + waltid + walt.id + https://maven.walt.id/repository/waltid/ [...] @@ -63,7 +63,7 @@ _Kotlin DSL_ ```kotlin [...] repositories { - maven("https://maven.walt.id/repository/waltid-ssi-kit/") + maven("https://maven.walt.id/repository/waltid/") } [...] val mdocVersion = "1.xxx.0" @@ -132,7 +132,7 @@ SIGNED MDOC: a267646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6c6973737565725369676e6564a26a6e616d65537061636573a1716f72672e69736f2e31383031332e352e3183d8185852a4686469676573744944006672616e646f6d501d5a0b315468e8e741c7d0fbf2267ea671656c656d656e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456616c756563446f65d8185852a4686469676573744944016672616e646f6d505a212f6b1afa24c80fdf756859b6e0e571656c656d656e744964656e7469666965726a676976656e5f6e616d656c656c656d656e7456616c7565644a6f686ed818585ba4686469676573744944026672616e646f6d50595961fbb375b6330e60016e33e3caa471656c656d656e744964656e7469666965726a62697274685f646174656c656c656d656e7456616c7565d903ec6a313939302d30312d31356a697373756572417574688443a10126a1182159014b308201473081eea00302010202085851077f1cb3d768300a06082a8648ce3d04030230173115301306035504030c0c4d444f432054657374204341301e170d3233303830323136323231395a170d3233303830333136323231395a301b3119301706035504030c104d444f432054657374204973737565723059301306072a8648ce3d020106082a8648ce3d030107034200045f1c8ff18cb0b57445f16eec0584fcf69a6829d955a3284fa42e4d091f6da49196f5b9c917a39ecbf2bf7cdd06597169433c1d9cde0a9ee9772bd29b12fcb775a320301e300c0603551d130101ff04023000300e0603551d0f0101ff040403020780300a06082a8648ce3d0403020348003045022075e093d7e7128060f42ca9a675b97c6312c46cbecd23afdbe8619e964eab37e2022100d9b522c7b80f93dd978a955d0ffdb5f64dc40fa9aa1aa6e10902b306821d13ed5901c3d8185901bea66776657273696f6e63312e306f646967657374416c676f726974686d675348412d3235366c76616c756544696765737473a1716f72672e69736f2e31383031332e352e31a3005820534172b2a1e4082a7644b42299271711891b29adfd50b10a18524e8827d308ae0158204892baa76842258533af9eac579397d024cbff8536afda2da2b9c62a4b30704102582002fc10a9f125740b67e29264cd03ba4994a56f3377c62344d092c614cc18bdb06d6465766963654b6579496e666fa1696465766963654b6579a401022001215820f2862d595d95758368138cb90e3c0df01a432ce1f569ea0d26e80351cf6d0425225820fd20afda5943e95dbd6c679fe1ffb425ec92a65bfcfa2c2c1882669d3bed737267646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6c76616c6964697479496e666fa3667369676e6564c0781e323032332d30382d30325431363a32323a31392e3235323531363736395a6976616c696446726f6dc0781e323032332d30382d30325431363a32323a31392e3235323531393730355a6a76616c6964556e74696cc0781e323032342d30382d30315431363a32323a31392e3235323532303435375a5840a59ce0142b6943b26da7a79a71167ab459702d4231a46990d573445034abee6fe275582686a71ab37fed5a6a0819c740bb79f6e24e7786022db07c7469cb1d09 ``` -#### Create, parse and verify an mDL request +#### Create, parse and verify a mdoc (mDL) request ```kotlin val cryptoProvider = SimpleCOSECryptoProvider(listOf( @@ -254,7 +254,45 @@ Namespace: org.iso.18013.5.1 - document_number: 123456789 ``` +### Sign a mobile eID document (ISO-IEC_23220-2) +```kotlin + val mdoc = MDocBuilder("org.iso.23220.mID.1") + .addItemToSign("org.iso.23220.1", "family_name", "Doe".toDE()) + .addItemToSign("org.iso.23220.1", "given_name", "John".toDE()) + .addItemToSign("org.iso.23220.1", "birth_date", FullDateElement(LocalDate(1990, 1, 15))) + .addItemToSign("org.iso.23220.1", "sex", "1".toDE()) // ISO/IEC 5218 + .addItemToSign("org.iso.23220.1", "height", "175".toDE()) + .addItemToSign("org.iso.23220.1", "weight", "70".toDE()) + .addItemToSign("org.iso.23220.1", "birthplace", "Vienna".toDE()) + .addItemToSign("org.iso.23220.1", "nationality", "AT".toDE()) + .addItemToSign("org.iso.23220.1", "telephone_number", "0987654".toDE()) + .addItemToSign("org.iso.23220.1", "email_address", "john@email.com".toDE()) + .sign(ValidityInfo(Clock.System.now(), Clock.System.now(), Clock.System.now().plus(365*24, DateTimeUnit.HOUR)), + deviceKeyInfo, cryptoProvider, ISSUER_KEY_ID + ) + +``` +### Verify certain elements of the above signed mobile eID document (ISO-IEC_23220-2) +```kotlin + val mdocRequest = MDocRequestBuilder(mdoc.docType.value) + .addDataElementRequest("org.iso.23220.1", "family_name", true) + .addDataElementRequest("org.iso.23220.1", "given_name", true) + .addDataElementRequest("org.iso.23220.1", "birth_date", true) + .build() + + val presentedMdoc = mdoc.presentWithDeviceSignature(mdocRequest, deviceAuthentication, cryptoProvider, DEVICE_KEY_ID) + + presentedMdoc.verify( + MDocVerificationParams( + VerificationType.forPresentation, + ISSUER_KEY_ID, DEVICE_KEY_ID, + deviceAuthentication = deviceAuthentication, + mDocRequest = mdocRequest + ), + cryptoProvider + ) +``` ## License diff --git a/build.gradle.kts b/build.gradle.kts index 35a0aa9..77d310c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -100,7 +100,7 @@ kotlin { val hasMavenAuth = secretMavenUsername.isNotEmpty() && secretMavenPassword.isNotEmpty() if (hasMavenAuth) { maven { - url = uri("https://maven.walt.id/repository/waltid-ssi-kit/") + url = uri("https://maven.walt.id/repository/waltid/") credentials { username = secretMavenUsername password = secretMavenPassword diff --git a/src/jvmTest/kotlin/id/walt/mdoc/JVMMdocTest.kt b/src/jvmTest/kotlin/id/walt/mdoc/JVMMdocTest.kt index 78d1287..14a449d 100644 --- a/src/jvmTest/kotlin/id/walt/mdoc/JVMMdocTest.kt +++ b/src/jvmTest/kotlin/id/walt/mdoc/JVMMdocTest.kt @@ -8,7 +8,6 @@ import id.walt.mdoc.dataelement.* import id.walt.mdoc.dataretrieval.DeviceRequest import id.walt.mdoc.dataretrieval.DeviceResponse import id.walt.mdoc.doc.* -import id.walt.mdoc.docrequest.MDocRequest import id.walt.mdoc.docrequest.MDocRequestBuilder import id.walt.mdoc.docrequest.MDocRequestVerificationParams import id.walt.mdoc.mdocauth.DeviceAuthentication @@ -97,7 +96,11 @@ class JVMMdocTest: AnnotationSpec() { @OptIn(ExperimentalSerializationApi::class) @Test - fun testSigning() { + fun testSigningMdl() { + // ISO-IEC_18013-5:2021 + // Personal identification — ISO-compliant driving licence + // Part 5: Mobile driving licence (mDL) application + // instantiate simple cose crypto provider for issuer keys and certificates val cryptoProvider = SimpleCOSECryptoProvider( listOf( @@ -108,7 +111,7 @@ class JVMMdocTest: AnnotationSpec() { // create device key info structure of device public key, for holder binding val deviceKeyInfo = DeviceKeyInfo(DataElement.fromCBOR(OneKey(deviceKeyPair.public, null).AsCBOR().EncodeToBytes())) - // build mdoc and sign using issuer key with holder binding to device key + // build mdoc of type mDL and sign using issuer key with holder binding to device key val mdoc = MDocBuilder("org.iso.18013.5.1.mDL") .addItemToSign("org.iso.18013.5.1", "family_name", "Doe".toDE()) .addItemToSign("org.iso.18013.5.1", "given_name", "John".toDE()) @@ -116,7 +119,7 @@ class JVMMdocTest: AnnotationSpec() { .sign(ValidityInfo(Clock.System.now(), Clock.System.now(), Clock.System.now().plus(365*24, DateTimeUnit.HOUR)), deviceKeyInfo, cryptoProvider, ISSUER_KEY_ID ) - println("SIGNED MDOC:") + println("SIGNED MDOC (mDL):") println(Cbor.encodeToHexString(mdoc)) mdoc.MSO shouldNotBe null @@ -300,4 +303,103 @@ class JVMMdocTest: AnnotationSpec() { println("Verified: $mdocVerified") mdocVerified shouldBe true } + + @Test + fun testSigningMobileEIDDocument() { + // ISO-IEC_23220-2 + // Cards and security devices for personal identification + // Building blocks for identity management via mobile devices + // Part 2: Data objects and encoding rules for generic eID-System + + // instantiate simple cose crypto provider for issuer keys and certificates + val cryptoProvider = SimpleCOSECryptoProvider( + listOf( + COSECryptoProviderKeyInfo(ISSUER_KEY_ID, AlgorithmID.ECDSA_256, issuerKeyPair.public, issuerKeyPair.private, listOf(issuerCertificate), listOf(caCertificate)), + COSECryptoProviderKeyInfo(DEVICE_KEY_ID, AlgorithmID.ECDSA_256, deviceKeyPair.public, deviceKeyPair.private) + ) + ) + // create device key info structure of device public key, for holder binding + val deviceKeyInfo = DeviceKeyInfo(DataElement.fromCBOR(OneKey(deviceKeyPair.public, null).AsCBOR().EncodeToBytes())) + + // build mdoc of type mID and sign using issuer key with holder binding to device key + val mdoc = MDocBuilder("org.iso.23220.mID.1") + .addItemToSign("org.iso.23220.1", "family_name", "Doe".toDE()) + .addItemToSign("org.iso.23220.1", "given_name", "John".toDE()) + .addItemToSign("org.iso.23220.1", "birth_date", FullDateElement(LocalDate(1990, 1, 15))) + .addItemToSign("org.iso.23220.1", "sex", "1".toDE()) // ISO/IEC 5218 + .addItemToSign("org.iso.23220.1", "height", "175".toDE()) + .addItemToSign("org.iso.23220.1", "weight", "70".toDE()) + .addItemToSign("org.iso.23220.1", "birthplace", "Vienna".toDE()) + .addItemToSign("org.iso.23220.1", "nationality", "AT".toDE()) + .addItemToSign("org.iso.23220.1", "telephone_number", "0987654".toDE()) + .addItemToSign("org.iso.23220.1", "email_address", "john@email.com".toDE()) + .sign(ValidityInfo(Clock.System.now(), Clock.System.now(), Clock.System.now().plus(365*24, DateTimeUnit.HOUR)), + deviceKeyInfo, cryptoProvider, ISSUER_KEY_ID + ) + + mdoc.nameSpaces.forEach { ns -> + println("mobile eID ($ns)") + mdoc.getIssuerSignedItems(ns).forEach { issuerSignedItem -> + println("- ${issuerSignedItem.elementIdentifier.value}: ${issuerSignedItem.elementValue.value.toString()}") + } + } + println("SIGNED MDOC (mobile eID):") + println(Cbor.encodeToHexString(mdoc)) + + mdoc.MSO shouldNotBe null + mdoc.MSO!!.digestAlgorithm.value shouldBe "SHA-256" + val signedItems = mdoc.getIssuerSignedItems("org.iso.23220.1") + signedItems shouldHaveSize 10 + signedItems.first().digestID.value shouldBe 0 + mdoc.MSO!!.valueDigests.value shouldContainKey MapKey("org.iso.23220.1") + OneKey(CBORObject.DecodeFromBytes(mdoc.MSO!!.deviceKeyInfo.deviceKey.toCBOR())).AsPublicKey().encoded shouldBe deviceKeyPair.public.encoded + mdoc.verify(MDocVerificationParams(VerificationType.forIssuance, ISSUER_KEY_ID), cryptoProvider) shouldBe true + + + // test presentation with device signature + val ephemeralReaderKey = COSE.OneKey.generateKey(AlgorithmID.ECDSA_256) + val deviceAuthentication = DeviceAuthentication(sessionTranscript = ListElement(listOf( + NullElement(), + EncodedCBORElement(ephemeralReaderKey.AsCBOR().EncodeToBytes()), + NullElement() + )), mdoc.docType.value, EncodedCBORElement(MapElement(mapOf()))) + + + // we present the mandatory attributes of the eIDAS minimal data set for natural persons (CIR 2015/1501), although the unique ID is missing in ISO-IEC_23220-2 + // mandatory: + // - current family name(s) + // - current first name(s) + // - date of birth; + // - a unique identifier constructed by the sending Member State in accordance with the technical specifications for the purposes of cross-border identification and which is as persistent as possible in time. + // optional: + // - first name(s) and family name(s) at birth + // - place of birth; + // - current address; + // - gender + + val mdocRequest = MDocRequestBuilder(mdoc.docType.value) + .addDataElementRequest("org.iso.23220.1", "family_name", true) + .addDataElementRequest("org.iso.23220.1", "given_name", true) + .addDataElementRequest("org.iso.23220.1", "birth_date", true) + .build() + + val presentedMdoc = mdoc.presentWithDeviceSignature(mdocRequest, deviceAuthentication, cryptoProvider, DEVICE_KEY_ID) + + presentedMdoc.verify( + MDocVerificationParams( + VerificationType.forPresentation, + ISSUER_KEY_ID, DEVICE_KEY_ID, + deviceAuthentication = deviceAuthentication, + mDocRequest = mdocRequest + ), + cryptoProvider + ) shouldBe true + + presentedMdoc.nameSpaces.forEach { ns -> + println("Presented mobile eID ($ns)") + presentedMdoc.getIssuerSignedItems(ns).forEach { issuerSignedItem -> + println("- ${issuerSignedItem.elementIdentifier.value}: ${issuerSignedItem.elementValue.value.toString()}") + } + } + } } \ No newline at end of file