Skip to content

Commit

Permalink
add notice about missing nameSpaces field in CBOR for ISO 23220-4
Browse files Browse the repository at this point in the history
  • Loading branch information
vkanellopoulos committed May 28, 2024
1 parent b57d93a commit 5f1e10c
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 59 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,11 @@ In order to add a new document in `DocumentManager`, the following steps should
3. Add the document to the `DocumentManager` using the `addDocument` method.

In order to use with the `addDocument` method, document's data must be in CBOR bytes that has the IssuerSigned structure
according to ISO 23220-4 :
according to ISO 23220-4 __*__ :

```cddl
IssuerSigned = {
"nameSpaces" : IssuerNameSpaces, ; Returned data elements
?"nameSpaces" : IssuerNameSpaces, ; Returned data elements
"issuerAuth" : IssuerAuth ; Contains the mobile security object (MSO) for issuer data authentication
}
IssuerNameSpaces = { ; Returned data elements for each namespace
Expand All @@ -155,6 +155,8 @@ IssuerSignedItem = {
IssuerAuth = COSE_Sign1 ; The payload is MobileSecurityObjectBytes
```

__*__**Important note**: Currently, the library does not support IssuerSigned structure without the `nameSpaces` field.

See the code below for an example of how to add a new document in `DocumentManager`:

