Skip to content

Commit

Permalink
COSE: Ensure verification of signed structures
Browse files Browse the repository at this point in the history
  • Loading branch information
nodh committed Dec 17, 2024
1 parent 5301c29 commit aeeb08f
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ class CredentialIssuer(
if (cwt.nonce == null || !authorizationService.verifyClientNonce(cwt.nonce!!.decodeToString()))
throw OAuth2Exception(Errors.INVALID_PROOF)
.also { Napier.w("client did provide invalid nonce in CWT in proof: ${cwt.nonce}") }
val header = coseSigned.protectedHeader.value
val header = coseSigned.protectedHeader
if (header.contentType != PROOF_CWT_TYPE)
throw OAuth2Exception(Errors.INVALID_PROOF)
.also { Napier.w("client did provide invalid header type in CWT in proof: $header") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ class DefaultCoseService(private val cryptoService: CryptoService) : CoseService
addCertificate: Boolean,
): KmmResult<CoseSigned<P>> = catching {
protectedHeader.withAlgorithmAndKeyId(addKeyId).let { coseHeader ->
calcSignature(coseHeader, payload, serializer).let { (rawPayload, signature) ->
CoseSigned<P>(
calcSignature(coseHeader, payload, serializer).let { signature ->
CoseSigned.create(
protectedHeader = coseHeader,
unprotectedHeader = unprotectedHeader.withCertificateIfExists(addCertificate),
payload = payload,
signature = signature,
rawPayload = rawPayload
payloadSerializer = serializer,
)
}
}
Expand Down Expand Up @@ -108,13 +108,16 @@ class DefaultCoseService(private val cryptoService: CryptoService) : CoseService
protectedHeader: CoseHeader,
payload: P?,
serializer: KSerializer<P>,
): Pair<ByteArray?, CryptoSignature.RawByteEncodable> =
CoseSigned.prepareCoseSignatureInput<P>(protectedHeader, payload, serializer).let { signatureInput ->
): CryptoSignature.RawByteEncodable =
CoseSigned.prepare<P>(
protectedHeader = protectedHeader,
externalAad = byteArrayOf(),
payload = payload,
payloadSerializer = serializer
).let { signatureInput ->
cryptoService.sign(signatureInput.serialize()).asKmmResult().getOrElse {
Napier.w("No signature from native code", it)
throw it
}.let { signature ->
signatureInput.payload to signature
}
}
}
Expand All @@ -132,7 +135,7 @@ class DefaultVerifierCoseService(
externalAad: ByteArray,
) = catching {
val signatureInput = coseSigned.prepareCoseSignatureInput(externalAad = externalAad)
val algorithm = coseSigned.protectedHeader.value.algorithm
val algorithm = coseSigned.protectedHeader.algorithm
?: throw IllegalArgumentException("Algorithm not specified")
val publicKey = signer.toCryptoPublicKey().getOrElse { ex ->
throw IllegalArgumentException("Signer not convertible")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import at.asitplus.wallet.lib.agent.CryptoService
import at.asitplus.wallet.lib.agent.DefaultCryptoService
import at.asitplus.wallet.lib.agent.EphemeralKeyWithoutCert
import at.asitplus.wallet.lib.iso.*
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.matthewnelson.encoding.base16.Base16
import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString
import kotlinx.datetime.Clock
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.builtins.ByteArraySerializer
Expand All @@ -28,7 +31,8 @@ class CoseServiceTest : FreeSpec({
cryptoService = DefaultCryptoService(keyMaterial)
coseService = DefaultCoseService(cryptoService)
verifierCoseService = DefaultVerifierCoseService()
randomPayload = Random.nextBytes(32)
// Prevent COSE-special bytes at the start of the payload
randomPayload = "This is the content: ".encodeToByteArray() + Random.nextBytes(32)
coseKey = keyMaterial.publicKey.toCoseKey().getOrThrow()
}

Expand All @@ -43,8 +47,14 @@ class CoseServiceTest : FreeSpec({
signed.payload shouldBe randomPayload
signed.signature.shouldNotBeNull()

val parsed = CoseSigned.deserialize(parameterSerializer, signed.serialize(parameterSerializer)).getOrThrow()
parsed shouldBe signed
val serialized = signed.serialize(parameterSerializer)
val parsed = CoseSigned.deserialize(parameterSerializer, serialized).getOrThrow()
withClue(
"signed.payload ${signed.wireFormat.payload?.encodeToString(Base16())} " +
"vs parsed.payload: ${parsed.payload?.encodeToString(Base16())}"
) {
parsed shouldBe signed
}

val result = verifierCoseService.verifyCose(parsed, coseKey)
result.isSuccess shouldBe true
Expand Down Expand Up @@ -76,7 +86,7 @@ class CoseServiceTest : FreeSpec({
signed.payload shouldBe mso
signed.signature.shouldNotBeNull()

val parsed = CoseSigned.deserialize(parameterSerializer,signed.serialize(parameterSerializer)).getOrThrow()
val parsed = CoseSigned.deserialize(parameterSerializer, signed.serialize(parameterSerializer)).getOrThrow()
parsed shouldBe signed

val result = verifierCoseService.verifyCose(parsed, coseKey)
Expand All @@ -94,7 +104,7 @@ class CoseServiceTest : FreeSpec({
signed.payload shouldBe null
signed.signature.shouldNotBeNull()

val parsed = CoseSigned.deserialize(parameterSerializer,signed.serialize(parameterSerializer)).getOrThrow()
val parsed = CoseSigned.deserialize(parameterSerializer, signed.serialize(parameterSerializer)).getOrThrow()
parsed shouldBe signed

val result = verifierCoseService.verifyCose(parsed, coseKey)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package at.asitplus.wallet.lib.cbor

import at.asitplus.signum.indispensable.CryptoSignature
import at.asitplus.signum.indispensable.cosef.CoseAlgorithm
import at.asitplus.signum.indispensable.cosef.CoseHeader
import at.asitplus.signum.indispensable.cosef.CoseSigned
import at.asitplus.wallet.lib.iso.*
Expand Down Expand Up @@ -47,9 +48,21 @@ class DeviceSignedItemSerializationTest : FreeSpec({
key = elementId,
value = Random.nextBytes(32),
)
val protectedHeader = CoseHeader()
val issuerAuth = CoseSigned<MobileSecurityObject>(protectedHeader, null, null, CryptoSignature.RSAorHMAC(byteArrayOf()), null)
val deviceAuth = CoseSigned<ByteArray>(protectedHeader, null, null, CryptoSignature.RSAorHMAC(byteArrayOf()), null)
val protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256)
val issuerAuth = CoseSigned.create(
protectedHeader,
null,
null,
CryptoSignature.RSAorHMAC(byteArrayOf()),
MobileSecurityObject.serializer()
)
val deviceAuth = CoseSigned.create(
protectedHeader,
null,
null,
CryptoSignature.RSAorHMAC(byteArrayOf()),
ByteArraySerializer()
)

val doc = Document(
docType = uuid4().toString(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package at.asitplus.wallet.lib.cbor

import at.asitplus.signum.indispensable.CryptoSignature
import at.asitplus.signum.indispensable.cosef.CoseAlgorithm
import at.asitplus.signum.indispensable.cosef.CoseHeader
import at.asitplus.signum.indispensable.cosef.CoseSigned
import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper
Expand Down Expand Up @@ -46,8 +47,14 @@ class IssuerSignedItemSerializationTest : FreeSpec({
elementIdentifier = elementId,
elementValue = Random.nextBytes(32),
)
val protectedHeader = CoseHeader()
val issuerAuth = CoseSigned<MobileSecurityObject>(protectedHeader, null, null, CryptoSignature.RSAorHMAC(byteArrayOf()), null)
val protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256)
val issuerAuth = CoseSigned.create(
protectedHeader,
null,
null,
CryptoSignature.RSAorHMAC(byteArrayOf()),
MobileSecurityObject.serializer()
)
val doc = Document(
docType = uuid4().toString(),
issuerSigned = IssuerSigned.fromIssuerSignedItems(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import io.matthewnelson.encoding.base16.Base16
import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString
import kotlinx.datetime.Clock
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.builtins.ByteArraySerializer
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlin.random.Random
Expand All @@ -48,14 +49,13 @@ class Tag24SerializationTest : FreeSpec({
)
),
deviceAuth = DeviceAuth(
deviceSignature = CoseSigned<ByteArray>(
protectedHeader = CoseHeader(),
deviceSignature = CoseSigned.create(
protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256),
unprotectedHeader = null,
payload = byteArrayOf(),
payload = null,
signature = CryptoSignature.RSAorHMAC(byteArrayOf()),
rawPayload = byteArrayOf()
payloadSerializer = ByteArraySerializer()
)

)
)

Expand Down Expand Up @@ -121,12 +121,12 @@ class Tag24SerializationTest : FreeSpec({
validityInfo = ValidityInfo(Clock.System.now(), Clock.System.now(), Clock.System.now())
)
val serializedMso = mso.serializeForIssuerAuth()
val input = CoseSigned<MobileSecurityObject>(
protectedHeader = CoseHeader(),
val input = CoseSigned.create(
protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256),
unprotectedHeader = null,
payload = mso,
signature = CryptoSignature.RSAorHMAC(byteArrayOf()),
rawPayload = serializedMso,
payloadSerializer = MobileSecurityObject.serializer(),
)

val serialized = vckCborSerializer.encodeToByteArray(input)
Expand Down Expand Up @@ -172,12 +172,12 @@ private fun MobileSecurityObject.Companion.deserializeFromIssuerAuth(it: ByteArr
private fun deviceKeyInfo() =
DeviceKeyInfo(CoseKey(CoseKeyType.EC2, keyParams = CoseKeyParams.EcYBoolParams(CoseEllipticCurve.P256)))

private fun issuerAuth() = CoseSigned<MobileSecurityObject>(
protectedHeader = CoseHeader(),
private fun issuerAuth() = CoseSigned.create(
protectedHeader = CoseHeader(algorithm = CoseAlgorithm.RS256),
unprotectedHeader = null,
payload = null,
signature = CryptoSignature.RSAorHMAC(byteArrayOf()),
rawPayload = null,
payloadSerializer = MobileSecurityObject.serializer(),
)

private fun issuerSignedItem() = IssuerSignedItem(0u, Random.nextBytes(16), "identifier", "value")
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package at.asitplus.wallet.lib.cbor

import at.asitplus.signum.indispensable.*
import at.asitplus.signum.indispensable.cosef.*
import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper
import at.asitplus.signum.supreme.HazardousMaterials
import at.asitplus.signum.supreme.hazmat.jcaPrivateKey
import at.asitplus.signum.supreme.sign.EphemeralKey
import at.asitplus.wallet.lib.agent.DefaultCryptoService
import at.asitplus.wallet.lib.agent.EphemeralKeyWithoutCert
import at.asitplus.wallet.lib.iso.vckCborSerializer
import com.authlete.cbor.CBORByteArray
import com.authlete.cbor.CBORDecoder
import com.authlete.cbor.CBORTaggedItem
Expand Down Expand Up @@ -165,7 +165,7 @@ class CoseServiceJvmTest : FreeSpec({
SigStructureBuilder().sign1(parsedCoseSign1).build().encode().encodeToString(Base16())
val signatureInput = CoseSignatureInput(
contextString = "Signature1",
protectedHeader = ByteStringWrapper(CoseHeader(algorithm = coseAlgorithm)),
protectedHeader = vckCborSerializer.encodeToByteArray(CoseHeader.serializer(), CoseHeader(algorithm = coseAlgorithm)),
externalAad = byteArrayOf(),
payload = randomPayload.encodeToByteArray(),
).serialize().encodeToString(Base16())
Expand Down

0 comments on commit aeeb08f

Please sign in to comment.