Skip to content

Commit

Permalink
Add data classes for CBOR/COSE signing
Browse files Browse the repository at this point in the history
  • Loading branch information
nodh committed Jul 17, 2023
1 parent 5b47cd7 commit 44a7d09
Show file tree
Hide file tree
Showing 10 changed files with 486 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package at.asitplus.wallet.lib.cbor

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

@Serializable(with = CoseAlgorithmSerializer::class)
enum class CoseAlgorithm(val value: Int) {

ES256(-7),
ES384(-35),
ES512(-36);

}


object CoseAlgorithmSerializer : KSerializer<CoseAlgorithm> {

override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("CoseAlgorithmSerializer", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: CoseAlgorithm) {
value.let { encoder.encodeInt(it.value) }
}

override fun deserialize(decoder: Decoder): CoseAlgorithm {
val decoded = decoder.decodeInt()
return CoseAlgorithm.values().first { it.value == decoded }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package at.asitplus.wallet.lib.cbor

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

@Serializable(with = CoseEllipticCurveSerializer::class)
enum class CoseEllipticCurve(val value: Int) {

P256(1),
P384(2),
P521(3),
X25519(4),
X448(5),
Ed25519(6),
Ed448(7);

}

object CoseEllipticCurveSerializer : KSerializer<CoseEllipticCurve?> {

override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("CoseEllipticCurveSerializer", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: CoseEllipticCurve?) {
value?.let { encoder.encodeInt(it.value) }
}

override fun deserialize(decoder: Decoder): CoseEllipticCurve? {
val decoded = decoder.decodeInt()
return CoseEllipticCurve.values().firstOrNull { it.value == decoded }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package at.asitplus.wallet.lib.cbor

import at.asitplus.wallet.lib.iso.cborSerializer
import io.github.aakira.napier.Napier
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.ByteString
import kotlinx.serialization.cbor.SerialLabel
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray

/**
* Protected header of a [CoseSigned].
*/
@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class CoseHeader(
@SerialLabel(1)
@SerialName("alg")
val algorithm: CoseAlgorithm? = null,
@SerialLabel(2)
@SerialName("crit")
val criticalHeaders: String? = null,
@SerialLabel(3)
@SerialName("content type")
val contentType: String? = null,
@SerialLabel(4)
@SerialName("kid")
val kid: String? = null,
@SerialLabel(5)
@SerialName("IV")
@ByteString
val iv: ByteArray? = null,
@SerialLabel(6)
@SerialName("Partial IV")
@ByteString
val partialIv: ByteArray? = null,
) {

fun serialize() = cborSerializer.encodeToByteArray(this)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as CoseHeader

if (algorithm != other.algorithm) return false
if (criticalHeaders != other.criticalHeaders) return false
if (contentType != other.contentType) return false
if (kid != other.kid) return false
if (iv != null) {
if (other.iv == null) return false
if (!iv.contentEquals(other.iv)) return false
} else if (other.iv != null) return false
if (partialIv != null) {
if (other.partialIv == null) return false
if (!partialIv.contentEquals(other.partialIv)) return false
} else if (other.partialIv != null) return false

return true
}

override fun hashCode(): Int {
var result = algorithm?.hashCode() ?: 0
result = 31 * result + (criticalHeaders?.hashCode() ?: 0)
result = 31 * result + (contentType?.hashCode() ?: 0)
result = 31 * result + (kid?.hashCode() ?: 0)
result = 31 * result + (iv?.contentHashCode() ?: 0)
result = 31 * result + (partialIv?.contentHashCode() ?: 0)
return result
}

companion object {
fun deserialize(it: ByteArray) = kotlin.runCatching {
cborSerializer.decodeFromByteArray<CoseHeader>(it)
}.getOrElse {
Napier.w("deserialize failed", it)
null
}
}
}
115 changes: 115 additions & 0 deletions vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseKey.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package at.asitplus.wallet.lib.cbor

import at.asitplus.wallet.lib.iso.cborSerializer
import io.github.aakira.napier.Napier
import io.matthewnelson.component.base64.encodeBase64
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.ByteString
import kotlinx.serialization.cbor.SerialLabel
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray

@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class CoseKey(
@SerialLabel(1)
@SerialName("kty")
val type: CoseKeyType,
@SerialLabel(2)
@SerialName("kid")
@ByteString
val keyId: ByteArray? = null,
@SerialLabel(3)
@SerialName("alg")
val algorithm: CoseAlgorithm? = null,
@SerialLabel(4)
@SerialName("key_ops")
val operations: Array<CoseKeyOperation>? = null,
@SerialLabel(5)
@SerialName("Base IV")
@ByteString
val baseIv: ByteArray? = null,
@SerialLabel(-1)
@SerialName("crv")
val curve: CoseEllipticCurve? = null,
@SerialLabel(-2)
@SerialName("x")
val x: ByteArray? = null,
@SerialLabel(-3)
@SerialName("y") // TODO might also be bool
val y: ByteArray? = null,
@SerialLabel(-4)
@SerialName("d")
val d: ByteArray? = null,
) {
fun serialize() = cborSerializer.encodeToByteArray(this)

companion object {

fun deserialize(it: ByteArray) = kotlin.runCatching {
cborSerializer.decodeFromByteArray<CoseHeader>(it)
}.getOrElse {
Napier.w("deserialize failed", it)
null
}

}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as CoseKey

if (type != other.type) return false
if (keyId != null) {
if (other.keyId == null) return false
if (!keyId.contentEquals(other.keyId)) return false
} else if (other.keyId != null) return false
if (algorithm != other.algorithm) return false
if (operations != null) {
if (other.operations == null) return false
if (!operations.contentEquals(other.operations)) return false
} else if (other.operations != null) return false
if (baseIv != null) {
if (other.baseIv == null) return false
if (!baseIv.contentEquals(other.baseIv)) return false
} else if (other.baseIv != null) return false
if (curve != other.curve) return false
if (x != null) {
if (other.x == null) return false
if (!x.contentEquals(other.x)) return false
} else if (other.x != null) return false
if (y != null) {
if (other.y == null) return false
if (!y.contentEquals(other.y)) return false
} else if (other.y != null) return false
if (d != null) {
if (other.d == null) return false
if (!d.contentEquals(other.d)) return false
} else if (other.d != null) return false

return true
}

override fun hashCode(): Int {
var result = type.hashCode()
result = 31 * result + (keyId?.contentHashCode() ?: 0)
result = 31 * result + (algorithm?.hashCode() ?: 0)
result = 31 * result + (operations?.contentHashCode() ?: 0)
result = 31 * result + (baseIv?.contentHashCode() ?: 0)
result = 31 * result + (curve?.hashCode() ?: 0)
result = 31 * result + (x?.contentHashCode() ?: 0)
result = 31 * result + (y?.contentHashCode() ?: 0)
result = 31 * result + (d?.contentHashCode() ?: 0)
return result
}

override fun toString(): String {
return "CoseKey(type=$type, keyId=${keyId?.encodeBase64()}, algorithm=$algorithm, operations=${operations?.contentToString()}, baseIv=${baseIv?.encodeBase64()}, curve=$curve, x=${x?.encodeBase64()}, y=${y?.encodeBase64()}, d=${d?.encodeBase64()})"
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package at.asitplus.wallet.lib.cbor

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

@Serializable(with = CoseKeyOperationSerializer::class)
enum class CoseKeyOperation(val value: Int) {

SIGN(1),
VERIFY(2),
ENCRYPT(3),
DECRYPT(4),
WRAP_KEY(5),
UNWRAP_KEY(6),
DERIVE_KEY(7),
DERIVE_BITS(8),
MAC_CREATE(9),
MAC_VERIFY(10);
}


object CoseKeyOperationSerializer : KSerializer<CoseKeyOperation> {

override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("CoseKeyOperationSerializer", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: CoseKeyOperation) {
value.let { encoder.encodeInt(it.value) }
}

override fun deserialize(decoder: Decoder): CoseKeyOperation {
val decoded = decoder.decodeInt()
return CoseKeyOperation.values().first { it.value == decoded }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package at.asitplus.wallet.lib.cbor

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

@Serializable(with = CoseKeyTypeSerializer::class)
enum class CoseKeyType(val value: Int) {

OKP(1),
EC2(2),
SYMMETRIC(4),
RESERVED(0);

}

object CoseKeyTypeSerializer : KSerializer<CoseKeyType> {

override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("CoseKeyTypeSerializer", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: CoseKeyType) {
value.let { encoder.encodeInt(it.value) }
}

override fun deserialize(decoder: Decoder): CoseKeyType {
val decoded = decoder.decodeInt()
return CoseKeyType.values().firstOrNull { it.value == decoded }
?: throw IllegalArgumentException("Not known: $decoded")
}
}
Loading

0 comments on commit 44a7d09

Please sign in to comment.