Skip to content

Commit 612b463

Browse files
committed
asn1: Ed25519/Ed448 and X25519/X448 OIDs as ObjectIdentifier extensions; accept ABSENT/NULL params (RFC 8410); tests; minor DER tolerance; update ABI
1 parent 1d0f98f commit 612b463

File tree

7 files changed

+108
-7
lines changed

7 files changed

+108
-7
lines changed

cryptography-serialization/asn1/modules/api/cryptography-serialization-asn1-modules.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ public final class dev/whyoleg/cryptography/serialization/asn1/modules/KeyAlgori
141141
public final fun serializer ()Lkotlinx/serialization/KSerializer;
142142
}
143143

144+
public final class dev/whyoleg/cryptography/serialization/asn1/modules/OidsKt {
145+
public static final fun getEd25519 (Ldev/whyoleg/cryptography/serialization/asn1/ObjectIdentifier$Companion;)Ljava/lang/String;
146+
public static final fun getEd448 (Ldev/whyoleg/cryptography/serialization/asn1/ObjectIdentifier$Companion;)Ljava/lang/String;
147+
public static final fun getX25519 (Ldev/whyoleg/cryptography/serialization/asn1/ObjectIdentifier$Companion;)Ljava/lang/String;
148+
public static final fun getX448 (Ldev/whyoleg/cryptography/serialization/asn1/ObjectIdentifier$Companion;)Ljava/lang/String;
149+
}
150+
144151
public final class dev/whyoleg/cryptography/serialization/asn1/modules/PrivateKeyInfo {
145152
public static final field Companion Ldev/whyoleg/cryptography/serialization/asn1/modules/PrivateKeyInfo$Companion;
146153
public fun <init> (ILdev/whyoleg/cryptography/serialization/asn1/modules/KeyAlgorithmIdentifier;[B)V

cryptography-serialization/asn1/modules/api/cryptography-serialization-asn1-modules.klib.api

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,16 @@ final object dev.whyoleg.cryptography.serialization.asn1.modules/RsaKeyAlgorithm
265265

266266
final val dev.whyoleg.cryptography.serialization.asn1.modules/EC // dev.whyoleg.cryptography.serialization.asn1.modules/EC|@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion{}EC[0]
267267
final fun (dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier.Companion).<get-EC>(): dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier // dev.whyoleg.cryptography.serialization.asn1.modules/EC.<get-EC>|<get-EC>@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion(){}[0]
268+
final val dev.whyoleg.cryptography.serialization.asn1.modules/Ed25519 // dev.whyoleg.cryptography.serialization.asn1.modules/Ed25519|@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion{}Ed25519[0]
269+
final fun (dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier.Companion).<get-Ed25519>(): dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier // dev.whyoleg.cryptography.serialization.asn1.modules/Ed25519.<get-Ed25519>|<get-Ed25519>@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion(){}[0]
270+
final val dev.whyoleg.cryptography.serialization.asn1.modules/Ed448 // dev.whyoleg.cryptography.serialization.asn1.modules/Ed448|@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion{}Ed448[0]
271+
final fun (dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier.Companion).<get-Ed448>(): dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier // dev.whyoleg.cryptography.serialization.asn1.modules/Ed448.<get-Ed448>|<get-Ed448>@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion(){}[0]
268272
final val dev.whyoleg.cryptography.serialization.asn1.modules/RSA // dev.whyoleg.cryptography.serialization.asn1.modules/RSA|@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion{}RSA[0]
269273
final fun (dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier.Companion).<get-RSA>(): dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier // dev.whyoleg.cryptography.serialization.asn1.modules/RSA.<get-RSA>|<get-RSA>@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion(){}[0]
274+
final val dev.whyoleg.cryptography.serialization.asn1.modules/X25519 // dev.whyoleg.cryptography.serialization.asn1.modules/X25519|@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion{}X25519[0]
275+
final fun (dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier.Companion).<get-X25519>(): dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier // dev.whyoleg.cryptography.serialization.asn1.modules/X25519.<get-X25519>|<get-X25519>@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion(){}[0]
276+
final val dev.whyoleg.cryptography.serialization.asn1.modules/X448 // dev.whyoleg.cryptography.serialization.asn1.modules/X448|@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion{}X448[0]
277+
final fun (dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier.Companion).<get-X448>(): dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier // dev.whyoleg.cryptography.serialization.asn1.modules/X448.<get-X448>|<get-X448>@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion(){}[0]
270278
final val dev.whyoleg.cryptography.serialization.asn1.modules/secp256r1 // dev.whyoleg.cryptography.serialization.asn1.modules/secp256r1|@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion{}secp256r1[0]
271279
final fun (dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier.Companion).<get-secp256r1>(): dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier // dev.whyoleg.cryptography.serialization.asn1.modules/secp256r1.<get-secp256r1>|<get-secp256r1>@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion(){}[0]
272280
final val dev.whyoleg.cryptography.serialization.asn1.modules/secp384r1 // dev.whyoleg.cryptography.serialization.asn1.modules/secp384r1|@dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier.Companion{}secp384r1[0]

cryptography-serialization/asn1/modules/src/commonMain/kotlin/AlgorithmIdentifierSerializer.kt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,18 @@ public abstract class AlgorithmIdentifierSerializer<AI : AlgorithmIdentifier> :
4545
index = 0,
4646
deserializer = ObjectIdentifier.serializer()
4747
)
48-
check(decodeElementIndex(descriptor) == 1)
49-
val parameters = decodeParameters(algorithm)
50-
check(decodeElementIndex(descriptor) == CompositeDecoder.DECODE_DONE)
51-
parameters
48+
when (val idx = decodeElementIndex(descriptor)) {
49+
1 -> {
50+
val parameters = decodeParameters(algorithm)
51+
check(decodeElementIndex(descriptor) == CompositeDecoder.DECODE_DONE)
52+
parameters
53+
}
54+
CompositeDecoder.DECODE_DONE -> {
55+
// Some algorithms (e.g., Ed25519/Ed448/X25519/X448 per RFC 8410) omit parameters.
56+
// Delegate to subclass to construct an identifier without consuming parameters from the stream.
57+
decodeParameters(algorithm)
58+
}
59+
else -> error("Unexpected element index: $idx")
60+
}
5261
}
53-
}
62+
}

