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
}