Skip to content

Commit 8587410

Browse files
authored
Add ability to inhibit Mac.Engine.reset calls whenever doFinal is invoked (#111)
1 parent 1dc7507 commit 8587410

File tree

8 files changed

+166
-16
lines changed

8 files changed

+166
-16
lines changed

library/mac/api/mac.api

+2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ public abstract class org/kotlincrypto/core/mac/Mac : javax/crypto/Mac, org/kotl
1212
}
1313

1414
protected abstract class org/kotlincrypto/core/mac/Mac$Engine : javax/crypto/MacSpi, java/lang/Cloneable, org/kotlincrypto/core/Copyable, org/kotlincrypto/core/Resettable, org/kotlincrypto/core/Updatable {
15+
public final field resetOnDoFinal Z
1516
protected fun <init> (Lorg/kotlincrypto/core/mac/Mac$Engine;)V
1617
public fun <init> ([B)V
18+
public fun <init> ([BZ)V
1719
public final fun clone ()Ljava/lang/Object;
1820
public abstract fun doFinal ()[B
1921
public fun doFinalInto ([BI)V

library/mac/api/mac.klib.api

+4
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ abstract class org.kotlincrypto.core.mac/Mac : org.kotlincrypto.core/Algorithm,
2727

2828
abstract class Engine : org.kotlincrypto.core/Copyable<org.kotlincrypto.core.mac/Mac.Engine>, org.kotlincrypto.core/Resettable, org.kotlincrypto.core/Updatable { // org.kotlincrypto.core.mac/Mac.Engine|null[0]
2929
constructor <init>(kotlin/ByteArray) // org.kotlincrypto.core.mac/Mac.Engine.<init>|<init>(kotlin.ByteArray){}[0]
30+
constructor <init>(kotlin/ByteArray, kotlin/Boolean) // org.kotlincrypto.core.mac/Mac.Engine.<init>|<init>(kotlin.ByteArray;kotlin.Boolean){}[0]
3031
constructor <init>(org.kotlincrypto.core.mac/Mac.Engine) // org.kotlincrypto.core.mac/Mac.Engine.<init>|<init>(org.kotlincrypto.core.mac.Mac.Engine){}[0]
3132

33+
final val resetOnDoFinal // org.kotlincrypto.core.mac/Mac.Engine.resetOnDoFinal|<get-resetOnDoFinal>(){}[0]
34+
final fun <get-resetOnDoFinal>(): kotlin/Boolean // org.kotlincrypto.core.mac/Mac.Engine.resetOnDoFinal.<get-resetOnDoFinal>|<get-resetOnDoFinal>(){}[0]
35+
3236
abstract fun doFinal(): kotlin/ByteArray // org.kotlincrypto.core.mac/Mac.Engine.doFinal|doFinal(){}[0]
3337
abstract fun macLength(): kotlin/Int // org.kotlincrypto.core.mac/Mac.Engine.macLength|macLength(){}[0]
3438
abstract fun reset(kotlin/ByteArray) // org.kotlincrypto.core.mac/Mac.Engine.reset|reset(kotlin.ByteArray){}[0]

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

+30-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.kotlincrypto.core.mac
1919

2020
import org.kotlincrypto.core.*
21+
import kotlin.jvm.JvmField
2122

2223
/**
2324
* Core abstraction for Message Authentication Code implementations.
@@ -141,13 +142,40 @@ public expect abstract class Mac: Algorithm, Copyable<Mac>, Resettable, Updatabl
141142
protected abstract class Engine: Copyable<Engine>, Resettable, Updatable {
142143

143144
/**
144-
* Initializes a new [Engine] with the provided [key].
145+
* Most [Mac.Engine] are backed by a `Digest`, whereby calling [reset] after
146+
* [doFinal] will cause a double reset (because `Digest.digest` does this inherently).
147+
* By setting this value to `false`, [Engine.reset] will **not** be called whenever
148+
* [doFinal] gets invoked.
145149
*
146-
* @throws [IllegalArgumentException] if [key] is empty.
150+
* **NOTE:** Implementations taking ownership of the automatic reset functionality
151+
* by setting this to `false` must ensure that whatever re-initialization steps were
152+
* taken in their [Engine.reset] function body are executed before their [doFinal]
153+
* and [doFinalInto] implementations return.
154+
* */
155+
@JvmField
156+
public val resetOnDoFinal: Boolean
157+
158+
/**
159+
* Initializes a new [Engine] with the provided [key] with the default [resetOnDoFinal]
160+
* value of `true` (i.e. [Engine.reset] will be called automatically after [Engine.doFinal]
161+
* or [Engine.doFinalInto] have been invoked).
162+
*
163+
* @param [key] The key that this [Engine] instance will use to apply its function to
164+
* @throws [IllegalArgumentException] if [key] is empty
147165
* */
148166
@Throws(IllegalArgumentException::class)
149167
public constructor(key: ByteArray)
150168

169+
/**
170+
* Initializes a new [Engine] with the provided [key] and [resetOnDoFinal] configuration.
171+
*
172+
* @param [key] the key that this [Engine] instance will use to apply its function to
173+
* @param [resetOnDoFinal] See [Engine.resetOnDoFinal] documentation
174+
* @throws [IllegalArgumentException] if [key] is empty
175+
* */
176+
@Throws(IllegalArgumentException::class)
177+
public constructor(key: ByteArray, resetOnDoFinal: Boolean)
178+
151179
/**
152180
* Creates a new [Engine] from [other], copying its state.
153181
* */

library/mac/src/commonMain/kotlin/org/kotlincrypto/core/mac/internal/-CommonPlatform.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ internal inline fun Mac.commonClearKey(engineReset: (ByteArray) -> Unit) {
7777
internal inline fun Mac.commonDoFinalInto(
7878
dest: ByteArray,
7979
destOffset: Int,
80+
engineResetOnDoFinal: Boolean,
8081
engineDoFinalInto: (dest: ByteArray, destOffset: Int) -> Unit,
8182
engineReset: () -> Unit,
8283
): Int {
@@ -90,6 +91,6 @@ internal inline fun Mac.commonDoFinalInto(
9091
ShortBufferException("Not enough room in dest for $len bytes")
9192
})
9293
engineDoFinalInto(dest, destOffset)
93-
engineReset()
94+
if (engineResetOnDoFinal) engineReset()
9495
return len
9596
}

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

+37
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,43 @@ class MacUnitTest {
9191
assertEquals(3, resetCount)
9292
}
9393

94+
@Test
95+
fun givenMacEngine_whenResetOnDoFinalFalse_thenEngineResetIsNOTCalled() {
96+
var resetCount = 0
97+
var doFinalCount = 0
98+
val finalExpected = ByteArray(15) { (it + 25).toByte() }
99+
100+
val mac = TestMac(
101+
ByteArray(5),
102+
algorithm = "test resetOnDoFinal false",
103+
macLen = finalExpected.size,
104+
resetOnDoFinal = false,
105+
reset = { resetCount++ },
106+
doFinal = { doFinalCount++; finalExpected },
107+
)
108+
109+
mac.reset()
110+
assertEquals(1, resetCount)
111+
112+
// doFinal
113+
mac.doFinal()
114+
assertEquals(1, doFinalCount)
115+
assertEquals(1, resetCount)
116+
117+
// update & doFinal
118+
mac.doFinal(ByteArray(25))
119+
assertEquals(2, doFinalCount)
120+
assertEquals(1, resetCount)
121+
122+
// doFinalInto
123+
mac.doFinalInto(ByteArray(finalExpected.size), 0)
124+
assertEquals(3, doFinalCount)
125+
assertEquals(1, resetCount)
126+
127+
mac.reset()
128+
assertEquals(2, resetCount)
129+
}
130+
94131
@Test
95132
fun givenMac_whenClearKey_thenSingle0ByteKeyPassedToEngine() {
96133
var zeroKey: ByteArray? = null

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ class TestMac : Mac {
2121
key: ByteArray,
2222
algorithm: String,
2323
macLen: Int = 0,
24+
resetOnDoFinal: Boolean = true,
2425
reset: () -> Unit = {},
2526
rekey: (new: ByteArray) -> Unit = {},
2627
doFinal: () -> ByteArray = { ByteArray(macLen) },
27-
): super(algorithm, TestEngine(key, reset, rekey, doFinal, macLen))
28+
): super(algorithm, TestEngine(key, reset, rekey, doFinal, macLen, resetOnDoFinal))
2829

2930
private constructor(algorithm: String, engine: TestEngine): super(algorithm, engine)
3031
private constructor(other: TestMac): super(other)
@@ -44,7 +45,8 @@ class TestMac : Mac {
4445
rekey: (new: ByteArray) -> Unit,
4546
doFinal: () -> ByteArray,
4647
macLen: Int,
47-
): super(key) {
48+
resetOnDoFinal: Boolean,
49+
): super(key, resetOnDoFinal) {
4850
this.reset = reset
4951
this.rekey = rekey
5052
this.doFinal = doFinal

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

+50-6
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public actual abstract class Mac: javax.crypto.Mac, Algorithm, Copyable<Mac>, Re
116116
public actual fun doFinalInto(dest: ByteArray, destOffset: Int): Int = commonDoFinalInto(
117117
dest = dest,
118118
destOffset = destOffset,
119+
engineResetOnDoFinal = engine.resetOnDoFinal,
119120
engineDoFinalInto = engine::doFinalInto,
120121
engineReset = engine::reset,
121122
)
@@ -150,19 +151,49 @@ public actual abstract class Mac: javax.crypto.Mac, Algorithm, Copyable<Mac>, Re
150151
protected actual abstract class Engine: MacSpi, Cloneable, Copyable<Engine>, Resettable, Updatable {
151152

152153
/**
153-
* Initializes a new [Engine] with the provided [key].
154+
* Most [Mac.Engine] are backed by a `Digest`, whereby calling [reset] after
155+
* [doFinal] will cause a double reset (because `Digest.digest` does this inherently).
156+
* By setting this value to `false`, [Engine.reset] will **not** be called whenever
157+
* [doFinal] gets invoked.
154158
*
155-
* @throws [IllegalArgumentException] if [key] is empty.
159+
* **NOTE:** Implementations taking ownership of the automatic reset functionality
160+
* by setting this to `false` must ensure that whatever re-initialization steps were
161+
* taken in their [Engine.reset] function body are executed before their [doFinal]
162+
* and [doFinalInto] implementations return.
163+
* */
164+
@JvmField
165+
public actual val resetOnDoFinal: Boolean
166+
167+
/**
168+
* Initializes a new [Engine] with the provided [key] with the default [resetOnDoFinal]
169+
* value of `true` (i.e. [Engine.reset] will be called automatically after [Engine.doFinal]
170+
* or [Engine.doFinalInto] have been invoked).
171+
*
172+
* @param [key] The key that this [Engine] instance will use to apply its function to
173+
* @throws [IllegalArgumentException] if [key] is empty
174+
* */
175+
@Throws(IllegalArgumentException::class)
176+
public actual constructor(key: ByteArray): this(key, resetOnDoFinal = true)
177+
178+
/**
179+
* Initializes a new [Engine] with the provided [key] and [resetOnDoFinal] configuration.
180+
*
181+
* @param [key] the key that this [Engine] instance will use to apply its function to
182+
* @param [resetOnDoFinal] See [Engine.resetOnDoFinal] documentation
183+
* @throws [IllegalArgumentException] if [key] is empty
156184
* */
157185
@Throws(IllegalArgumentException::class)
158-
public actual constructor(key: ByteArray) {
186+
public actual constructor(key: ByteArray, resetOnDoFinal: Boolean) {
159187
require(key.isNotEmpty()) { "key cannot be empty" }
188+
this.resetOnDoFinal = resetOnDoFinal
160189
}
161190

162191
/**
163192
* Creates a new [Engine] from [other], copying its state.
164193
* */
165-
protected actual constructor(other: Engine)
194+
protected actual constructor(other: Engine) {
195+
this.resetOnDoFinal = other.resetOnDoFinal
196+
}
166197

167198
/**
168199
* The number of bytes the implementation returns when [doFinal] is called.
@@ -212,6 +243,10 @@ public actual abstract class Mac: javax.crypto.Mac, Algorithm, Copyable<Mac>, Re
212243
@Throws(IllegalArgumentException::class)
213244
public actual abstract fun reset(newKey: ByteArray)
214245

246+
// Gets set in engineDoFinal if resetOnDoFinal is set to false. Subsequent
247+
// engineReset call will then change it back to false and return early.
248+
private var consumeNextEngineReset = false
249+
215250
// MacSpi
216251
/** @suppress */
217252
@Deprecated("Do not use. Will be marked as ERROR in a later release")
@@ -230,7 +265,13 @@ public actual abstract class Mac: javax.crypto.Mac, Algorithm, Copyable<Mac>, Re
230265
}
231266
/** @suppress */
232267
@Deprecated("Do not use. Will be marked as ERROR in a later release")
233-
protected final override fun engineReset() { reset() }
268+
protected final override fun engineReset() {
269+
if (consumeNextEngineReset) {
270+
consumeNextEngineReset = false
271+
return
272+
}
273+
reset()
274+
}
234275
/** @suppress */
235276
@Deprecated("Do not use. Will be marked as ERROR in a later release")
236277
protected final override fun engineGetMacLength(): Int = macLength()
@@ -257,11 +298,14 @@ public actual abstract class Mac: javax.crypto.Mac, Algorithm, Copyable<Mac>, Re
257298
/** @suppress */
258299
@Deprecated("Do not use. Will be marked as ERROR in a later release")
259300
protected final override fun engineDoFinal(): ByteArray {
301+
if (!resetOnDoFinal) consumeNextEngineReset = true
302+
260303
val b = doFinal()
261304

262305
// Android API 23 and below javax.crypto.Mac does not call engineReset()
306+
@Suppress("DEPRECATION")
263307
@OptIn(InternalKotlinCryptoApi::class)
264-
KC_ANDROID_SDK_INT?.let { if (it <= 23) reset() }
308+
if ((KC_ANDROID_SDK_INT ?: 24) < 24) engineReset()
265309

266310
return b
267311
}

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

+37-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package org.kotlincrypto.core.mac
1919

2020
import org.kotlincrypto.core.*
2121
import org.kotlincrypto.core.mac.internal.*
22+
import kotlin.jvm.JvmField
2223

2324
/**
2425
* Core abstraction for Message Authentication Code implementations.
@@ -109,7 +110,7 @@ public actual abstract class Mac: Algorithm, Copyable<Mac>, Resettable, Updatabl
109110
* */
110111
public actual fun doFinal(): ByteArray {
111112
val final = engine.doFinal()
112-
engine.reset()
113+
if (engine.resetOnDoFinal) engine.reset()
113114
return final
114115
}
115116

@@ -136,6 +137,7 @@ public actual abstract class Mac: Algorithm, Copyable<Mac>, Resettable, Updatabl
136137
public actual fun doFinalInto(dest: ByteArray, destOffset: Int): Int = commonDoFinalInto(
137138
dest = dest,
138139
destOffset = destOffset,
140+
engineResetOnDoFinal = engine.resetOnDoFinal,
139141
engineDoFinalInto = engine::doFinalInto,
140142
engineReset = engine::reset
141143
)
@@ -172,19 +174,49 @@ public actual abstract class Mac: Algorithm, Copyable<Mac>, Resettable, Updatabl
172174
protected actual abstract class Engine: Copyable<Engine>, Resettable, Updatable {
173175

174176
/**
175-
* Initializes a new [Engine] with the provided [key].
177+
* Most [Mac.Engine] are backed by a `Digest`, whereby calling [reset] after
178+
* [doFinal] will cause a double reset (because `Digest.digest` does this inherently).
179+
* By setting this value to `false`, [Engine.reset] will **not** be called whenever
180+
* [doFinal] gets invoked.
176181
*
177-
* @throws [IllegalArgumentException] if [key] is empty.
182+
* **NOTE:** Implementations taking ownership of the automatic reset functionality
183+
* by setting this to `false` must ensure that whatever re-initialization steps were
184+
* taken in their [Engine.reset] function body are executed before their [doFinal]
185+
* and [doFinalInto] implementations return.
186+
* */
187+
@JvmField
188+
public actual val resetOnDoFinal: Boolean
189+
190+
/**
191+
* Initializes a new [Engine] with the provided [key] with the default [resetOnDoFinal]
192+
* value of `true` (i.e. [Engine.reset] will be called automatically after [Engine.doFinal]
193+
* or [Engine.doFinalInto] have been invoked).
194+
*
195+
* @param [key] The key that this [Engine] instance will use to apply its function to
196+
* @throws [IllegalArgumentException] if [key] is empty
197+
* */
198+
@Throws(IllegalArgumentException::class)
199+
public actual constructor(key: ByteArray): this(key, resetOnDoFinal = true)
200+
201+
/**
202+
* Initializes a new [Engine] with the provided [key] and [resetOnDoFinal] configuration.
203+
*
204+
* @param [key] the key that this [Engine] instance will use to apply its function to
205+
* @param [resetOnDoFinal] See [Engine.resetOnDoFinal] documentation
206+
* @throws [IllegalArgumentException] if [key] is empty
178207
* */
179208
@Throws(IllegalArgumentException::class)
180-
public actual constructor(key: ByteArray) {
209+
public actual constructor(key: ByteArray, resetOnDoFinal: Boolean) {
181210
require(key.isNotEmpty()) { "key cannot be empty" }
211+
this.resetOnDoFinal = resetOnDoFinal
182212
}
183213

184214
/**
185215
* Creates a new [Engine] from [other], copying its state.
186216
* */
187-
protected actual constructor(other: Engine)
217+
protected actual constructor(other: Engine) {
218+
this.resetOnDoFinal = other.resetOnDoFinal
219+
}
188220

189221
/**
190222
* The number of bytes the implementation returns when [doFinal] is called.

0 commit comments

Comments
 (0)