diff --git a/CHANGELOG.md b/CHANGELOG.md index 96fc6276..9055d58a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### 3.11.0 NEXT +* Add type parameter to `CoseSigned` for its payload +* Add companion method `CoseSigned.fromObject` to create a `CoseSigned` with a typed payload (outside of the usual `ByteArray`) + ### 3.10.0 (Supreme 0.5.0) More ~~cowbell~~ targets! A new artifact, minor breaking changes and a lot more targets ahead! diff --git a/docs/docs/examples.md b/docs/docs/examples.md index 82aca051..e5e3dcb6 100644 --- a/docs/docs/examples.md +++ b/docs/docs/examples.md @@ -69,29 +69,28 @@ val protectedHeader = CoseHeader( val payload = byteArrayOf(0xC, 0xA, 0xF, 0xE) ``` -Both of these are signature inputs, so we'll construct a `CoseSignatureInput` to sign. +Both of these are signature inputs, so we can construct the signature input: ```kotlin -val signatureInput = CoseSignatureInput( - contextString = "Signature1", - protectedHeader = ByteStringWrapper(protectedHeader), - externalAad = byteArrayOf(), +val signatureInput = CoseSigned.prepareCoseSignatureInput( + protectedHeader = protectedHeader, payload = payload, -).serialize() + externalAad = byteArrayOf() +) ``` - Now, everything is ready to be signed: ```kotlin val signature = signer.sign(signatureInput).signature //TODO handle error -val coseSigned = CoseSigned( - ByteStringWrapper(protectedHeader), - unprotectedHeader = null, - payload, - signature -).serialize() // sadly, there's no cwt.io, but you can use cbor.me to explore the signed data +CoseSigned( + protectedHeader = ByteStringWrapper(protectedHeader), + unprotectedHeader = unprotectedHeader, + payload = payload, + signature = signature +) +// sadly, there's no cwt.io, but you can use cbor.me to explore the signed data ``` ## Create and Parse a Custom-Tagged ASN.1 Structure diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSigned.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSigned.kt index acdcd57a..f5a3faf2 100644 --- a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSigned.kt +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSigned.kt @@ -1,28 +1,17 @@ package at.asitplus.signum.indispensable.cosef +import at.asitplus.KmmResult import at.asitplus.catching -import at.asitplus.signum.indispensable.CryptoPublicKey -import at.asitplus.signum.indispensable.CryptoSignature -import at.asitplus.signum.indispensable.SignatureAlgorithm +import at.asitplus.signum.indispensable.* import at.asitplus.signum.indispensable.cosef.io.Base16Strict import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper +import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapperSerializer import at.asitplus.signum.indispensable.cosef.io.coseCompliantSerializer import at.asitplus.signum.indispensable.pki.X509Certificate import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.builtins.ByteArraySerializer +import kotlinx.serialization.* import kotlinx.serialization.cbor.ByteString import kotlinx.serialization.cbor.CborArray -import kotlinx.serialization.decodeFromByteArray -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encodeToByteArray -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder /** * Representation of a signed COSE_Sign1 object, i.e. consisting of protected header, unprotected header and payload. @@ -30,26 +19,29 @@ import kotlinx.serialization.encoding.Encoder * See [RFC 9052](https://www.rfc-editor.org/rfc/rfc9052.html). */ @OptIn(ExperimentalSerializationApi::class) -@Serializable +@Serializable(with = CoseSignedSerializer::class) @CborArray -data class CoseSigned( - @Serializable(with = ByteStringWrapperCoseHeaderSerializer::class) +data class CoseSigned

