Every comment is reduced to its first sentence plus @param, @return and @throws tags. Comment lines: 20040 to 8526. ktlint 1.8 applied with its default (official Kotlin) style across all modules; 262 files reformatted. The 264 remaining ktlint findings are naming and filename rules, which are code changes, and are left alone. Comments and formatting only. 1208 tests pass, counted from the XML. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
119 lines
4.0 KiB
Kotlin
119 lines
4.0 KiB
Kotlin
package io.flamenet.e2e
|
|
|
|
import kotlin.test.Test
|
|
import kotlin.test.assertContentEquals
|
|
import kotlin.test.assertEquals
|
|
import kotlin.test.assertFailsWith
|
|
import kotlin.test.assertFalse
|
|
import kotlin.test.assertTrue
|
|
|
|
/**
|
|
* Zeroing key material once it has been used (ios#39, Android half).
|
|
*/
|
|
class SecureBytesTest {
|
|
private val passphrase = "a-long-enough-passphrase"
|
|
private val salt = ByteArray(16) { it.toByte() }
|
|
|
|
// ---- The failure that would matter most -----------------------------.
|
|
|
|
/**
|
|
* The pinned vectors already cover `authSecret` and `accountKeySeed`.
|
|
*/
|
|
@Test
|
|
fun `wiping the intermediate did not change what is derived`() {
|
|
val (auth, account) = AccountKdf.signInSecrets(passphrase, salt)
|
|
assertContentEquals(AccountKdf.authSecret(passphrase, salt), auth)
|
|
assertContentEquals(AccountKdf.accountKeySeed(passphrase, salt), account)
|
|
}
|
|
|
|
/**
|
|
* And neither is zeroes, which is what "derived from a wiped buffer" would
|
|
* look like.
|
|
*/
|
|
@Test
|
|
fun `no derived secret is a run of zeroes`() {
|
|
val (auth, account) = AccountKdf.signInSecrets(passphrase, salt)
|
|
assertFalse(auth.all { it == 0.toByte() }, "the auth secret is all zeroes")
|
|
assertFalse(account.all { it == 0.toByte() }, "the account seed is all zeroes")
|
|
assertFalse(
|
|
AccountKdf.identityKey(passphrase, salt).all { it == 0.toByte() },
|
|
"the identity key is all zeroes",
|
|
)
|
|
}
|
|
|
|
/** Derivation stays deterministic across calls, wiping or not. */
|
|
@Test
|
|
fun `derivation is still deterministic`() {
|
|
assertContentEquals(
|
|
AccountKdf.identityKey(passphrase, salt),
|
|
AccountKdf.identityKey(passphrase, salt),
|
|
)
|
|
}
|
|
|
|
/** Different contexts still separate, so the wipe did not collapse them. */
|
|
@Test
|
|
fun `the contexts still produce different secrets`() {
|
|
val auth = AccountKdf.authSecret(passphrase, salt)
|
|
val identity = AccountKdf.identityKey(passphrase, salt)
|
|
val account = AccountKdf.accountKeySeed(passphrase, salt)
|
|
assertFalse(auth.contentEquals(identity))
|
|
assertFalse(auth.contentEquals(account))
|
|
assertFalse(identity.contentEquals(account))
|
|
}
|
|
|
|
// ---- The wiping itself ----------------------------------------------.
|
|
|
|
@Test
|
|
fun `wipe zeroes the array it is given`() {
|
|
val secret = ByteArray(32) { 0x41 }
|
|
SecureBytes.wipe(secret)
|
|
assertTrue(secret.all { it == 0.toByte() })
|
|
}
|
|
|
|
@Test
|
|
fun `wiping nothing is not an error`() {
|
|
SecureBytes.wipe(null)
|
|
SecureBytes.wipe(ByteArray(0))
|
|
}
|
|
|
|
@Test
|
|
fun `withSecret wipes on the way out`() {
|
|
val secret = ByteArray(32) { 0x41 }
|
|
val seen = SecureBytes.withSecret(secret) { it.copyOf() }
|
|
assertTrue(seen.all { it == 0x41.toByte() }, "the body saw a wiped buffer")
|
|
assertTrue(secret.all { it == 0.toByte() }, "the buffer was not wiped")
|
|
}
|
|
|
|
/**
|
|
* The case the `finally` exists for.
|
|
*/
|
|
@Test
|
|
fun `withSecret wipes when the body throws`() {
|
|
val secret = ByteArray(32) { 0x41 }
|
|
assertFailsWith<IllegalStateException> {
|
|
SecureBytes.withSecret(secret) { error("derivation failed half-way") }
|
|
}
|
|
assertTrue(secret.all { it == 0.toByte() }, "a throw left the material behind")
|
|
}
|
|
|
|
/**
|
|
* The honest limit, asserted rather than left to the prose.
|
|
*/
|
|
@Test
|
|
fun `a copy taken beforehand survives the wipe`() {
|
|
val secret = ByteArray(32) { 0x41 }
|
|
val copy = SecureBytes.copyOf(secret)
|
|
SecureBytes.wipe(secret)
|
|
assertTrue(secret.all { it == 0.toByte() })
|
|
assertTrue(
|
|
copy.all { it == 0x41.toByte() },
|
|
"the copy was wiped too, which would mean these share a buffer",
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `withSecret returns what the body returned`() {
|
|
assertEquals(7, SecureBytes.withSecret(ByteArray(4)) { 7 })
|
|
}
|
|
}
|