Skip to content

Commit 7ff81a2

Browse files
authored
Throw InvalidKeyException if javax.crypto.Mac.init is invoked (#43)
1 parent f969802 commit 7ff81a2

File tree

4 files changed

+132
-50
lines changed

4 files changed

+132
-50
lines changed

library/mac/src/commonTest/kotlin/org/kotlincrypto/core/MacUnitTest.kt

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -22,55 +22,6 @@ import kotlin.test.fail
2222

2323
class MacUnitTest {
2424

25-
@Suppress("UnnecessaryOptInAnnotation")
26-
@OptIn(InternalKotlinCryptoApi::class)
27-
private class TestMac : Mac {
28-
29-
constructor(
30-
key: ByteArray,
31-
algorithm: String,
32-
reset: () -> Unit = {},
33-
doFinal: () -> ByteArray = { ByteArray(0) },
34-
): super(algorithm, TestEngine(key, reset, doFinal))
35-
36-
private constructor(algorithm: String, engine: TestEngine): super(algorithm, engine)
37-
38-
override fun copy(engineCopy: Engine): Mac = TestMac(algorithm(), engineCopy as TestEngine)
39-
40-
private class TestEngine: Engine {
41-
42-
private val reset: () -> Unit
43-
private val doFinal: () -> ByteArray
44-
45-
constructor(
46-
key: ByteArray,
47-
reset: () -> Unit,
48-
doFinal: () -> ByteArray,
49-
): super(key) {
50-
this.reset = reset
51-
this.doFinal = doFinal
52-
}
53-
54-
private constructor(state: State, engine: TestEngine): super(state) {
55-
this.reset = engine.reset
56-
this.doFinal = engine.doFinal
57-
}
58-
59-
// To ensure that Java implementation initializes javax.crypto.Mac
60-
// on instantiation so that it does not throw IllegalStateException
61-
// whenever updating.
62-
override fun update(input: Byte) { throw ConcurrentModificationException() }
63-
64-
override fun reset() { reset.invoke() }
65-
override fun update(input: ByteArray) {}
66-
override fun update(input: ByteArray, offset: Int, len: Int) {}
67-
override fun macLength(): Int = 0
68-
override fun doFinal(): ByteArray = doFinal.invoke()
69-
70-
override fun copy(): Engine = TestEngine(object : State() {}, this)
71-
}
72-
}
73-
7425
@Test
7526
fun givenMac_whenEmptyKey_thenThrowsException() {
7627
try {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c) 2023 Matthew Nelson
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
**/
16+
package org.kotlincrypto.core
17+
18+
@Suppress("UnnecessaryOptInAnnotation")
19+
@OptIn(InternalKotlinCryptoApi::class)
20+
class TestMac : Mac {
21+
22+
constructor(
23+
key: ByteArray,
24+
algorithm: String,
25+
reset: () -> Unit = {},
26+
doFinal: () -> ByteArray = { ByteArray(0) },
27+
): super(algorithm, TestEngine(key, reset, doFinal))
28+
29+
private constructor(algorithm: String, engine: TestEngine): super(algorithm, engine)
30+
31+
override fun copy(engineCopy: Engine): Mac = TestMac(algorithm(), engineCopy as TestEngine)
32+
33+
private class TestEngine: Engine {
34+
35+
private val reset: () -> Unit
36+
private val doFinal: () -> ByteArray
37+
38+
constructor(
39+
key: ByteArray,
40+
reset: () -> Unit,
41+
doFinal: () -> ByteArray,
42+
): super(key) {
43+
this.reset = reset
44+
this.doFinal = doFinal
45+
}
46+
47+
private constructor(state: State, engine: TestEngine): super(state) {
48+
this.reset = engine.reset
49+
this.doFinal = engine.doFinal
50+
}
51+
52+
// To ensure that Java implementation initializes javax.crypto.Mac
53+
// on instantiation so that it does not throw IllegalStateException
54+
// whenever updating.
55+
override fun update(input: Byte) { throw ConcurrentModificationException() }
56+
57+
override fun reset() { reset.invoke() }
58+
override fun update(input: ByteArray) {}
59+
override fun update(input: ByteArray, offset: Int, len: Int) {}
60+
override fun macLength(): Int = 0
61+
override fun doFinal(): ByteArray = doFinal.invoke()
62+
63+
override fun copy(): Engine = TestEngine(object : State() {}, this)
64+
}
65+
}

library/mac/src/jvmMain/kotlin/org/kotlincrypto/core/Mac.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package org.kotlincrypto.core
1818
import org.kotlincrypto.core.internal.commonInit
1919
import org.kotlincrypto.core.internal.commonToString
2020
import java.nio.ByteBuffer
21+
import java.security.InvalidKeyException
2122
import java.security.Key
2223
import java.security.spec.AlgorithmParameterSpec
2324
import javax.crypto.MacSpi
@@ -83,6 +84,7 @@ protected actual constructor(
8384
protected actual abstract class Engine: MacSpi, Cloneable, Copyable<Engine>, Resettable, Updatable {
8485

8586
private val hashCode: Int = Any().hashCode()
87+
private var isInitialized = false
8688

8789
/**
8890
* Initializes a new [Engine] with the provided [key].
@@ -115,7 +117,25 @@ protected actual constructor(
115117
}
116118
protected final override fun engineReset() { reset() }
117119
protected final override fun engineGetMacLength(): Int = macLength()
118-
protected final override fun engineInit(p0: Key?, p1: AlgorithmParameterSpec?) { /* no-op */ }
120+
121+
// Is called immediately from Mac init block with a blanked key (required in order to set
122+
// javax.crypto.Mac.initialized to true.
123+
protected final override fun engineInit(p0: Key?, p1: AlgorithmParameterSpec?) {
124+
if (isInitialized) {
125+
126+
// Throw an exception b/c if caller is trying to re-init the javax.crypto.Mac with a new key,
127+
// the normal behavior is to blank the Spi state. If they do not know this is an issue,
128+
// any output would not be correct b/c implementations do not re-init. KotlinCrypto users
129+
// already know it's initialized because the API is designed to require the key upon instantiation
130+
// so init is never needed to be called.
131+
throw InvalidKeyException(
132+
"org.kotlincrypto.Mac does not support re-initialization " +
133+
"(it's already initialized). A new instance is required to be created."
134+
)
135+
}
136+
137+
isInitialized = true
138+
}
119139
protected final override fun engineDoFinal(): ByteArray = doFinal()
120140

121141
public final override fun clone(): Any = copy()
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2023 Matthew Nelson
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
**/
16+
package org.kotlincrypto.core
17+
18+
import java.security.InvalidKeyException
19+
import javax.crypto.spec.SecretKeySpec
20+
import kotlin.test.Test
21+
import kotlin.test.fail
22+
23+
class JvmMacUnitTest {
24+
25+
private val key = ByteArray(20) { it.toByte() }
26+
27+
@Test
28+
fun givenJvm_whenJavaxCryptoMacInitInvoked_thenThrowsException() {
29+
val mac = TestMac(key, "My Algorithm")
30+
val keySpec = SecretKeySpec(key, mac.algorithm())
31+
32+
try {
33+
mac.init(keySpec)
34+
fail()
35+
} catch (_: InvalidKeyException) {
36+
// pass
37+
}
38+
39+
try {
40+
mac.copy().init(keySpec)
41+
fail()
42+
} catch (_: InvalidKeyException) {
43+
// pass
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)