Skip to content

Commit e102d2b

Browse files
committed
Add tests for streaming ciphers, signatures and digests
1 parent 9858150 commit e102d2b

File tree

13 files changed

+386
-10
lines changed

13 files changed

+386
-10
lines changed

build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ abstract class GenerateProviderTestsTask : DefaultTask() {
8787

8888
"AesCbcTest",
8989
"AesCbcCompatibilityTest",
90+
"AesCtrTest",
9091
"AesCtrCompatibilityTest",
9192
"AesEcbCompatibilityTest",
9293
"AesGcmTest",

cryptography-providers-tests-api/src/commonMain/kotlin/utils.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99

1010
import dev.whyoleg.cryptography.materials.key.*
11+
import kotlinx.io.*
1112
import kotlinx.io.bytestring.*
1213
import kotlin.io.encoding.*
1314
import kotlin.test.*
@@ -64,6 +65,11 @@ fun digest(name: String): CryptographyAlgorithmId<Digest> = when (name) {
6465
else -> error("Unknown digest: $name")
6566
}
6667

68+
fun Buffer(bytes: ByteString): Buffer = Buffer().apply { write(bytes) }
69+
70+
fun Buffer.bufferedSource(): Source = (this as RawSource).buffered()
71+
fun Buffer.bufferedSink(): Sink = (this as RawSink).buffered()
72+
6773
expect fun disableJsConsoleDebug()
6874

6975
// Wasm tests on browser cannot be filtered: https://youtrack.jetbrains.com/issue/KT-58291

cryptography-providers-tests/src/commonMain/kotlin/default/AesBasedTest.kt

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ package dev.whyoleg.cryptography.providers.tests.default
77
import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.providers.tests.api.*
10+
import dev.whyoleg.cryptography.random.*
11+
import kotlinx.io.*
12+
import kotlinx.io.bytestring.*
1013

1114
abstract class AesBasedTest<A : AES<*>>(
1215
private val algorithmId: CryptographyAlgorithmId<A>,
1316
provider: CryptographyProvider,
14-
) : ProviderTest(provider) {
17+
) : ProviderTest(provider), CipherTest {
1518

1619
protected inner class AesTestScope(
1720
logger: TestLogger,
@@ -28,4 +31,40 @@ abstract class AesBasedTest<A : AES<*>>(
2831
block(AesTestScope(logger, context, provider, algorithm, keySize))
2932
}
3033
}
34+
35+
suspend fun AlgorithmTestScope<*>.assertCipherWithIvViaFunction(
36+
encryptor: AES.IvEncryptor,
37+
decryptor: AES.IvDecryptor,
38+
ivSize: Int,
39+
plaintext: ByteString,
40+
) {
41+
val iv = ByteString(CryptographyRandom.nextBytes(ivSize))
42+
listOf(
43+
encryptor.resetIv(context).encryptWithIv(iv, plaintext),
44+
encryptor.resetIv(context).encryptingSourceWithIv(iv, Buffer(plaintext).bufferedSource()).buffered()
45+
.use { it.readByteString() },
46+
Buffer().also { output ->
47+
encryptor.resetIv(context).encryptingSinkWithIv(iv, output.bufferedSink()).buffered().use { it.write(plaintext) }
48+
}.readByteString(),
49+
).forEach { ciphertext ->
50+
assertContentEquals(plaintext, decryptor.decryptWithIv(iv, ciphertext))
51+
assertContentEquals(
52+
plaintext,
53+
decryptor.decryptingSourceWithIv(iv, Buffer(ciphertext).bufferedSource()).buffered().use { it.readByteString() }
54+
)
55+
assertContentEquals(
56+
plaintext,
57+
Buffer().also { output ->
58+
decryptor.decryptingSinkWithIv(iv, output.bufferedSink()).buffered().use { it.write(ciphertext) }
59+
}.readByteString()
60+
)
61+
}
62+
}
63+
}
64+
65+
// GCM mode on JDK has a check which tries to prevent reuse of the same IV with the same key.
66+
// we need to set random IV first to be able to reuse IV for different plaintext for the same key
67+
private suspend fun AES.IvEncryptor.resetIv(context: TestContext): AES.IvEncryptor {
68+
if (context.provider.isJdk && this is AES.IvAuthenticatedEncryptor) encrypt(ByteString())
69+
return this
3170
}

cryptography-providers-tests/src/commonMain/kotlin/default/AesCbcTest.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.providers.tests.api.*
1010
import dev.whyoleg.cryptography.random.*
11+
import kotlinx.io.bytestring.*
1112
import kotlin.test.*
1213

1314
private const val blockSize = 16
@@ -90,4 +91,30 @@ abstract class AesCbcTest(provider: CryptographyProvider) : AesBasedTest<AES.CBC
9091
assertNotEquals(data, it)
9192
}
9293
}
94+
95+
@Test
96+
fun testFunctions() = runTestForEachKeySize {
97+
if (!supportsFunctions()) return@runTestForEachKeySize
98+
99+
val key = algorithm.keyGenerator(keySize).generateKey()
100+
val cipher = key.cipher()
101+
repeat(100) {
102+
val size = CryptographyRandom.nextInt(20000)
103+
val data = ByteString(CryptographyRandom.nextBytes(size))
104+
assertCipherViaFunction(cipher, cipher, data)
105+
}
106+
}
107+
108+
@Test
109+
fun testFunctionsWithIv() = runTestForEachKeySize {
110+
if (!supportsFunctions()) return@runTestForEachKeySize
111+
112+
val key = algorithm.keyGenerator(keySize).generateKey()
113+
val cipher = key.cipher()
114+
repeat(100) {
115+
val size = CryptographyRandom.nextInt(20000)
116+
val data = ByteString(CryptographyRandom.nextBytes(size))
117+
assertCipherWithIvViaFunction(cipher, cipher, ivSize, data)
118+
}
119+
}
93120
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2024 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.whyoleg.cryptography.providers.tests.default
6+
7+
import dev.whyoleg.cryptography.*
8+
import dev.whyoleg.cryptography.algorithms.*
9+
import dev.whyoleg.cryptography.providers.tests.api.*
10+
import dev.whyoleg.cryptography.random.*
11+
import kotlinx.io.bytestring.*
12+
import kotlin.test.*
13+
14+
private const val ivSize = 16
15+
16+
abstract class AesCtrTest(provider: CryptographyProvider) : AesBasedTest<AES.CTR>(AES.CTR, provider) {
17+
@Test
18+
fun testFunctions() = runTestForEachKeySize {
19+
if (!supportsFunctions()) return@runTestForEachKeySize
20+
21+
val key = algorithm.keyGenerator(keySize).generateKey()
22+
val cipher = key.cipher()
23+
repeat(100) {
24+
val size = CryptographyRandom.nextInt(20000)
25+
val data = ByteString(CryptographyRandom.nextBytes(size))
26+
assertCipherViaFunction(cipher, cipher, data)
27+
}
28+
}
29+
30+
@Test
31+
fun testFunctionsWithIv() = runTestForEachKeySize {
32+
if (!supportsFunctions()) return@runTestForEachKeySize
33+
34+
val key = algorithm.keyGenerator(keySize).generateKey()
35+
val cipher = key.cipher()
36+
repeat(100) {
37+
val size = CryptographyRandom.nextInt(20000)
38+
val data = ByteString(CryptographyRandom.nextBytes(size))
39+
assertCipherWithIvViaFunction(cipher, cipher, ivSize, data)
40+
}
41+
}
42+
}