```kotlin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Expected data format is CBOR. The CBOR data must be in the following structure:

```cddl
IssuerSigned = {
"nameSpaces" : IssuerNameSpaces, ; Returned data elements
?"nameSpaces" : IssuerNameSpaces, ; Returned data elements
"issuerAuth" : IssuerAuth ; Contains the mobile security object (MSO) for issuer data authentication
}
IssuerNameSpaces = { ; Returned data elements for each namespace
Expand All @@ -27,6 +27,8 @@ IssuerSignedItem = {
IssuerAuth = COSE_Sign1 ; The payload is MobileSecurityObjectBytes
```

**Important** Currently `nameSpaces` field should exist and must not be empty.

The document is added in the storage and can be retrieved using the DocumentManager::getDocumentById method.

#### Return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Expected data format is CBOR. The CBOR data must be in the following structure:

```cddl
IssuerSigned = {
"nameSpaces" : IssuerNameSpaces, ; Returned data elements
?"nameSpaces" : IssuerNameSpaces, ; Returned data elements
"issuerAuth" : IssuerAuth ; Contains the mobile security object (MSO) for issuer data authentication
}
IssuerNameSpaces = { ; Returned data elements for each namespace
Expand All @@ -27,6 +27,8 @@ IssuerSignedItem = {
IssuerAuth = COSE_Sign1 ; The payload is MobileSecurityObjectBytes
```

**Important** Currently `nameSpaces` field should exist and must not be empty.

The document is added in the storage and can be retrieved using the DocumentManager::getDocumentById method.

#### Return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ interface DocumentManager {
*
* ```cddl
* IssuerSigned = {
* "nameSpaces" : IssuerNameSpaces, ; Returned data elements
* ?"nameSpaces" : IssuerNameSpaces, ; Returned data elements
* "issuerAuth" : IssuerAuth ; Contains the mobile security object (MSO) for issuer data authentication
* }
* IssuerNameSpaces = { ; Returned data elements for each namespace
Expand All @@ -99,6 +99,8 @@ interface DocumentManager {
* IssuerAuth = COSE_Sign1 ; The payload is MobileSecurityObjectBytes
* ```
*
* **Important** Currently `nameSpaces` field should exist and must not be empty.
*
* The document is added in the storage and can be retrieved using the
* [DocumentManager::getDocumentById] method.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@
*/
package eu.europa.ec.eudi.wallet.document.internal

import COSE.AlgorithmID.ECDSA_256
import COSE.HeaderKeys.Algorithm
import COSE.OneKey
import COSE.Sign1Message
import com.android.identity.mdoc.mso.MobileSecurityObjectGenerator
import com.android.identity.util.Timestamp
import com.upokecenter.cbor.CBORObject
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.util.io.pem.PemReader
import java.security.KeyFactory
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.PublicKey
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.security.spec.PKCS8EncodedKeySpec
Expand Down Expand Up @@ -72,3 +80,56 @@ internal val issuerCertificate: X509Certificate = PemReader(SAMPLE_ISSUER_DS.rea
@get:JvmSynthetic
internal val PrivateKey.oneKey
get() = OneKey(null, this)

@JvmSynthetic
internal fun generateMso(
digestAlg: String,
docType: String,
authKey: PublicKey,
nameSpaces: CBORObject,
) =
MobileSecurityObjectGenerator(digestAlg, docType, authKey)
.apply {
val now = Timestamp.now().toEpochMilli()
val signed = Timestamp.ofEpochMilli(now)
val validFrom = Timestamp.ofEpochMilli(now)
val validUntil = Timestamp.ofEpochMilli(now + 1000L * 60L * 60L * 24L * 365L)
setValidityInfo(signed, validFrom, validUntil, null)

val digestIds = nameSpaces.entries.associate { (nameSpace, issuerSignedItems) ->
nameSpace.AsString() to calculateDigests(digestAlg, issuerSignedItems)
}
digestIds.forEach { (nameSpace, digestIds) ->
addDigestIdsForNamespace(nameSpace, digestIds)
}
}
.generate()

@JvmSynthetic
internal fun calculateDigests(digestAlg: String, issuerSignedItems: CBORObject): Map<Long, ByteArray> {
return issuerSignedItems.values.associate { issuerSignedItemBytes ->
val issuerSignedItem = issuerSignedItemBytes.getEmbeddedCBORObject()
val digest = MessageDigest.getInstance(digestAlg)
.digest(issuerSignedItemBytes.EncodeToBytes())
issuerSignedItem["digestID"].AsInt32().toLong() to digest
}
}

@JvmSynthetic
internal fun signMso(mso: ByteArray) = Sign1Message(false, true).apply {
protectedAttributes.Add(Algorithm.AsCBOR(), ECDSA_256.AsCBOR())
unprotectedAttributes.Add(33L, issuerCertificate.encoded)
SetContent(mso.withTag24())
sign(issuerPrivateKey.oneKey)
}.EncodeToCBORObject()

@JvmSynthetic
internal fun generateData(
issuerNameSpaces: CBORObject,
issuerAuth: CBORObject,
): ByteArray {
return mapOf(
"nameSpaces" to issuerNameSpaces,
"issuerAuth" to issuerAuth,
).let { CBORObject.FromObject(it).EncodeToBytes() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,12 @@
*/
package eu.europa.ec.eudi.wallet.document.sample

import COSE.AlgorithmID.ECDSA_256
import COSE.HeaderKeys.Algorithm
import COSE.Sign1Message
import android.content.Context
import com.android.identity.mdoc.mso.MobileSecurityObjectGenerator
import com.android.identity.util.Timestamp
import com.upokecenter.cbor.CBORObject
import eu.europa.ec.eudi.wallet.document.AddDocumentResult
import eu.europa.ec.eudi.wallet.document.CreateIssuanceRequestResult
import eu.europa.ec.eudi.wallet.document.DocumentManager
import eu.europa.ec.eudi.wallet.document.internal.*
import java.security.MessageDigest
import java.security.PublicKey

/**
* A [SampleDocumentManager] implementation that composes a [DocumentManager] and provides methods to load sample data.
Expand Down Expand Up @@ -72,7 +65,7 @@ class SampleDocumentManagerImpl(
request.name = context.docTypeName(docType) ?: docType
val authKey = request.publicKey

val mso = generateMso(docType, authKey, nameSpaces)
val mso = generateMso(DIGEST_ALG, docType, authKey, nameSpaces)
val issuerAuth = signMso(mso)
val data = generateData(nameSpaces, issuerAuth)

Expand All @@ -94,51 +87,5 @@ class SampleDocumentManagerImpl(

private companion object {
private const val DIGEST_ALG = "SHA-256"

private fun generateMso(
docType: String,
authKey: PublicKey,
nameSpaces: CBORObject,
) =
MobileSecurityObjectGenerator(DIGEST_ALG, docType, authKey)
.apply {
val now = Timestamp.now().toEpochMilli()
val signed = Timestamp.ofEpochMilli(now)
val validFrom = Timestamp.ofEpochMilli(now)
val validUntil = Timestamp.ofEpochMilli(now + 1000L * 60L * 60L * 24L * 365L)
setValidityInfo(signed, validFrom, validUntil, null)

nameSpaces.entries.forEach { (nameSpace, issuerSignedItems) ->
val digestIds = calculateDigests(issuerSignedItems)
addDigestIdsForNamespace(nameSpace.AsString(), digestIds)
}
}
.generate()

private fun calculateDigests(issuerSignedItems: CBORObject): Map<Long, ByteArray> {
return issuerSignedItems.values.associate { issuerSignedItemBytes ->
val issuerSignedItem = issuerSignedItemBytes.getEmbeddedCBORObject()
val digest = MessageDigest.getInstance(DIGEST_ALG)
.digest(issuerSignedItemBytes.EncodeToBytes())
issuerSignedItem["digestID"].AsInt32().toLong() to digest
}
}

private fun signMso(mso: ByteArray) = Sign1Message(false, true).apply {
protectedAttributes.Add(Algorithm.AsCBOR(), ECDSA_256.AsCBOR())
unprotectedAttributes.Add(33L, issuerCertificate.encoded)
SetContent(mso.withTag24())
sign(issuerPrivateKey.oneKey)
}.EncodeToCBORObject()

private fun generateData(
issuerNameSpaces: CBORObject,
issuerAuth: CBORObject,
): ByteArray {
return mapOf(
"nameSpaces" to issuerNameSpaces,
"issuerAuth" to issuerAuth,
).let { CBORObject.FromObject(it).EncodeToBytes() }
}
}
}

0 comments on commit 5f1e10c

Please sign in to comment.