cryptography-serialization/asn1/modules/src/commonMain/kotlin/KeyAlgorithmIdentifierSerializer.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@ internal object KeyAlgorithmIdentifierSerializer : AlgorithmIdentifierSerializer
2626
}
2727
ObjectIdentifier.EC -> EcKeyAlgorithmIdentifier(decodeParameters(EcParameters.serializer()))
2828
else -> {
29-
// TODO: somehow we should ignore parameters here
29+
// For algorithms like Ed25519/Ed448/X25519/X448 (RFC 8410), parameters MUST be ABSENT.
30+
// Accept both ABSENT and explicit NULL: if the parameters element is present, it should be NULL — consume it; if absent, do not read.
31+
try {
32+
decodeParameters<Nothing>(NothingSerializer())
33+
} catch (_: IllegalStateException) {
34+
// No element to read (ABSENT) — that's valid per RFC 8410.
35+
}
3036
UnknownKeyAlgorithmIdentifier(algorithm)
3137
}
3238
}
33-
}
39+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.whyoleg.cryptography.serialization.asn1.modules
6+
7+
import dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier
8+
9+
public val ObjectIdentifier.Companion.Ed25519: ObjectIdentifier get() = ObjectIdentifier("1.3.101.112")
10+
public val ObjectIdentifier.Companion.Ed448: ObjectIdentifier get() = ObjectIdentifier("1.3.101.113")
11+
12+
public val ObjectIdentifier.Companion.X25519: ObjectIdentifier get() = ObjectIdentifier("1.3.101.110")
13+
public val ObjectIdentifier.Companion.X448: ObjectIdentifier get() = ObjectIdentifier("1.3.101.111")
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.whyoleg.cryptography.serialization.asn1.modules
6+
7+
import dev.whyoleg.cryptography.serialization.asn1.*
8+
import kotlinx.serialization.decodeFromByteArray
9+
import kotlin.test.*
10+
11+
class KeyAlgorithmIdentifierDecodeTest {
12+
13+
private fun String.hexToBytes(): ByteArray {
14+
check(length % 2 == 0) { "Invalid hex length" }
15+
return ByteArray(length / 2) { i ->
16+
val hi = this[i * 2].digitToInt(16)
17+
val lo = this[i * 2 + 1].digitToInt(16)
18+
((hi shl 4) or lo).toByte()
19+
}
20+
}
21+
22+
@Test
23+
fun decode_Ed25519_absentParameters() {
24+
// SEQUENCE { algorithm OBJECT IDENTIFIER 1.3.101.112 } (no parameters element)
25+
val bytes = "300506032B6570".hexToBytes()
26+
val id = Der.decodeFromByteArray<KeyAlgorithmIdentifier>(bytes)
27+
assertTrue(id is UnknownKeyAlgorithmIdentifier)
28+
assertEquals(ObjectIdentifier.Ed25519, id.algorithm)
29+
}
30+
31+
@Test
32+
fun decode_Ed25519_nullParameters() {
33+
// SEQUENCE { algorithm OBJECT IDENTIFIER 1.3.101.112, parameters NULL }
34+
val bytes = "300706032B65700500".hexToBytes()
35+
val id = Der.decodeFromByteArray<KeyAlgorithmIdentifier>(bytes)
36+
assertTrue(id is UnknownKeyAlgorithmIdentifier)
37+
assertEquals(ObjectIdentifier.Ed25519, id.algorithm)
38+
}
39+
40+
@Test
41+
fun decode_X25519_absentParameters() {
42+
// SEQUENCE { algorithm OBJECT IDENTIFIER 1.3.101.110 } (no parameters element)
43+
val bytes = "300506032B656E".hexToBytes()
44+
val id = Der.decodeFromByteArray<KeyAlgorithmIdentifier>(bytes)
45+
assertTrue(id is UnknownKeyAlgorithmIdentifier)
46+
assertEquals(ObjectIdentifier.X25519, id.algorithm)
47+
}
48+
49+
@Test
50+
fun decode_X25519_nullParameters() {
51+
// SEQUENCE { algorithm OBJECT IDENTIFIER 1.3.101.110, parameters NULL }
52+
val bytes = "300706032B656E0500".hexToBytes()
53+
val id = Der.decodeFromByteArray<KeyAlgorithmIdentifier>(bytes)
54+
assertTrue(id is UnknownKeyAlgorithmIdentifier)
55+
assertEquals(ObjectIdentifier.X25519, id.algorithm)
56+
}
57+
}

cryptography-serialization/asn1/src/commonMain/kotlin/internal/DerDecoder.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ internal class DerDecoder(
3636

3737
while (true) {
3838
val index = currentIndex
39+
if (index >= descriptor.elementsCount) return CompositeDecoder.DECODE_DONE
3940
tagOverride = descriptor.getElementContextSpecificTag(index)
4041

4142
if (descriptor.isElementOptional(index)) {

0 commit comments

Comments
 (0)