cryptography-providers-tests/src/commonMain/kotlin/default/AesGcmTest.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ package dev.whyoleg.cryptography.providers.tests.default
77
import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.BinarySize.Companion.bits
99
import dev.whyoleg.cryptography.algorithms.*
10+
import dev.whyoleg.cryptography.providers.tests.api.*
1011
import dev.whyoleg.cryptography.random.*
12+
import kotlinx.io.bytestring.*
1113
import kotlin.test.*
1214

1315
private const val ivSize = 12
@@ -51,4 +53,34 @@ abstract class AesGcmTest(provider: CryptographyProvider) : AesBasedTest<AES.GCM
5153

5254
assertFails { wrongKey.cipher().decrypt(ciphertext) }
5355
}
56+
57+
@Test
58+
fun testFunctions() = runTestForEachKeySize {
59+
if (!supportsFunctions()) return@runTestForEachKeySize
60+
61+
val key = algorithm.keyGenerator(keySize).generateKey()
62+
listOf(96, 104, 112, 120, 128).forEach { tagSizeBits ->
63+
val cipher = key.cipher(tagSizeBits.bits)
64+
repeat(100) {
65+
val size = CryptographyRandom.nextInt(20000)
66+
val data = ByteString(CryptographyRandom.nextBytes(size))
67+
assertCipherViaFunction(cipher, cipher, data)
68+
}
69+
}
70+
}
71+
72+
@Test
73+
fun testFunctionsWithIv() = runTestForEachKeySize {
74+
if (!supportsFunctions()) return@runTestForEachKeySize
75+
76+
val key = algorithm.keyGenerator(keySize).generateKey()
77+
listOf(96, 104, 112, 120, 128).forEach { tagSizeBits ->
78+
val cipher = key.cipher(tagSizeBits.bits)
79+
repeat(100) {
80+
val size = CryptographyRandom.nextInt(20000)
81+
val data = ByteString(CryptographyRandom.nextBytes(size))
82+
assertCipherWithIvViaFunction(cipher, cipher, ivSize, data)
83+
}
84+
}
85+
}
5486
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) 2024 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.whyoleg.cryptography.providers.tests.default
6+
7+
import dev.whyoleg.cryptography.operations.*
8+
import dev.whyoleg.cryptography.providers.tests.api.*
9+
import kotlinx.io.*
10+
import kotlinx.io.bytestring.*
11+
12+
interface CipherTest {
13+
suspend fun AlgorithmTestScope<*>.assertCipherViaFunction(
14+
encryptor: Encryptor,
15+
decryptor: Decryptor,
16+
plaintext: ByteString,
17+
) {
18+
listOf(
19+
encryptor.encrypt(plaintext),
20+
encryptor.encryptingSource(Buffer(plaintext).bufferedSource()).buffered().use { it.readByteString() },
21+
Buffer().also { output ->
22+
encryptor.encryptingSink(output.bufferedSink()).buffered().use { it.write(plaintext) }
23+
}.readByteString(),
24+
).forEach { ciphertext ->
25+
assertContentEquals(plaintext, decryptor.decrypt(ciphertext))
26+
assertContentEquals(
27+
plaintext,
28+
decryptor.decryptingSource(Buffer(ciphertext).bufferedSource()).buffered().use { it.readByteString() }
29+
)
30+
assertContentEquals(
31+
plaintext,
32+
Buffer().also { output ->
33+
decryptor.decryptingSink(output.bufferedSink()).buffered().use { it.write(ciphertext) }
34+
}.readByteString()
35+
)
36+
}
37+
}
38+
}

