Skip to content

Commit

Permalink
Start with support of RSA keys and algorithms, adding TODOs
Browse files Browse the repository at this point in the history
  • Loading branch information
nodh committed Sep 21, 2023
1 parent d1ebc94 commit 5312d84
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,75 @@ sealed class CryptoPublicKey {
abstract fun toCoseKey(): CoseKey
abstract fun toJsonWebKey(): JsonWebKey

data class Rsa(
val keyId: String,
val n: ByteArray,
val e: ByteArray,
) : CryptoPublicKey() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as Rsa

if (keyId != other.keyId) return false
if (!n.contentEquals(other.n)) return false
if (!e.contentEquals(other.e)) return false

return true
}

override fun hashCode(): Int {
var result = keyId.hashCode()
result = 31 * result + n.contentHashCode()
result = 31 * result + e.contentHashCode()
return result
}

companion object {

fun fromKeyId(it: String): CryptoPublicKey? {
// TODO RSA
return null
}

fun fromAnsiX963Bytes(it: ByteArray): CryptoPublicKey? {
// TODO RSA
return null
}

fun fromModulus(n: ByteArray, e: ByteArray): CryptoPublicKey {
// TODO RSA
return CryptoPublicKey.Rsa(
keyId = "TODO",
n = n,
e = e
)
}
}

fun toAnsiX963ByteArray(): KmmResult<ByteArray> {
// TODO RSA
return KmmResult.success(byteArrayOf())
}

override fun toCoseKey() = CoseKey(
type = CoseKeyType.RSA,
keyId = keyId.encodeToByteArray(),
algorithm = CoseAlgorithm.ES256,
x = n,
y = e
// TODO RSA
)

override fun toJsonWebKey() = JsonWebKey(
type = JwkType.RSA,
keyId = keyId,
n = n,
e = e,
)
}

