mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-11-01 07:51:15 +00:00
refactor(noise/protocol): polish Kotlin idioms and remove Java legacy
Address IDE/compiler warnings across the protocol/ package after the Java to Kotlin migration: - Replace reference-identity ByteArray checks `!=` with `!==` in ChaChaPoly/AESGCM*CipherState (in-place encrypt/decrypt guards) to silence the "dangerous array comparison" warning while preserving the original semantics. - Drop `java.util.Arrays.fill` and `Arrays`-style helpers in favour of ByteArray.fill / copyInto / copyOfRange where appropriate. Cold paths in NewHopeDHState, Curve25519DHState, Curve448DHState and parts of SymmetricState now use Kotlin extensions; tight crypto inner loops (HMAC body, ChaChaPoly) keep System.arraycopy intentionally. - Use property access (`digestLength`) instead of `getDigestLength()`. - Fold redundant if/else into `if-expression`, drop redundant `val space: Int` + assignment, redundant `Companion.` qualifiers, and unused `val cipher` re-assign in fork(). - Rename `prev_h` to `prevH` (style). - Replace string concatenation with templates in log statements and exception messages. - Convert unused catch parameters `e` to `_` and remove stale local `var index: Int` in hmac(). - Strip stale `@param secondaryKey/offset/length` KDoc block above SymmetricState.split (the params moved to the merged doc). - Drop dead `check(!(local == null || remote == null))` from HandshakeState.mixDH — Kotlin types are already non-null. Crypto behaviour is unchanged; output of every method matches the prior Kotlin/Java implementation byte-for-byte. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
544d569708
commit
f1645e73f2
@ -172,14 +172,13 @@ internal class AESGCMFallbackCipherState : CipherState {
|
||||
ad: ByteArray?, plaintext: ByteArray, plaintextOffset: Int,
|
||||
ciphertext: ByteArray, ciphertextOffset: Int, length: Int
|
||||
): Int {
|
||||
val space: Int
|
||||
require(!(ciphertextOffset < 0 || ciphertextOffset > ciphertext.size))
|
||||
require(!(length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.size || length > plaintext.size || (plaintext.size - plaintextOffset) < length))
|
||||
space = ciphertext.size - ciphertextOffset
|
||||
val space: Int = ciphertext.size - ciphertextOffset
|
||||
if (!haskey) {
|
||||
// The key is not set yet - return the plaintext as-is.
|
||||
if (length > space) throw ShortBufferException()
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
if (plaintext !== ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
plaintext,
|
||||
plaintextOffset,
|
||||
ciphertext,
|
||||
@ -219,7 +218,7 @@ internal class AESGCMFallbackCipherState : CipherState {
|
||||
if (!haskey) {
|
||||
// The key is not set yet - return the ciphertext as-is.
|
||||
if (length > space) throw ShortBufferException()
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
if (plaintext !== ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
ciphertext,
|
||||
ciphertextOffset,
|
||||
plaintext,
|
||||
@ -244,8 +243,7 @@ internal class AESGCMFallbackCipherState : CipherState {
|
||||
}
|
||||
|
||||
override fun fork(key: ByteArray, offset: Int): CipherState {
|
||||
val cipher: CipherState
|
||||
cipher = AESGCMFallbackCipherState()
|
||||
val cipher: CipherState = AESGCMFallbackCipherState()
|
||||
cipher.initializeKey(key, offset)
|
||||
return cipher
|
||||
}
|
||||
|
||||
@ -45,7 +45,12 @@ import javax.crypto.spec.SecretKeySpec
|
||||
internal class AESGCMOnCtrCipherState
|
||||
@Throws(NoSuchAlgorithmException::class)
|
||||
constructor() : CipherState {
|
||||
private val cipher: Cipher
|
||||
private val cipher: Cipher = try {
|
||||
Cipher.getInstance("AES/CTR/NoPadding")
|
||||
} catch (e: NoSuchPaddingException) {
|
||||
// AES/CTR is available, but not the unpadded version? Huh?
|
||||
throw NoSuchAlgorithmException("AES/CTR/NoPadding not available", e)
|
||||
}
|
||||
private var keySpec: SecretKeySpec? = null
|
||||
private var n: Long = 0
|
||||
private val iv: ByteArray = ByteArray(16)
|
||||
@ -53,13 +58,6 @@ constructor() : CipherState {
|
||||
private val ghash: GHASH = GHASH()
|
||||
|
||||
init {
|
||||
cipher = try {
|
||||
Cipher.getInstance("AES/CTR/NoPadding")
|
||||
} catch (e: NoSuchPaddingException) {
|
||||
// AES/CTR is available, but not the unpadded version? Huh?
|
||||
throw NoSuchAlgorithmException("AES/CTR/NoPadding not available", e)
|
||||
}
|
||||
|
||||
// Try to set a 256-bit key on the cipher. Some JCE's are
|
||||
// configured to disallow 256-bit AES if an extra policy
|
||||
// file has not been installed.
|
||||
@ -85,9 +83,9 @@ constructor() : CipherState {
|
||||
val params = IvParameterSpec(iv)
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, params)
|
||||
} catch (e: InvalidKeyException) {
|
||||
} catch (_: InvalidKeyException) {
|
||||
// Shouldn't happen.
|
||||
} catch (e: InvalidAlgorithmParameterException) {
|
||||
} catch (_: InvalidAlgorithmParameterException) {
|
||||
// Shouldn't happen.
|
||||
}
|
||||
}
|
||||
@ -196,14 +194,13 @@ constructor() : CipherState {
|
||||
ad: ByteArray?, plaintext: ByteArray, plaintextOffset: Int,
|
||||
ciphertext: ByteArray, ciphertextOffset: Int, length: Int
|
||||
): Int {
|
||||
val space: Int
|
||||
require(!(ciphertextOffset < 0 || ciphertextOffset > ciphertext.size))
|
||||
require(!(length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.size || length > plaintext.size || (plaintext.size - plaintextOffset) < length))
|
||||
space = ciphertext.size - ciphertextOffset
|
||||
val space: Int = ciphertext.size - ciphertextOffset
|
||||
if (keySpec == null) {
|
||||
// The key is not set yet - return the plaintext as-is.
|
||||
if (length > space) throw ShortBufferException()
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
if (plaintext !== ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
plaintext,
|
||||
plaintextOffset,
|
||||
ciphertext,
|
||||
@ -259,7 +256,7 @@ constructor() : CipherState {
|
||||
if (keySpec == null) {
|
||||
// The key is not set yet - return the ciphertext as-is.
|
||||
if (length > space) throw ShortBufferException()
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
if (plaintext !== ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
ciphertext,
|
||||
ciphertextOffset,
|
||||
plaintext,
|
||||
@ -281,7 +278,7 @@ constructor() : CipherState {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
ghash.update(ciphertext, ciphertextOffset, dataLen)
|
||||
ghash.pad((if (ad != null) ad.size else 0).toLong(), dataLen.toLong())
|
||||
ghash.pad((ad?.size ?: 0).toLong(), dataLen.toLong())
|
||||
ghash.finish(iv, 0, 16)
|
||||
var temp = 0
|
||||
for (index in 0..15) temp =
|
||||
|
||||
@ -89,7 +89,7 @@ internal class ChaChaPolyCipherState : CipherState {
|
||||
*/
|
||||
private fun finish(ad: ByteArray?, length: Int) {
|
||||
poly.pad()
|
||||
putLittleEndian64(polyKey, 0, (if (ad != null) ad.size else 0).toLong())
|
||||
putLittleEndian64(polyKey, 0, (ad?.size ?: 0).toLong())
|
||||
putLittleEndian64(polyKey, 8, length.toLong())
|
||||
poly.update(polyKey, 0, 16)
|
||||
poly.finish(polyKey, 0)
|
||||
@ -128,14 +128,13 @@ internal class ChaChaPolyCipherState : CipherState {
|
||||
ad: ByteArray?, plaintext: ByteArray, plaintextOffset: Int,
|
||||
ciphertext: ByteArray, ciphertextOffset: Int, length: Int
|
||||
): Int {
|
||||
val space: Int
|
||||
require(!(ciphertextOffset < 0 || ciphertextOffset > ciphertext.size))
|
||||
require(!(length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.size || length > plaintext.size || (plaintext.size - plaintextOffset) < length))
|
||||
space = ciphertext.size - ciphertextOffset
|
||||
val space: Int = ciphertext.size - ciphertextOffset
|
||||
if (!haskey) {
|
||||
// The key is not set yet - return the plaintext as-is.
|
||||
if (length > space) throw ShortBufferException()
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
if (plaintext !== ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
plaintext,
|
||||
plaintextOffset,
|
||||
ciphertext,
|
||||
@ -162,16 +161,15 @@ internal class ChaChaPolyCipherState : CipherState {
|
||||
plaintextOffset: Int,
|
||||
length: Int
|
||||
): Int {
|
||||
var space: Int
|
||||
require(!(ciphertextOffset < 0 || ciphertextOffset > ciphertext.size))
|
||||
space = ciphertext.size - ciphertextOffset
|
||||
var space: Int = ciphertext.size - ciphertextOffset
|
||||
if (length > space) throw ShortBufferException()
|
||||
require(!(length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.size || length > ciphertext.size || (ciphertext.size - ciphertextOffset) < length))
|
||||
space = plaintext.size - plaintextOffset
|
||||
if (!haskey) {
|
||||
// The key is not set yet - return the ciphertext as-is.
|
||||
if (length > space) throw ShortBufferException()
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
if (plaintext !== ciphertext || plaintextOffset != ciphertextOffset) System.arraycopy(
|
||||
ciphertext,
|
||||
ciphertextOffset,
|
||||
plaintext,
|
||||
@ -241,21 +239,25 @@ internal class ChaChaPolyCipherState : CipherState {
|
||||
outputOffset += 4
|
||||
length -= 4
|
||||
}
|
||||
if (length == 3) {
|
||||
value = block[posn]
|
||||
output[outputOffset] = (input[inputOffset].toInt() xor value).toByte()
|
||||
output[outputOffset + 1] =
|
||||
(input[inputOffset + 1].toInt() xor (value shr 8)).toByte()
|
||||
output[outputOffset + 2] =
|
||||
(input[inputOffset + 2].toInt() xor (value shr 16)).toByte()
|
||||
} else if (length == 2) {
|
||||
value = block[posn]
|
||||
output[outputOffset] = (input[inputOffset].toInt() xor value).toByte()
|
||||
output[outputOffset + 1] =
|
||||
(input[inputOffset + 1].toInt() xor (value shr 8)).toByte()
|
||||
} else if (length == 1) {
|
||||
value = block[posn]
|
||||
output[outputOffset] = (input[inputOffset].toInt() xor value).toByte()
|
||||
when (length) {
|
||||
3 -> {
|
||||
value = block[posn]
|
||||
output[outputOffset] = (input[inputOffset].toInt() xor value).toByte()
|
||||
output[outputOffset + 1] =
|
||||
(input[inputOffset + 1].toInt() xor (value shr 8)).toByte()
|
||||
output[outputOffset + 2] =
|
||||
(input[inputOffset + 2].toInt() xor (value shr 16)).toByte()
|
||||
}
|
||||
2 -> {
|
||||
value = block[posn]
|
||||
output[outputOffset] = (input[inputOffset].toInt() xor value).toByte()
|
||||
output[outputOffset + 1] =
|
||||
(input[inputOffset + 1].toInt() xor (value shr 8)).toByte()
|
||||
}
|
||||
1 -> {
|
||||
value = block[posn]
|
||||
output[outputOffset] = (input[inputOffset].toInt() xor value).toByte()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -49,21 +49,21 @@ internal class Curve25519DHState : DHState {
|
||||
}
|
||||
|
||||
override fun getPublicKey(key: ByteArray, offset: Int) {
|
||||
System.arraycopy(publicKey, 0, key, offset, 32)
|
||||
publicKey.copyInto(key, offset, 0, 32)
|
||||
}
|
||||
|
||||
override fun setPublicKey(key: ByteArray, offset: Int) {
|
||||
System.arraycopy(key, offset, publicKey, 0, 32)
|
||||
key.copyInto(publicKey, 0, offset, offset + 32)
|
||||
privateKey.fill(0)
|
||||
mode = 0x01
|
||||
}
|
||||
|
||||
override fun getPrivateKey(key: ByteArray, offset: Int) {
|
||||
System.arraycopy(privateKey, 0, key, offset, 32)
|
||||
privateKey.copyInto(key, offset, 0, 32)
|
||||
}
|
||||
|
||||
override fun setPrivateKey(key: ByteArray, offset: Int) {
|
||||
System.arraycopy(key, offset, privateKey, 0, 32)
|
||||
key.copyInto(privateKey, 0, offset, offset + 32)
|
||||
Curve25519.eval(publicKey, 0, privateKey, null)
|
||||
mode = 0x03
|
||||
}
|
||||
@ -99,8 +99,8 @@ internal class Curve25519DHState : DHState {
|
||||
override fun copyFrom(other: DHState) {
|
||||
check(other is Curve25519DHState) { "Mismatched DH key objects" }
|
||||
if (other === this) return
|
||||
System.arraycopy(other.privateKey, 0, privateKey, 0, 32)
|
||||
System.arraycopy(other.publicKey, 0, publicKey, 0, 32)
|
||||
other.privateKey.copyInto(privateKey, 0, 0, 32)
|
||||
other.publicKey.copyInto(publicKey, 0, 0, 32)
|
||||
mode = other.mode
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,21 +49,21 @@ internal class Curve448DHState : DHState {
|
||||
}
|
||||
|
||||
override fun getPublicKey(key: ByteArray, offset: Int) {
|
||||
System.arraycopy(publicKey, 0, key, offset, 56)
|
||||
publicKey.copyInto(key, offset, 0, 56)
|
||||
}
|
||||
|
||||
override fun setPublicKey(key: ByteArray, offset: Int) {
|
||||
System.arraycopy(key, offset, publicKey, 0, 56)
|
||||
key.copyInto(publicKey, 0, offset, offset + 56)
|
||||
privateKey.fill(0)
|
||||
mode = 0x01
|
||||
}
|
||||
|
||||
override fun getPrivateKey(key: ByteArray, offset: Int) {
|
||||
System.arraycopy(privateKey, 0, key, offset, 56)
|
||||
privateKey.copyInto(key, offset, 0, 56)
|
||||
}
|
||||
|
||||
override fun setPrivateKey(key: ByteArray, offset: Int) {
|
||||
System.arraycopy(key, offset, privateKey, 0, 56)
|
||||
key.copyInto(privateKey, 0, offset, offset + 56)
|
||||
Curve448.eval(publicKey, 0, privateKey, null)
|
||||
mode = 0x03
|
||||
}
|
||||
@ -99,8 +99,8 @@ internal class Curve448DHState : DHState {
|
||||
override fun copyFrom(other: DHState) {
|
||||
check(other is Curve448DHState) { "Mismatched DH key objects" }
|
||||
if (other === this) return
|
||||
System.arraycopy(other.privateKey, 0, privateKey, 0, 56)
|
||||
System.arraycopy(other.publicKey, 0, publicKey, 0, 56)
|
||||
other.privateKey.copyInto(privateKey, 0, 0, 56)
|
||||
other.publicKey.copyInto(publicKey, 0, 0, 56)
|
||||
mode = other.mode
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,7 +28,6 @@ import com.bitchat.android.noise.southernstorm.protocol.Noise.destroy
|
||||
import com.bitchat.android.noise.southernstorm.protocol.Pattern.lookup
|
||||
import com.bitchat.android.noise.southernstorm.protocol.Pattern.reverseFlags
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.Arrays
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.ShortBufferException
|
||||
|
||||
@ -97,8 +96,8 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
* @return true if a pre-shared key is needed; false if not.
|
||||
*/
|
||||
fun needsPreSharedKey(): Boolean {
|
||||
if (preSharedKey != null) return false
|
||||
else return (requirements and PSK_REQUIRED) != 0
|
||||
return if (preSharedKey != null) false
|
||||
else (requirements and PSK_REQUIRED) != 0
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,8 +167,8 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
* will return false.
|
||||
*/
|
||||
fun needsLocalKeyPair(): Boolean {
|
||||
if (localKeyPair != null) return !localKeyPair!!.hasPrivateKey()
|
||||
else return false
|
||||
return if (localKeyPair != null) !localKeyPair!!.hasPrivateKey()
|
||||
else false
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,8 +179,8 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
* false if not.
|
||||
*/
|
||||
fun hasLocalKeyPair(): Boolean {
|
||||
if (localKeyPair != null) return localKeyPair!!.hasPrivateKey()
|
||||
else return false
|
||||
return if (localKeyPair != null) localKeyPair!!.hasPrivateKey()
|
||||
else false
|
||||
}
|
||||
|
||||
/**
|
||||
@ -193,8 +192,8 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
* will return false.
|
||||
*/
|
||||
fun needsRemotePublicKey(): Boolean {
|
||||
if (remotePublicKey != null) return !remotePublicKey!!.hasPublicKey()
|
||||
else return false
|
||||
return if (remotePublicKey != null) !remotePublicKey!!.hasPublicKey()
|
||||
else false
|
||||
}
|
||||
|
||||
/**
|
||||
@ -205,8 +204,8 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
* false if not.
|
||||
*/
|
||||
fun hasRemotePublicKey(): Boolean {
|
||||
if (remotePublicKey != null) return remotePublicKey!!.hasPublicKey()
|
||||
else return false
|
||||
return if (remotePublicKey != null) remotePublicKey!!.hasPublicKey()
|
||||
else false
|
||||
}
|
||||
|
||||
val fixedEphemeralKey: DHState?
|
||||
@ -227,7 +226,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
try {
|
||||
fixedEphemeral =
|
||||
createDH(localEphemeral!!.dhName)
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
} catch (_: NoSuchAlgorithmException) {
|
||||
// This shouldn't happen - the local ephemeral key would
|
||||
// have already been created with the same name!
|
||||
fixedEphemeral = null
|
||||
@ -253,7 +252,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
try {
|
||||
fixedHybrid =
|
||||
createDH(localHybrid!!.dhName)
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
} catch (_: NoSuchAlgorithmException) {
|
||||
// This shouldn't happen - the local hybrid key would
|
||||
// have already been created with the same name!
|
||||
fixedHybrid = null
|
||||
@ -320,21 +319,21 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
if ((flags.toInt() and Pattern.FLAG_LOCAL_EPHEMERAL.toInt()) != 0) localEphemeral =
|
||||
createDH(dh)
|
||||
if ((flags.toInt() and Pattern.FLAG_LOCAL_HYBRID.toInt()) != 0) localHybrid =
|
||||
Noise.createDH(hybrid!!)
|
||||
createDH(hybrid!!)
|
||||
if ((flags.toInt() and Pattern.FLAG_REMOTE_STATIC.toInt()) != 0) remotePublicKey =
|
||||
createDH(dh)
|
||||
if ((flags.toInt() and Pattern.FLAG_REMOTE_EPHEMERAL.toInt()) != 0) remoteEphemeral =
|
||||
createDH(dh)
|
||||
if ((flags.toInt() and Pattern.FLAG_REMOTE_HYBRID.toInt()) != 0) remoteHybrid =
|
||||
Noise.createDH(hybrid!!)
|
||||
createDH(hybrid!!)
|
||||
|
||||
|
||||
// We cannot use hybrid algorithms like New Hope for ephemeral or static keys,
|
||||
// as the unbalanced nature of the algorithm only works with "f" and "ff" tokens.
|
||||
if (localKeyPair is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '" + localKeyPair!!.dhName + "' for static keys")
|
||||
if (localEphemeral is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '" + localEphemeral!!.dhName + "' for ephemeral keys")
|
||||
if (remotePublicKey is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '" + remotePublicKey!!.dhName + "' for static keys")
|
||||
if (remoteEphemeral is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '" + remoteEphemeral!!.dhName + "' for ephemeral keys")
|
||||
if (localKeyPair is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '${localKeyPair!!.dhName}' for static keys")
|
||||
if (localEphemeral is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '${localEphemeral!!.dhName}' for ephemeral keys")
|
||||
if (remotePublicKey is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '${remotePublicKey!!.dhName}' for static keys")
|
||||
if (remoteEphemeral is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '${remoteEphemeral!!.dhName}' for ephemeral keys")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -379,9 +378,9 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
|
||||
// Log the symmetric state BEFORE any mixing operations (matching iOS)
|
||||
Log.d(TAG, "=== ANDROID HANDSHAKE START - INITIAL STATE ===")
|
||||
Log.d(TAG, "Protocol: " + symmetric!!.protocolName)
|
||||
Log.d(TAG, "Role: " + (if (isInitiator) "INITIATOR" else "RESPONDER"))
|
||||
Log.d(TAG, "Initial symmetric hash: " + bytesToHex(symmetric!!.handshakeHash!!))
|
||||
Log.d(TAG, "Protocol: ${symmetric!!.protocolName}")
|
||||
Log.d(TAG, "Role: ${if (isInitiator) "INITIATOR" else "RESPONDER"}")
|
||||
Log.d(TAG, "Initial symmetric hash: ${bytesToHex(symmetric!!.handshakeHash!!)}")
|
||||
|
||||
|
||||
// Hash the prologue value.
|
||||
@ -389,7 +388,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
val p = prologue
|
||||
if (p != null) symmetric!!.mixHash(p, 0, p.size)
|
||||
else symmetric!!.mixHash(emptyPrologue, 0, 0)
|
||||
Log.d(TAG, "Hash after empty prologue: " + bytesToHex(symmetric!!.handshakeHash!!))
|
||||
Log.d(TAG, "Hash after empty prologue: ${bytesToHex(symmetric!!.handshakeHash!!)}")
|
||||
|
||||
|
||||
// Hash the pre-shared key into the chaining key and handshake hash.
|
||||
@ -421,13 +420,12 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
Log.d(TAG, "=== ANDROID HANDSHAKE START - FINAL STATE ===")
|
||||
Log.d(
|
||||
TAG,
|
||||
"Final symmetric hash after mixPreMessageKeys(): " + bytesToHex(symmetric!!.handshakeHash!!)
|
||||
"Final symmetric hash after mixPreMessageKeys(): ${bytesToHex(symmetric!!.handshakeHash!!)}"
|
||||
)
|
||||
Log.d(TAG, "===========================================")
|
||||
|
||||
// The handshake has officially started - set the first action.
|
||||
if (isInitiator) action = WRITE_MESSAGE
|
||||
else action = READ_MESSAGE
|
||||
action = if (isInitiator) WRITE_MESSAGE else READ_MESSAGE
|
||||
}
|
||||
|
||||
/**
|
||||
@ -579,7 +577,6 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
* @param remote Remote public key object.
|
||||
*/
|
||||
private fun mixDH(local: DHState, remote: DHState) {
|
||||
check(!(local == null || remote == null)) { "Pattern definition error" }
|
||||
val len = local.sharedKeyLength
|
||||
val shared = ByteArray(len)
|
||||
try {
|
||||
@ -750,7 +747,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
else -> {
|
||||
// Unknown token code. Abort.
|
||||
throw IllegalStateException(
|
||||
"Unknown handshake token " + token.toInt().toString()
|
||||
"Unknown handshake token ${token.toInt()}"
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -777,7 +774,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
// If we failed, then clear any sensitive data that may have
|
||||
// already been written to the message buffer.
|
||||
if (!success) {
|
||||
Arrays.fill(message, messageOffset, message.size - messageOffset, 0.toByte())
|
||||
message.fill(0, messageOffset, message.size - messageOffset)
|
||||
action = FAILED
|
||||
}
|
||||
}
|
||||
@ -817,7 +814,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
payload: ByteArray,
|
||||
payloadOffset: Int
|
||||
): Int {
|
||||
var messageOffset = messageOffset
|
||||
@Suppress("NAME_SHADOWING") var messageOffset = messageOffset
|
||||
var success = false
|
||||
val messageEnd = messageOffset + messageLength
|
||||
|
||||
@ -955,7 +952,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
else -> {
|
||||
// Unknown token code. Abort.
|
||||
throw IllegalStateException(
|
||||
"Unknown handshake token " + token.toInt().toString()
|
||||
"Unknown handshake token ${token.toInt()}"
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -976,7 +973,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
// If we failed, then clear any sensitive data that may have
|
||||
// already been written to the payload buffer.
|
||||
if (!success) {
|
||||
Arrays.fill(payload, payloadOffset, payload.size - payloadOffset, 0.toByte())
|
||||
payload.fill(0, payloadOffset, payload.size - payloadOffset)
|
||||
action = FAILED
|
||||
}
|
||||
}
|
||||
@ -1143,7 +1140,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
|
||||
private fun bytesToHex(bytes: ByteArray): String {
|
||||
val sb = StringBuilder()
|
||||
for (b in bytes) {
|
||||
sb.append(String.format("%02x", b))
|
||||
sb.append("%02x".format(b))
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ internal class NewHopeDHState : DHStateHybrid {
|
||||
*/
|
||||
private inner class NewHopeWithPrivateKey(private val randomData: ByteArray) : NewHopeTor() {
|
||||
override fun randombytes(buffer: ByteArray) {
|
||||
System.arraycopy(randomData, 0, buffer, 0, buffer.size)
|
||||
randomData.copyInto(buffer, 0, 0, buffer.size)
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,21 +102,19 @@ internal class NewHopeDHState : DHStateHybrid {
|
||||
|
||||
override fun getPublicKey(key: ByteArray, offset: Int) {
|
||||
val pk = publicKey
|
||||
if (pk != null) System.arraycopy(pk, 0, key, offset, publicKeyLength)
|
||||
if (pk != null) pk.copyInto(key, offset, 0, publicKeyLength)
|
||||
else key.fill(0, offset, offset + publicKeyLength)
|
||||
}
|
||||
|
||||
override fun setPublicKey(key: ByteArray, offset: Int) {
|
||||
publicKey?.let { Noise.destroy(it) }
|
||||
val len = publicKeyLength
|
||||
val pk = ByteArray(len)
|
||||
System.arraycopy(key, offset, pk, 0, len)
|
||||
publicKey = pk
|
||||
publicKey = key.copyOfRange(offset, offset + len)
|
||||
}
|
||||
|
||||
override fun getPrivateKey(key: ByteArray, offset: Int) {
|
||||
val sk = privateKey
|
||||
if (sk != null) System.arraycopy(sk, 0, key, offset, privateKeyLength)
|
||||
if (sk != null) sk.copyInto(key, offset, 0, privateKeyLength)
|
||||
else key.fill(0, offset, offset + privateKeyLength)
|
||||
}
|
||||
|
||||
@ -125,9 +123,7 @@ internal class NewHopeDHState : DHStateHybrid {
|
||||
// Guess the key type from the length of the test data.
|
||||
keyType = if (offset == 0 && key.size == 64) KeyType.AlicePrivate else KeyType.BobPrivate
|
||||
val len = privateKeyLength
|
||||
val sk = ByteArray(len)
|
||||
System.arraycopy(key, offset, sk, 0, len)
|
||||
privateKey = sk
|
||||
privateKey = key.copyOfRange(offset, offset + len)
|
||||
}
|
||||
|
||||
override fun setToNullPublicKey() {
|
||||
@ -169,7 +165,7 @@ internal class NewHopeDHState : DHStateHybrid {
|
||||
}
|
||||
KeyType.BobCalculated -> {
|
||||
// The shared key for Bob was already computed when the key was generated.
|
||||
System.arraycopy(privateKey!!, 0, sharedKey, 0, NewHope.SHAREDBYTES)
|
||||
privateKey!!.copyInto(sharedKey, 0, 0, NewHope.SHAREDBYTES)
|
||||
}
|
||||
else -> throw IllegalStateException("Cannot calculate with this DH object")
|
||||
}
|
||||
|
||||
@ -28,7 +28,6 @@ import com.bitchat.android.noise.southernstorm.protocol.Noise.destroy
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.security.DigestException
|
||||
import java.security.MessageDigest
|
||||
import java.util.Arrays
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.ShortBufferException
|
||||
|
||||
@ -57,7 +56,7 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
*/
|
||||
var handshakeHash: ByteArray?
|
||||
private set
|
||||
private var prev_h: ByteArray?
|
||||
private var prevH: ByteArray?
|
||||
|
||||
/**
|
||||
* Constructs a new symmetric state object.
|
||||
@ -73,50 +72,38 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
this.protocolName = protocolName
|
||||
cipher = createCipher(cipherName)
|
||||
hash = createHash(hashName)
|
||||
val hashLength = hash!!.getDigestLength()
|
||||
val hashLength = hash!!.digestLength
|
||||
ck = ByteArray(hashLength)
|
||||
this.handshakeHash = ByteArray(hashLength)
|
||||
prev_h = ByteArray(hashLength)
|
||||
val initialHash = ByteArray(hashLength)
|
||||
this.handshakeHash = initialHash
|
||||
prevH = ByteArray(hashLength)
|
||||
|
||||
val protocolNameBytes: ByteArray
|
||||
try {
|
||||
protocolNameBytes = protocolName.toByteArray(charset("UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
val protocolNameBytes: ByteArray = try {
|
||||
protocolName.toByteArray(charset("UTF-8"))
|
||||
} catch (_: UnsupportedEncodingException) {
|
||||
// If UTF-8 is not supported, then we are definitely in trouble!
|
||||
throw UnsupportedOperationException("UTF-8 encoding is not supported")
|
||||
}
|
||||
|
||||
if (protocolNameBytes.size <= hashLength) {
|
||||
System.arraycopy(
|
||||
protocolNameBytes, 0,
|
||||
this.handshakeHash, 0, protocolNameBytes.size
|
||||
)
|
||||
Arrays.fill(
|
||||
this.handshakeHash,
|
||||
protocolNameBytes.size,
|
||||
handshakeHash!!.size,
|
||||
0.toByte()
|
||||
)
|
||||
protocolNameBytes.copyInto(initialHash, 0, 0, protocolNameBytes.size)
|
||||
initialHash.fill(0, protocolNameBytes.size, initialHash.size)
|
||||
} else {
|
||||
hashOne(
|
||||
protocolNameBytes, 0, protocolNameBytes.size,
|
||||
this.handshakeHash!!, 0, handshakeHash!!.size
|
||||
initialHash, 0, initialHash.size
|
||||
)
|
||||
}
|
||||
|
||||
System.arraycopy(this.handshakeHash, 0, ck, 0, hashLength)
|
||||
initialHash.copyInto(ck!!, 0, 0, hashLength)
|
||||
|
||||
|
||||
// LOGGING: Initial symmetric state after protocol name initialization (matching iOS)
|
||||
Log.d(TAG, "=== ANDROID SYMMETRIC STATE INITIALIZED ===")
|
||||
Log.d(TAG, "Protocol: " + protocolName)
|
||||
Log.d(
|
||||
TAG, "Initial hash (h): " + Companion.bytesToHex(
|
||||
this.handshakeHash!!
|
||||
)
|
||||
)
|
||||
Log.d(TAG, "Initial chaining key (ck): " + Companion.bytesToHex(ck!!))
|
||||
Log.d(TAG, "Hash length: " + handshakeHash!!.size)
|
||||
Log.d(TAG, "Protocol: $protocolName")
|
||||
Log.d(TAG, "Initial hash (h): " + bytesToHex(initialHash))
|
||||
Log.d(TAG, "Initial chaining key (ck): " + bytesToHex(ck!!))
|
||||
Log.d(TAG, "Hash length: " + initialHash.size)
|
||||
Log.d(TAG, "=========================================")
|
||||
}
|
||||
|
||||
@ -137,15 +124,11 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
fun mixKey(data: ByteArray, offset: Int, length: Int) {
|
||||
// LOGGING: Before mixKey operation (matching iOS)
|
||||
val inputData = ByteArray(length)
|
||||
System.arraycopy(data, offset, inputData, 0, length)
|
||||
data.copyInto(inputData, 0, offset, offset + length)
|
||||
Log.d(TAG, "*** Android mixKey() BEFORE ***")
|
||||
Log.d(TAG, "Input data (" + length + " bytes): " + bytesToHex(inputData))
|
||||
Log.d(TAG, "Current CK: " + Companion.bytesToHex(ck!!))
|
||||
Log.d(
|
||||
TAG, "Current Hash: " + Companion.bytesToHex(
|
||||
this.handshakeHash!!
|
||||
)
|
||||
)
|
||||
Log.d(TAG, "Input data ($length bytes): " + bytesToHex(inputData))
|
||||
Log.d(TAG, "Current CK: " + bytesToHex(ck!!))
|
||||
Log.d(TAG, "Current Hash: " + bytesToHex(this.handshakeHash!!))
|
||||
|
||||
val keyLength = cipher!!.keyLength
|
||||
val tempKey = ByteArray(keyLength)
|
||||
@ -172,12 +155,8 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
|
||||
// LOGGING: After mixKey operation (matching iOS)
|
||||
Log.d(TAG, "*** Android mixKey() AFTER ***")
|
||||
Log.d(TAG, "New CK: " + Companion.bytesToHex(ck!!))
|
||||
Log.d(
|
||||
TAG, "Hash unchanged: " + Companion.bytesToHex(
|
||||
this.handshakeHash!!
|
||||
)
|
||||
)
|
||||
Log.d(TAG, "New CK: " + bytesToHex(ck!!))
|
||||
Log.d(TAG, "Hash unchanged: " + bytesToHex(this.handshakeHash!!))
|
||||
Log.d(TAG, "Cipher now has key: " + (cipher!!.macLength > 0))
|
||||
}
|
||||
|
||||
@ -191,28 +170,21 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
fun mixHash(data: ByteArray, offset: Int, length: Int) {
|
||||
// LOGGING: Before mixHash operation (matching iOS)
|
||||
val inputData = ByteArray(length)
|
||||
System.arraycopy(data, offset, inputData, 0, length)
|
||||
data.copyInto(inputData, 0, offset, offset + length)
|
||||
Log.d(TAG, "*** Android mixHash() BEFORE ***")
|
||||
Log.d(TAG, "Input data (" + length + " bytes): " + bytesToHex(inputData))
|
||||
Log.d(
|
||||
TAG, "Current Hash: " + Companion.bytesToHex(
|
||||
this.handshakeHash!!
|
||||
)
|
||||
)
|
||||
Log.d(TAG, "Input data ($length bytes): " + bytesToHex(inputData))
|
||||
Log.d(TAG, "Current Hash: " + bytesToHex(this.handshakeHash!!))
|
||||
|
||||
val h = handshakeHash!!
|
||||
hashTwo(
|
||||
this.handshakeHash!!, 0, handshakeHash!!.size, data, offset, length,
|
||||
this.handshakeHash!!, 0, handshakeHash!!.size
|
||||
h, 0, h.size, data, offset, length,
|
||||
h, 0, h.size
|
||||
)
|
||||
|
||||
|
||||
// LOGGING: After mixHash operation (matching iOS)
|
||||
Log.d(TAG, "*** Android mixHash() AFTER ***")
|
||||
Log.d(
|
||||
TAG, "New Hash: " + Companion.bytesToHex(
|
||||
this.handshakeHash!!
|
||||
)
|
||||
)
|
||||
Log.d(TAG, "New Hash: " + bytesToHex(this.handshakeHash!!))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -221,7 +193,7 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
* @param key The pre-shared key value.
|
||||
*/
|
||||
fun mixPreSharedKey(key: ByteArray) {
|
||||
val temp = ByteArray(hash!!.getDigestLength())
|
||||
val temp = ByteArray(hash!!.digestLength)
|
||||
try {
|
||||
hkdf(ck!!, 0, ck!!.size, key, 0, key.size, ck!!, 0, ck!!.size, temp, 0, temp.size)
|
||||
mixHash(temp, 0, temp.size)
|
||||
@ -333,10 +305,11 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
plaintextOffset: Int,
|
||||
length: Int
|
||||
): Int {
|
||||
System.arraycopy(this.handshakeHash, 0, prev_h, 0, handshakeHash!!.size)
|
||||
val h = handshakeHash!!
|
||||
h.copyInto(prevH!!, 0, 0, h.size)
|
||||
mixHash(ciphertext, ciphertextOffset, length)
|
||||
return cipher!!.decryptWithAd(
|
||||
prev_h,
|
||||
prevH,
|
||||
ciphertext,
|
||||
ciphertextOffset,
|
||||
plaintext,
|
||||
@ -348,20 +321,15 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
/**
|
||||
* Splits the symmetric state into two ciphers for session encryption,
|
||||
* and optionally mixes in a secondary symmetric key.
|
||||
*
|
||||
*
|
||||
* @param secondaryKey The buffer containing the secondary key.
|
||||
* @param offset The offset of the first secondary key byte.
|
||||
* @param length The length of the secondary key in bytes, which
|
||||
* must be either 0 or 32.
|
||||
* @return The pair of ciphers for sending and receiving.
|
||||
*
|
||||
*
|
||||
* @throws IllegalArgumentException The length is not 0 or 32.
|
||||
*/
|
||||
/**
|
||||
* Splits the symmetric state into two ciphers for session encryption.
|
||||
*
|
||||
* @return The pair of ciphers for sending and receiving.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun split(
|
||||
secondaryKey: ByteArray? = ByteArray(0),
|
||||
@ -388,8 +356,8 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
if (c1 == null || c2 == null || pair == null) {
|
||||
// Could not create some of the objects. Clean up the others
|
||||
// to avoid accidental leakage of k1 or k2.
|
||||
if (c1 != null) c1.destroy()
|
||||
if (c2 != null) c2.destroy()
|
||||
c1?.destroy()
|
||||
c2?.destroy()
|
||||
pair = null
|
||||
}
|
||||
}
|
||||
@ -420,9 +388,9 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
Noise.destroy(this.handshakeHash!!)
|
||||
this.handshakeHash = null
|
||||
}
|
||||
if (prev_h != null) {
|
||||
Noise.destroy(prev_h!!)
|
||||
prev_h = null
|
||||
if (prevH != null) {
|
||||
Noise.destroy(prevH!!)
|
||||
prevH = null
|
||||
}
|
||||
}
|
||||
|
||||
@ -450,8 +418,8 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
hash!!.update(data, offset, length)
|
||||
try {
|
||||
hash!!.digest(output, outputOffset, outputLength)
|
||||
} catch (e: DigestException) {
|
||||
Arrays.fill(output, outputOffset, outputLength, 0.toByte())
|
||||
} catch (_: DigestException) {
|
||||
output.fill(0, outputOffset, outputLength)
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,8 +448,8 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
hash!!.update(data2, offset2, length2)
|
||||
try {
|
||||
hash!!.digest(output, outputOffset, outputLength)
|
||||
} catch (e: DigestException) {
|
||||
Arrays.fill(output, outputOffset, outputLength, 0.toByte())
|
||||
} catch (_: DigestException) {
|
||||
output.fill(0, outputOffset, outputLength)
|
||||
}
|
||||
}
|
||||
|
||||
@ -505,19 +473,18 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
) {
|
||||
// In all of the algorithms of interest to us, the block length
|
||||
// is twice the size of the hash length.
|
||||
val hashLength = hash!!.getDigestLength()
|
||||
val hashLength = hash!!.digestLength
|
||||
val blockLength = hashLength * 2
|
||||
val block = ByteArray(blockLength)
|
||||
var index: Int
|
||||
try {
|
||||
if (keyLength <= blockLength) {
|
||||
System.arraycopy(key, keyOffset, block, 0, keyLength)
|
||||
Arrays.fill(block, keyLength, blockLength, 0.toByte())
|
||||
block.fill(0, keyLength, blockLength)
|
||||
} else {
|
||||
hash!!.reset()
|
||||
hash!!.update(key, keyOffset, keyLength)
|
||||
hash!!.digest(block, 0, hashLength)
|
||||
Arrays.fill(block, hashLength, blockLength, 0.toByte())
|
||||
block.fill(0, hashLength, blockLength)
|
||||
}
|
||||
for (i in 0 until blockLength) {
|
||||
block[i] = (block[i].toInt() xor 0x36).toByte()
|
||||
@ -533,8 +500,8 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
hash!!.update(block, 0, blockLength)
|
||||
hash!!.update(output, outputOffset, hashLength)
|
||||
hash!!.digest(output, outputOffset, outputLength)
|
||||
} catch (e: DigestException) {
|
||||
Arrays.fill(output, outputOffset, outputLength, 0.toByte())
|
||||
} catch (_: DigestException) {
|
||||
output.fill(0, outputOffset, outputLength)
|
||||
} finally {
|
||||
destroy(block)
|
||||
}
|
||||
@ -564,7 +531,7 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
output1: ByteArray, output1Offset: Int, output1Length: Int,
|
||||
output2: ByteArray, output2Offset: Int, output2Length: Int
|
||||
) {
|
||||
val hashLength = hash!!.getDigestLength()
|
||||
val hashLength = hash!!.digestLength
|
||||
val tempKey = ByteArray(hashLength)
|
||||
val tempHash = ByteArray(hashLength + 1)
|
||||
try {
|
||||
@ -590,7 +557,7 @@ internal class SymmetricState(protocolName: String, cipherName: String, hashName
|
||||
private fun bytesToHex(bytes: ByteArray): String {
|
||||
val sb = StringBuilder()
|
||||
for (b in bytes) {
|
||||
sb.append(String.format("%02x", b))
|
||||
sb.append("%02x".format(b))
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user