cryptography-providers-tests/src/commonMain/kotlin/default/DigestTest.kt

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package dev.whyoleg.cryptography.providers.tests.default
66

77
import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
9+
import dev.whyoleg.cryptography.operations.*
910
import dev.whyoleg.cryptography.providers.tests.api.*
1011
import dev.whyoleg.cryptography.random.*
1112
import kotlinx.io.*
@@ -22,11 +23,48 @@ abstract class DigestTest(provider: CryptographyProvider) : ProviderTest(provide
2223
val hasher = algorithm.hasher()
2324
assertEquals(digestSize, hasher.hash(ByteArray(0)).size)
2425
repeat(8) { n ->
25-
val size = 10.0.pow(n).toInt()
26-
val data = CryptographyRandom.nextBytes(size)
27-
val result = hasher.hash(data)
28-
assertEquals(digestSize, result.size)
29-
assertContentEquals(result, hasher.hash(data))
26+
val maxSize = 10.0.pow(n).toInt()
27+
((1..10).map { CryptographyRandom.nextInt(maxSize) } + maxSize).forEach { size ->
28+
val data = ByteString(CryptographyRandom.nextBytes(size))
29+
30+
val digest = hasher.hash(data)
31+
assertEquals(digestSize, digest.size)
32+
assertContentEquals(digest, hasher.hash(data))
33+
if (supportsFunctions()) {
34+
val chunked: UpdateFunction. () -> Unit = {
35+
val steps = 10
36+
var step = data.size / steps
37+
if (step == 0) step = data.size
38+
var start = 0
39+
while (start < data.size) {
40+
update(data, start, minOf(data.size, start + step))
41+
start += step
42+
}
43+
}
44+
val viaSource: UpdateFunction. () -> Unit = {
45+
updatingSource(Buffer(data).bufferedSource()).buffered().use {
46+
assertContentEquals(data, it.readByteString())
47+
}
48+
}
49+
val viaSink: UpdateFunction. () -> Unit = {
50+
val output = Buffer()
51+
updatingSink(output.bufferedSink()).buffered().use { it.write(data) }
52+
assertContentEquals(data, output.readByteString())
53+
}
54+
55+
hasher.createHashFunction().use { function ->
56+
// test 1
57+
chunked(function)
58+
assertContentEquals(digest, function.hash())
59+
// test 2
60+
viaSource(function)
61+
assertContentEquals(digest, function.hash())
62+
// test 3
63+
viaSink(function)
64+
assertContentEquals(digest, function.hash())
65+
}
66+
}
67+
}
3068
}
3169
}
3270