data class Ec(
val curve: EcCurve,
val keyId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fun commonName(block: () -> String) = oid { "550403" } + utf8String { block() }

fun subjectPublicKey(block: () -> CryptoPublicKey) = when (val value = block()) {
is CryptoPublicKey.Ec -> value.encodeToDer()
is CryptoPublicKey.Rsa -> TODO()
}

fun utcTime(block: () -> Instant): ByteArray {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import kotlinx.serialization.encoding.Encoder
@Serializable(with = CoseAlgorithmSerializer::class)
enum class CoseAlgorithm(val value: Int) {

// TODO RSA add at least algorithm with SHA256
ES256(-7),
ES384(-35),
ES512(-36),
Expand Down
24 changes: 4 additions & 20 deletions vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseKey.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ data class CoseKey(
val baseIv: ByteArray? = null,
@SerialLabel(-1)
@SerialName("crv")
// TODO RSA this may also be the RSA modulus n, see https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters
val curve: CoseEllipticCurve? = null,
@SerialLabel(-2)
@SerialName("x")
// TODO RSA this may also be the RSA exponent e, see https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters
val x: ByteArray? = null,
@SerialLabel(-3)
@SerialName("y") // TODO might also be bool
Expand All @@ -66,6 +68,7 @@ data class CoseKey(
}

fun fromAnsiX963Bytes(type: CoseKeyType, curve: CoseEllipticCurve, it: ByteArray): CoseKey? {
// TODO RSA
if (type != CoseKeyType.EC2 || curve != CoseEllipticCurve.P256) {
return null
}
Expand All @@ -86,26 +89,6 @@ data class CoseKey(
)
}

fun fromCoordinates(
type: CoseKeyType,
curve: CoseEllipticCurve,
x: ByteArray,
y: ByteArray
): CoseKey? {
if (type != CoseKeyType.EC2 || curve != CoseEllipticCurve.P256) {
return null
}
val keyId = MultibaseHelper.calcKeyId(curve, x, y)
?: return null
return CoseKey(
type = type,
keyId = keyId.encodeToByteArray(),
algorithm = CoseAlgorithm.ES256,
curve = curve,
x = x,
y = y
)
}
}

override fun toString(): String {
Expand Down Expand Up @@ -171,6 +154,7 @@ data class CoseKey(
}

fun toCryptoPublicKey(): CryptoPublicKey? {
// TODO RSA
if (this.type != CoseKeyType.EC2 || this.curve == null || this.keyId == null || this.x == null || this.y == null) return null
return CryptoPublicKey.Ec(
curve = curve.toJwkCurve(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ enum class CoseKeyType(val value: Int) {

OKP(1),
EC2(2),
// TODO RSA check usages of other enum members
RSA(3),
SYMMETRIC(4),
RESERVED(0);

Expand Down
108 changes: 71 additions & 37 deletions vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JsonWebKey.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,39 +26,15 @@ data class JsonWebKey(
@SerialName("y")
@Serializable(with = ByteArrayBase64UrlSerializer::class)
val y: ByteArray? = null,
@SerialName("n")
@Serializable(with = ByteArrayBase64UrlSerializer::class)
val n: ByteArray? = null,
@SerialName("e")
@Serializable(with = ByteArrayBase64UrlSerializer::class)
val e: ByteArray? = null,
) {
fun serialize() = jsonSerializer.encodeToString(this)

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

other as JsonWebKey

if (type != other.type) return false
if (curve != other.curve) return false
if (keyId != other.keyId) 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

return true
}

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

companion object {

fun deserialize(it: String) = kotlin.runCatching {
Expand All @@ -69,6 +45,7 @@ data class JsonWebKey(
}

fun fromKeyId(it: String): JsonWebKey? {
// TODO RSA
val (xCoordinate, yCoordinate) = MultibaseHelper.calcPublicKey(it)
?: return null
return JsonWebKey(
Expand All @@ -81,6 +58,7 @@ data class JsonWebKey(
}

fun fromAnsiX963Bytes(type: JwkType, curve: EcCurve, it: ByteArray): JsonWebKey? {
// TODO RSA
if (type != JwkType.EC || curve != EcCurve.SECP_256_R_1) {
return null
}
Expand All @@ -106,6 +84,7 @@ data class JsonWebKey(
x: ByteArray,
y: ByteArray
): JsonWebKey? {
// TODO RSA
if (type != JwkType.EC || curve != EcCurve.SECP_256_R_1) {
return null
}
Expand Down Expand Up @@ -142,13 +121,68 @@ data class JsonWebKey(
}

fun toCryptoPublicKey(): CryptoPublicKey? {
if (this.type != JwkType.EC || this.curve == null || this.x == null || this.y == null) return null
return CryptoPublicKey.Ec(
curve = this.curve,
keyId = identifier,
x = x,
y = y,
)
return when (this.type) {
JwkType.EC -> {
if (this.curve == null || this.x == null || this.y == null) return null
CryptoPublicKey.Ec(
curve = this.curve,
keyId = identifier,
x = x,
y = y,
)
}

JwkType.RSA -> {
if (this.n == null || this.e == null) return null
CryptoPublicKey.Rsa(
keyId = identifier,
n = n,
e = e
)
}

null -> null
}
}

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

other as JsonWebKey

if (curve != other.curve) return false
if (type != other.type) return false
if (keyId != other.keyId) 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 (n != null) {
if (other.n == null) return false
if (!n.contentEquals(other.n)) return false
} else if (other.n != null) return false
if (e != null) {
if (other.e == null) return false
if (!e.contentEquals(other.e)) return false
} else if (other.e != null) return false

return true
}

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


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable
@Serializable(with = JwsAlgorithmSerializer::class)
enum class JwsAlgorithm(val text: String) {

// TODO RSA Add RSA algorithms with SHA256 at least
ES256("ES256"),
ES384("ES384"),
ES512("ES512"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ object JwsExtensions {
* JWS spec concatenates the R and S values,
* but JCA needs an ASN.1 structure (SEQUENCE of two INTEGER) around it
*/
// TODO RSA
fun ByteArray.convertToAsn1Signature(len: Int): ByteArray = if (size == len * 2) {
val rValue = sliceArray(0 until len).toAsn1Integer()
val sValue = sliceArray(len until len * 2).toAsn1Integer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.matthewnelson.encoding.base64.Base64
import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArrayOrNull
import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString


// TODO RSA Support RSA-Publickey with Prefix 0x1205, then DER-encoded ASN.1 type RSAPublicKey in PKCS#1/RFC8017
object MultibaseHelper {

private const val PREFIX_DID_KEY = "did:key"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ actual class DefaultCryptoService : CryptoService {
publicKey = SecKeyCopyPublicKey(privateKey)!!
val publicKeyData = SecKeyCopyExternalRepresentation(publicKey, null)
val data = CFBridgingRelease(publicKeyData) as NSData
// TODO RSA
this.cryptoPublicKey = CryptoPublicKey.Ec.fromAnsiX963Bytes(EcCurve.SECP_256_R_1, data.toByteArray())!!
val tbsCertificate = TbsCertificate(
version = 2,
Expand Down Expand Up @@ -173,6 +174,7 @@ actual class DefaultVerifierCryptoService : VerifierCryptoService {
algorithm: JwsAlgorithm,
publicKey: CryptoPublicKey
): KmmResult<Boolean> {
// TODO RSA
if (publicKey !is CryptoPublicKey.Ec) {
return KmmResult.failure(IllegalArgumentException("Public key is not an EC key"))
}
Expand Down Expand Up @@ -209,6 +211,7 @@ actual object CryptoUtils {
actual fun extractPublicKeyFromX509Cert(it: ByteArray): CryptoPublicKey? {
if (it.isEmpty()) return null
memScoped {
// TODO RSA
val certData = CFBridgingRetain(toData(it)) as CFDataRef
val certificate = SecCertificateCreateWithData(null, certData)
val publicKey = SecCertificateCopyKey(certificate)
Expand Down
Loading

0 comments on commit 5312d84

Please sign in to comment.