( @ByteString val protectedHeader: ByteStringWrapper, val unprotectedHeader: CoseHeader?, @ByteString val payload: ByteArray?, @ByteString - @SerialName("signature") - private val rawSignature: ByteArray + val rawSignature: ByteArray, ) { constructor( - protectedHeader: ByteStringWrapper, + protectedHeader: CoseHeader, unprotectedHeader: CoseHeader?, payload: ByteArray?, signature: CryptoSignature.RawByteEncodable - ) : this(protectedHeader, unprotectedHeader, payload, signature.rawByteArray) + ) : this( + protectedHeader = ByteStringWrapper(value = protectedHeader), + unprotectedHeader = unprotectedHeader, + payload = payload, + rawSignature = signature.rawByteArray + ) val signature: CryptoSignature by lazy { if (protectedHeader.value.usesEC() ?: unprotectedHeader?.usesEC() ?: (rawSignature.size < 2048)) @@ -57,19 +49,30 @@ data class CoseSigned( else CryptoSignature.RSAorHMAC(rawSignature) } - fun serialize() = coseCompliantSerializer.encodeToByteArray(this) + fun serialize(): ByteArray = coseCompliantSerializer.encodeToByteArray(CoseSignedSerializer(), this) + + /** + * Decodes the payload of this object into a [ByteStringWrapper] containing an object of type [P]. + * + * Note that this does not work if the payload is directly a [ByteArray]. + */ + fun getTypedPayload(deserializer: KSerializer

): KmmResult?> = catching { + payload?.let { + coseCompliantSerializer.decodeFromByteArray(ByteStringWrapperSerializer(deserializer), it) + } + } override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false - other as CoseSigned + other as CoseSigned<*> if (protectedHeader != other.protectedHeader) return false if (unprotectedHeader != other.unprotectedHeader) return false if (payload != null) { if (other.payload == null) return false - if (!payload.contentEquals(other.payload)) return false + if (!payload.contentEqualsIfArray(other.payload)) return false } else if (other.payload != null) return false return rawSignature.contentEquals(other.rawSignature) } @@ -77,7 +80,7 @@ data class CoseSigned( override fun hashCode(): Int { var result = protectedHeader.hashCode() result = 31 * result + (unprotectedHeader?.hashCode() ?: 0) - result = 31 * result + (payload?.contentHashCode() ?: 0) + result = 31 * result + (payload?.contentHashCodeIfArray() ?: 0) result = 31 * result + rawSignature.contentHashCode() return result } @@ -90,9 +93,53 @@ data class CoseSigned( } companion object { - fun deserialize(it: ByteArray) = catching { - coseCompliantSerializer.decodeFromByteArray(it) + fun deserialize(it: ByteArray): KmmResult> = catching { + coseCompliantSerializer.decodeFromByteArray>(it) } + + /** + * Creates a [CoseSigned] object from the given parameters, + * encapsulating the [payload] into a [ByteStringWrapper]. + * + * This has to be an inline function with a reified type parameter, + * so it can't be a constructor (leads to a runtime error). + */ + inline fun fromObject( + protectedHeader: CoseHeader, + unprotectedHeader: CoseHeader?, + payload: P, + signature: CryptoSignature.RawByteEncodable + ) = CoseSigned

( + protectedHeader = ByteStringWrapper(value = protectedHeader), + unprotectedHeader = unprotectedHeader, + payload = when (payload) { + is ByteArray -> payload + is ByteStringWrapper<*> -> coseCompliantSerializer.encodeToByteArray(payload) + else -> coseCompliantSerializer.encodeToByteArray(ByteStringWrapper(payload)) + }, + rawSignature = signature.rawByteArray + ) + + /** + * Called by COSE signing implementations to get the bytes that will be + * used as the input for signature calculation of a `COSE_Sign1` object + */ + inline fun prepareCoseSignatureInput( + protectedHeader: CoseHeader, + payload: P?, + externalAad: ByteArray = byteArrayOf(), + ): ByteArray = CoseSignatureInput( + contextString = "Signature1", + protectedHeader = ByteStringWrapper(protectedHeader), + externalAad = externalAad, + payload = when (payload) { + is ByteArray -> payload + is ByteStringWrapper<*> -> coseCompliantSerializer.encodeToByteArray(payload) + else -> coseCompliantSerializer.encodeToByteArray(ByteStringWrapper(payload)) + }, + ).serialize() + + } } @@ -105,7 +152,6 @@ fun CoseHeader.usesEC(): Boolean? = algorithm?.algorithm?.let { it is SignatureA @CborArray data class CoseSignatureInput( val contextString: String, - @Serializable(with = ByteStringWrapperCoseHeaderSerializer::class) @ByteString val protectedHeader: ByteStringWrapper, @ByteString @@ -155,19 +201,3 @@ data class CoseSignatureInput( } } -object ByteStringWrapperCoseHeaderSerializer : KSerializer> { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("ByteStringWrapperCoseHeaderSerializer", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: ByteStringWrapper) { - val bytes = coseCompliantSerializer.encodeToByteArray(value.value) - encoder.encodeSerializableValue(ByteArraySerializer(), bytes) - } - - override fun deserialize(decoder: Decoder): ByteStringWrapper { - val bytes = decoder.decodeSerializableValue(ByteArraySerializer()) - return ByteStringWrapper(coseCompliantSerializer.decodeFromByteArray(bytes), bytes) - } - -} diff --git a/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt new file mode 100644 index 00000000..19a8bae5 --- /dev/null +++ b/indispensable-cosef/src/commonMain/kotlin/at/asitplus/signum/indispensable/cosef/CoseSignedSerializer.kt @@ -0,0 +1,44 @@ +package at.asitplus.signum.indispensable.cosef + +import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapperSerializer +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ByteArraySerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.descriptors.buildSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.encoding.encodeStructure + +class CoseSignedSerializer

: KSerializer> { + + @OptIn(InternalSerializationApi::class) + override val descriptor: SerialDescriptor = buildSerialDescriptor("CoseSigned", StructureKind.LIST) { + element("protectedHeader", ByteStringWrapperSerializer(CoseHeader.serializer()).descriptor) + element("unprotectedHeader", CoseHeader.serializer().descriptor) + element("payload", ByteArraySerializer().descriptor) + element("signature", ByteArraySerializer().descriptor) + } + + override fun deserialize(decoder: Decoder): CoseSigned

{ + return decoder.decodeStructure(descriptor) { + val protectedHeader = decodeSerializableElement(descriptor, 0, ByteStringWrapperSerializer(CoseHeader.serializer())) + val unprotectedHeader = decodeNullableSerializableElement(descriptor, 1, CoseHeader.serializer()) + val payload = decodeNullableSerializableElement(descriptor, 2, ByteArraySerializer()) + val signature = decodeSerializableElement(descriptor, 3, ByteArraySerializer()) + CoseSigned(protectedHeader, unprotectedHeader, payload, signature) + } + } + + override fun serialize(encoder: Encoder, value: CoseSigned

) { + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, ByteStringWrapperSerializer(CoseHeader.serializer()), value.protectedHeader) + encodeNullableSerializableElement(descriptor, 1, CoseHeader.serializer(), value.unprotectedHeader) + encodeNullableSerializableElement(descriptor, 2, ByteArraySerializer(), value.payload) + encodeSerializableElement(descriptor, 3, ByteArraySerializer(), value.rawSignature) + } + } + +} diff --git a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt index da1fceba..c6e27946 100644 --- a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt +++ b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseEqualsTest.kt @@ -1,5 +1,7 @@ package at.asitplus.signum.indispensable.cosef +import at.asitplus.signum.indispensable.CryptoSignature +import at.asitplus.signum.indispensable.cosef.io.Base16Strict import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe @@ -9,65 +11,120 @@ import io.kotest.property.arbitrary.byte import io.kotest.property.arbitrary.byteArray import io.kotest.property.arbitrary.int import io.kotest.property.checkAll +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString +import kotlinx.serialization.Serializable class CoseEqualsTest : FreeSpec({ - "Test Equals" - { - checkAll( - Arb.byteArray( - length = Arb.int(0, 10), - content = Arb.byte() + "equals with byte array" { + checkAll(Arb.byteArray(length = Arb.int(0, 10), content = Arb.byte())) { bytes -> + val bytesSigned1 = CoseSigned( + protectedHeader = ByteStringWrapper(CoseHeader()), + unprotectedHeader = null, + payload = bytes, + rawSignature = bytes + ) + val bytesSigned2 = CoseSigned( + protectedHeader = ByteStringWrapper(CoseHeader()), + unprotectedHeader = null, + payload = bytes, + rawSignature = bytes ) - ) { s1 -> - val signed1 = CoseSigned( - protectedHeader = ByteStringWrapper(CoseHeader()), + + bytesSigned1 shouldBe bytesSigned1 + bytesSigned2 shouldBe bytesSigned1 + bytesSigned1.hashCode() shouldBe bytesSigned1.hashCode() + bytesSigned1.hashCode() shouldBe bytesSigned2.hashCode() + + val reversed = bytes.reversedArray().let { it + it + 1 + 3 + 5 } + val reversedSigned1 = CoseSigned( + protectedHeader = ByteStringWrapper(CoseHeader()), unprotectedHeader = null, - payload = s1, - rawSignature = s1 + payload = reversed, + rawSignature = reversed ) - val signed11 = CoseSigned( - protectedHeader = ByteStringWrapper(CoseHeader()), + val reversedSigned2 = CoseSigned( + protectedHeader = ByteStringWrapper(CoseHeader()), unprotectedHeader = null, - payload = s1, - rawSignature = s1 + payload = reversed, + rawSignature = reversed ) - signed1 shouldBe signed1 - signed11 shouldBe signed1 - signed1.hashCode() shouldBe signed1.hashCode() - signed1.hashCode() shouldBe signed11.hashCode() + reversedSigned2 shouldBe reversedSigned2 + reversedSigned2 shouldBe reversedSigned1 + + reversedSigned1.hashCode() shouldBe reversedSigned1.hashCode() + reversedSigned1.hashCode() shouldBe reversedSigned2.hashCode() + + bytesSigned1 shouldNotBe reversedSigned1 + bytesSigned1 shouldNotBe reversedSigned2 + + bytesSigned1.hashCode() shouldNotBe reversedSigned1.hashCode() + bytesSigned1.hashCode() shouldNotBe reversedSigned2.hashCode() + + reversedSigned1 shouldNotBe bytesSigned1 + reversedSigned1 shouldNotBe bytesSigned2 - val s2 = s1.reversedArray().let { it + it + 1 + 3 + 5 } - val signed2 = CoseSigned( - protectedHeader = ByteStringWrapper(CoseHeader()), + reversedSigned1.hashCode() shouldNotBe bytesSigned1.hashCode() + reversedSigned1.hashCode() shouldNotBe bytesSigned2.hashCode() + } + } + + "equals with data class" { + checkAll(Arb.byteArray(length = Arb.int(0, 10), content = Arb.byte())) { bytes -> + val payload = DataClass(content = bytes.encodeToString(Base16Strict)) + val bytesSigned1 = CoseSigned.fromObject( + protectedHeader = CoseHeader(), unprotectedHeader = null, - payload = s2, - rawSignature = s2 + payload = payload, + signature = CryptoSignature.RSAorHMAC(bytes) ) - val signed22 = CoseSigned( - protectedHeader = ByteStringWrapper(CoseHeader()), + val bytesSigned2 = CoseSigned.fromObject( + protectedHeader = CoseHeader(), unprotectedHeader = null, - payload = s2, - rawSignature = s2 + payload = payload, + signature = CryptoSignature.RSAorHMAC(bytes) ) - signed22 shouldBe signed22 - signed22 shouldBe signed2 + bytesSigned1 shouldBe bytesSigned1 + bytesSigned2 shouldBe bytesSigned1 + bytesSigned1.hashCode() shouldBe bytesSigned1.hashCode() + bytesSigned1.hashCode() shouldBe bytesSigned2.hashCode() - signed2.hashCode() shouldBe signed2.hashCode() - signed2.hashCode() shouldBe signed22.hashCode() + val reversed = DataClass(content = bytes.reversedArray().let { it + it + 1 + 3 + 5 }.encodeToString(Base16Strict)) + val reversedSigned1 = CoseSigned.fromObject( + protectedHeader = CoseHeader(), + unprotectedHeader = null, + payload = reversed, + signature = CryptoSignature.RSAorHMAC(bytes) + ) + val reversedSigned2 = CoseSigned.fromObject( + protectedHeader = CoseHeader(), + unprotectedHeader = null, + payload = reversed, + signature = CryptoSignature.RSAorHMAC(bytes) + ).also { println(it.serialize().encodeToString(Base16Strict))} + + reversedSigned2 shouldBe reversedSigned2 + reversedSigned2 shouldBe reversedSigned1 - signed1 shouldNotBe signed2 - signed1 shouldNotBe signed22 + reversedSigned1.hashCode() shouldBe reversedSigned1.hashCode() + reversedSigned1.hashCode() shouldBe reversedSigned2.hashCode() - signed1.hashCode() shouldNotBe signed2.hashCode() - signed1.hashCode() shouldNotBe signed22.hashCode() + bytesSigned1 shouldNotBe reversedSigned1 + bytesSigned1 shouldNotBe reversedSigned2 - signed2 shouldNotBe signed1 - signed2 shouldNotBe signed11 + bytesSigned1.hashCode() shouldNotBe reversedSigned1.hashCode() + bytesSigned1.hashCode() shouldNotBe reversedSigned2.hashCode() - signed2.hashCode() shouldNotBe signed1.hashCode() - signed2.hashCode() shouldNotBe signed11.hashCode() + reversedSigned1 shouldNotBe bytesSigned1 + reversedSigned1 shouldNotBe bytesSigned2 + + reversedSigned1.hashCode() shouldNotBe bytesSigned1.hashCode() + reversedSigned1.hashCode() shouldNotBe bytesSigned2.hashCode() } } -}) \ No newline at end of file +}) + +@Serializable +data class DataClass(val content: String) \ No newline at end of file diff --git a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt index 211a6428..48f22fef 100644 --- a/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt +++ b/indispensable-cosef/src/commonTest/kotlin/at/asitplus/signum/indispensable/cosef/CoseSerializationTest.kt @@ -1,7 +1,9 @@ package at.asitplus.signum.indispensable.cosef import at.asitplus.signum.indispensable.CryptoSignature +import at.asitplus.signum.indispensable.cosef.io.Base16Strict import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper +import at.asitplus.signum.indispensable.cosef.io.coseCompliantSerializer import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.nulls.shouldNotBeNull @@ -10,32 +12,45 @@ import io.kotest.matchers.string.shouldContain import io.matthewnelson.encoding.base16.Base16 import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArray import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString +import kotlinx.serialization.builtins.ByteArraySerializer +import kotlinx.serialization.encodeToByteArray import kotlin.random.Random -val Base16Strict = Base16(strict = true) - class CoseSerializationTest : FreeSpec({ + "Serialization is correct for byte array" { + val payload = "This is the content.".encodeToByteArray() + val cose = CoseSigned( + protectedHeader = CoseHeader(algorithm = CoseAlgorithm.ES256), + unprotectedHeader = CoseHeader(), + payload = payload, + signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()) //RSAorHMAC because EC expects tuple + ) + val serialized = cose.serialize().encodeToString(Base16Strict).uppercase() + + serialized shouldContain "546869732069732074686520636F6E74656E742E" // "This is the content." + serialized shouldContain "43A10126" + cose.getTypedPayload(ByteArraySerializer()).isFailure shouldBe true + } - "Serialization is correct" { - val cose = CoseSigned( - protectedHeader = ByteStringWrapper(CoseHeader(algorithm = CoseAlgorithm.ES256)), + "Serialization is correct for data class" { + val payload = DataClass("This is the content.") + val cose = CoseSigned.fromObject( + protectedHeader = CoseHeader(algorithm = CoseAlgorithm.ES256), unprotectedHeader = CoseHeader(), - payload = "This is the content.".encodeToByteArray(), + payload = payload, signature = CryptoSignature.RSAorHMAC("bar".encodeToByteArray()) //RSAorHMAC because EC expects tuple ) val serialized = cose.serialize().encodeToString(Base16Strict).uppercase() serialized shouldContain "546869732069732074686520636F6E74656E742E" // "This is the content." serialized shouldContain "43A10126" + cose.getTypedPayload(DataClass.serializer()).getOrThrow()?.value shouldBe payload } "Serialize header" { val header = CoseHeader(algorithm = CoseAlgorithm.ES256, kid = "11".encodeToByteArray()) - header.serialize().encodeToString(Base16Strict).uppercase() - .also { println(it) } - val deserialized = CoseHeader.deserialize(header.serialize()).getOrThrow().shouldNotBeNull() deserialized.algorithm shouldBe header.algorithm @@ -49,11 +64,9 @@ class CoseSerializationTest : FreeSpec({ ) val serialized = header.serialize().encodeToString(Base16Strict).uppercase() - .also { println(it) } serialized shouldContain "COSE_Key".encodeToByteArray().encodeToString(Base16Strict) val deserialized = CoseHeader.deserialize(header.serialize()).getOrThrow().shouldNotBeNull() - deserialized.algorithm shouldBe header.algorithm deserialized.kid shouldBe header.kid } @@ -63,23 +76,48 @@ class CoseSerializationTest : FreeSpec({ "742e58408eb33e4ca31d1c465ab05aac34cc6b23d58fef5c083106c4d25a" + "91aef0b0117e2af9a291aa32e14ab834dc56ed2a223444547e01f11d3b09" + "16e5a4c345cacb36" - val cose = CoseSigned.deserialize(input.uppercase().decodeToByteArray(Base16Strict)) - .also { println(it) } - cose.shouldNotBeNull() + val cose = CoseSigned.deserialize(input.uppercase().decodeToByteArray(Base16Strict)).getOrThrow() + + cose.payload shouldBe "This is the content.".encodeToByteArray() } + "CoseSignatureInput is correct for ByteArray" { + val payload = Random.nextBytes(32) + val header = CoseHeader(algorithm = CoseAlgorithm.ES256) + val inputManual = CoseSignatureInput( + contextString = "Signature1", + protectedHeader = ByteStringWrapper(header), + externalAad = byteArrayOf(), + payload = payload + ).serialize().encodeToString(Base16()) - "CoseSignatureInput is correct" { - val signatureInput = CoseSignatureInput( + val inputLibrary = CoseSigned.prepareCoseSignatureInput( + protectedHeader = header, + payload = payload, + ).encodeToString(Base16()) + + inputManual.shouldContain("Signature1".encodeToByteArray().encodeToString(Base16())) + inputLibrary shouldBe inputManual + } + + "CoseSignatureInput is correct for custom types" { + val payload = DataClass(Random.nextBytes(32).encodeToString(Base16Strict)) + val header = CoseHeader(algorithm = CoseAlgorithm.ES256) + val inputManual = CoseSignatureInput( contextString = "Signature1", - protectedHeader = ByteStringWrapper(CoseHeader(algorithm = CoseAlgorithm.ES256)), + protectedHeader = ByteStringWrapper(header), externalAad = byteArrayOf(), - payload = Random.nextBytes(32) + payload = coseCompliantSerializer.encodeToByteArray(ByteStringWrapper(payload)), ).serialize().encodeToString(Base16()) - .also { println(it) } - signatureInput.shouldContain("Signature1".encodeToByteArray().encodeToString(Base16())) + val inputLibrary = CoseSigned.prepareCoseSignatureInput( + protectedHeader = header, + payload = payload, + ).encodeToString(Base16()) + + inputManual.shouldContain("Signature1".encodeToByteArray().encodeToString(Base16())) + inputLibrary shouldBe inputManual }