cryptography-providers-tests/src/commonMain/kotlin/default/EcdsaTest.kt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.providers.tests.api.*
1010
import dev.whyoleg.cryptography.random.*
11+
import kotlinx.io.bytestring.*
1112
import kotlin.io.encoding.*
1213
import kotlin.math.*
1314
import kotlin.test.*
1415

15-
abstract class EcdsaTest(provider: CryptographyProvider) : ProviderTest(provider) {
16+
abstract class EcdsaTest(provider: CryptographyProvider) : ProviderTest(provider), SignatureTest {
1617

1718
//all sizes are in bytes
1819
// `privateKeySizes` contains three sizes.
@@ -83,4 +84,35 @@ abstract class EcdsaTest(provider: CryptographyProvider) : ProviderTest(provider
8384
}
8485
}
8586
}
87+
88+
@Test
89+
fun testFunctions() = testAlgorithm(ECDSA) {
90+
if (!supportsFunctions()) return@testAlgorithm
91+
92+
listOf(
93+
EC.Curve.P256,
94+
EC.Curve.P384,
95+
EC.Curve.P521,
96+
EC.Curve("secp256k1"),
97+
).forEach { curve ->
98+
if (!supportsCurve(curve)) return@forEach
99+
100+
val keyPair = algorithm.keyPairGenerator(curve).generateKey()
101+
102+
generateDigests { digest, _ ->
103+
if (!supportsDigest(digest)) return@generateDigests
104+
105+
ECDSA.SignatureFormat.entries.forEach { format ->
106+
val signatureGenerator = keyPair.privateKey.signatureGenerator(digest, format)
107+
val signatureVerifier = keyPair.publicKey.signatureVerifier(digest, format)
108+
109+
repeat(10) {
110+
val size = CryptographyRandom.nextInt(20000)
111+
val data = ByteString(CryptographyRandom.nextBytes(size))
112+
assertSignaturesViaFunction(signatureGenerator, signatureVerifier, data)
113+
}
114+
}
115+
}
116+
}
117+
}
86118
}

cryptography-providers-tests/src/commonMain/kotlin/default/HmacTest.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.providers.tests.api.*
1010
import dev.whyoleg.cryptography.random.*
11+
import kotlinx.io.bytestring.*
1112
import kotlin.math.*
1213
import kotlin.test.*
1314

14-
abstract class HmacTest(provider: CryptographyProvider) : ProviderTest(provider) {
15+
abstract class HmacTest(provider: CryptographyProvider) : ProviderTest(provider), SignatureTest {
1516

1617
private class HmacTestScope(
1718
logger: TestLogger,
@@ -81,4 +82,19 @@ abstract class HmacTest(provider: CryptographyProvider) : ProviderTest(provider)
8182
val signature = key.signatureGenerator().generateSignature(data)
8283
assertFalse(wrongKey.signatureVerifier().tryVerifySignature(data, signature))
8384
}
85+
86+
@Test
87+
fun testFunctions() = runTestForEachDigest {
88+
if (!supportsFunctions()) return@runTestForEachDigest
89+
90+
val key = algorithm.keyGenerator(digest).generateKey()
91+
val signatureGenerator = key.signatureGenerator()
92+
val signatureVerifier = key.signatureVerifier()
93+
94+
repeat(10) {
95+
val size = CryptographyRandom.nextInt(20000)
96+
val data = ByteString(CryptographyRandom.nextBytes(size))
97+
assertSignaturesViaFunction(signatureGenerator, signatureVerifier, data)
98+
}
99+
}
84100
}

0 commit comments

Comments
 (0)