Skip to content

Commit 329cbd1

Browse files
misc: merge from main
2 parents 99dd2a6 + 5fc36ff commit 329cbd1

File tree

3 files changed

+79
-1
lines changed

3 files changed

+79
-1
lines changed

runtime/runtime-core/api/runtime-core.api

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,10 @@ public final class aws/smithy/kotlin/runtime/hashing/EcdsaJVMKt {
729729
public static final fun ecdsaSecp256r1 ([B[B)[B
730730
}
731731

732+
public final class aws/smithy/kotlin/runtime/hashing/EcdsaKt {
733+
public static final fun ecdsaSecp256r1Rs ([B[B)[B
734+
}
735+
732736
public abstract interface class aws/smithy/kotlin/runtime/hashing/HashFunction {
733737
public abstract fun digest ()[B
734738
public abstract fun getBlockSizeBytes ()I

runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/hashing/Ecdsa.kt

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,41 @@
55
package aws.smithy.kotlin.runtime.hashing
66

77
/**
8-
* ECDSA on the SECP256R1 curve.
8+
* ECDSA on the SECP256R1 curve returning ASN.1 DER format.
99
*/
1010
public expect fun ecdsaSecp256r1(key: ByteArray, message: ByteArray): ByteArray
11+
12+
/**
13+
* ECDSA on the SECP256R1 curve returning raw r||s format.
14+
*/
15+
public fun ecdsaSecp256r1Rs(key: ByteArray, message: ByteArray): ByteArray {
16+
val derSignature = ecdsaSecp256r1(key, message)
17+
return parseDerSignature(derSignature)
18+
}
19+
20+
/**
21+
* Parses an ASN.1 DER encoded ECDSA signature and converts it to raw r||s format.
22+
*/
23+
private fun parseDerSignature(derSignature: ByteArray): ByteArray {
24+
var index = 2 // Skip SEQUENCE tag and length
25+
26+
// Read r
27+
index++ // Skip INTEGER tag
28+
val rLength = derSignature[index++].toInt() and 0xFF
29+
val r = derSignature.sliceArray(index until index + rLength)
30+
index += rLength
31+
32+
// Read s
33+
index++ // Skip INTEGER tag
34+
val sLength = derSignature[index++].toInt() and 0xFF
35+
val s = derSignature.sliceArray(index until index + sLength)
36+
37+
// Remove leading zero bytes and pad to 32 bytes
38+
val rFixed = r.dropWhile { it == 0.toByte() }.toByteArray()
39+
val sFixed = s.dropWhile { it == 0.toByte() }.toByteArray()
40+
41+
val rPadded = if (rFixed.size < 32) ByteArray(32 - rFixed.size) + rFixed else rFixed
42+
val sPadded = if (sFixed.size < 32) ByteArray(32 - sFixed.size) + sFixed else sFixed
43+
44+
return rPadded + sPadded
45+
}

runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/hashing/EcdsaJVMTest.kt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package aws.smithy.kotlin.runtime.hashing
66

77
import org.junit.jupiter.api.Assertions.assertFalse
88
import org.junit.jupiter.api.Assertions.assertTrue
9+
import org.junit.jupiter.api.condition.EnabledForJreRange
10+
import org.junit.jupiter.api.condition.JRE
911
import java.security.*
1012
import java.security.interfaces.*
1113
import java.security.spec.*
@@ -30,6 +32,10 @@ class EcdsaJVMTest {
3032

3133
assertTrue(signature.isNotEmpty())
3234
assertTrue(signature.size >= 64) // ECDSA signatures are typically 70-72 bytes in DER format
35+
36+
val rawSignature = ecdsaSecp256r1Rs(privateKey, message)
37+
assertTrue(signature.isNotEmpty())
38+
assertTrue(rawSignature.size == 64)
3339
}
3440

3541
@Test
@@ -44,6 +50,13 @@ class EcdsaJVMTest {
4450
assertTrue(signature1.isNotEmpty())
4551
assertTrue(signature2.isNotEmpty())
4652
assertFalse(signature1.contentEquals(signature2))
53+
54+
val rawSignature1 = ecdsaSecp256r1Rs(privateKey, message1)
55+
val rawSignature2 = ecdsaSecp256r1Rs(privateKey, message2)
56+
57+
assertTrue(rawSignature1.isNotEmpty())
58+
assertTrue(rawSignature2.isNotEmpty())
59+
assertFalse(rawSignature1.contentEquals(rawSignature2))
4760
}
4861

4962
@Test
@@ -53,6 +66,9 @@ class EcdsaJVMTest {
5366

5467
val signature = ecdsaSecp256r1(privateKey, message)
5568
assertTrue(signature.isNotEmpty())
69+
70+
val rawSignature = ecdsaSecp256r1Rs(privateKey, message)
71+
assertTrue(rawSignature.isNotEmpty())
5672
}
5773

5874
@Test
@@ -62,6 +78,9 @@ class EcdsaJVMTest {
6278

6379
val signature = ecdsaSecp256r1(privateKey, largeMessage)
6480
assertTrue(signature.isNotEmpty())
81+
82+
val rawSignature = ecdsaSecp256r1Rs(privateKey, largeMessage)
83+
assertTrue(rawSignature.isNotEmpty())
6584
}
6685

6786
@Test
@@ -81,4 +100,24 @@ class EcdsaJVMTest {
81100

82101
assertTrue(verifier.verify(signature))
83102
}
103+
104+
@Test
105+
// SHA256withECDSAinP1363Format algorithm was introduced in Java 11, skip on Java 8
106+
@EnabledForJreRange(min = JRE.JAVA_11)
107+
fun testVerifyRawSignature() {
108+
val keyGen = KeyPairGenerator.getInstance("EC")
109+
keyGen.initialize(ECGenParameterSpec("secp256r1"))
110+
val keyPair = keyGen.generateKeyPair()
111+
val privateKey = (keyPair.private as ECPrivateKey).s.toByteArray()
112+
val publicKey = keyPair.public
113+
114+
val message = "Hello, World!".toByteArray()
115+
val signature = ecdsaSecp256r1Rs(privateKey, message)
116+
117+
val verifier = Signature.getInstance("SHA256withECDSAinP1363Format")
118+
verifier.initVerify(publicKey)
119+
verifier.update(message)
120+
121+
assertTrue(verifier.verify(signature))
122+
}
84123
}

0 commit comments

Comments
 (0)