refactor(noise/protocol): migrate HandshakeState to Kotlin

- Use Kotlin properties (`protocolName`, `handshakeHash`) and update
  every getter call (`getDHName/getPublicKeyLength/getMACLength/
  getSharedKeyLength/getProtocolName/getHandshakeHash`) to the new
  property names exposed by the migrated DHState / SymmetricState /
  CipherState interfaces.
- Initialize `pattern` non-null via `requireNotNull(lookup(...))`,
  and give the other late-init `var` fields (`patternIndex`,
  `preSharedKey`, `prologue`) sensible defaults so the class
  compiles cleanly.
- Resolve smart-cast errors on mutable nullable fields (`prologue`,
  `fixedEphemeral`, `fixedHybrid`, `localHybrid`) by copying to
  local `val`s before use.
- Replace `preSharedKey != null` / `mixPreSharedKey(preSharedKey)`
  guards with `?.let` blocks, and pass `localKeyPair!!` etc. into
  `mixPublicKey` to satisfy its now-non-null parameter.
- Tighten the cipher/hash name locals in the protocol-name parser
  from `String?` to `String` so the SymmetricState constructor
  accepts them.
- Token-processing loop, fallback bookkeeping and message buffer
  layout are unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
yet 2026-05-15 18:00:06 +04:00
parent 1f2c62d205
commit 84f70ff12d

View File

@ -73,18 +73,14 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
var action: Int
private set
private var requirements: Int
private var pattern: ShortArray?
private var patternIndex: Int
private var preSharedKey: ByteArray?
private var prologue: ByteArray?
private var pattern: ShortArray
private var patternIndex: Int = 1
private var preSharedKey: ByteArray? = null
private var prologue: ByteArray? = null
val protocolName: String?
/**
* Gets the name of the Noise protocol.
*
* @return The protocol name.
*/
get() = symmetric!!.getProtocolName()
/** Name of the Noise protocol. */
val protocolName: String
get() = symmetric!!.protocolName
val role: Int
/**
@ -230,7 +226,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
if (localEphemeral == null) return null
try {
fixedEphemeral =
createDH(localEphemeral!!.getDHName())
createDH(localEphemeral!!.dhName)
} catch (e: NoSuchAlgorithmException) {
// This shouldn't happen - the local ephemeral key would
// have already been created with the same name!
@ -256,7 +252,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
if (localHybrid == null) return null
try {
fixedHybrid =
createDH(localHybrid!!.getDHName())
createDH(localHybrid!!.dhName)
} catch (e: NoSuchAlgorithmException) {
// This shouldn't happen - the local hybrid key would
// have already been created with the same name!
@ -286,12 +282,11 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
val patternId = components[1]
var dh = components[2]
var hybrid: String? = null
val cipher: String? = components[3]
val hash: String? = components[4]
val cipher: String = components[3]
val hash: String = components[4]
require(!(prefix != "Noise" && prefix != "NoisePSK")) { "Prefix must be Noise or NoisePSK" }
pattern = lookup(patternId)
requireNotNull(pattern) { "Handshake pattern is not recognized" }
var flags = pattern!![0]
pattern = requireNotNull(lookup(patternId)) { "Handshake pattern is not recognized" }
var flags = pattern[0]
var extraReqs = 0
if ((flags.toInt() and Pattern.FLAG_REMOTE_REQUIRED.toInt()) != 0 && patternId.length > 1) extraReqs =
extraReqs or FALLBACK_POSSIBLE
@ -336,10 +331,10 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
// 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!!.getDHName() + "' for static keys")
if (localEphemeral is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '" + localEphemeral!!.getDHName() + "' for ephemeral keys")
if (remotePublicKey is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '" + remotePublicKey!!.getDHName() + "' for static keys")
if (remoteEphemeral is DHStateHybrid) throw NoSuchAlgorithmException("Cannot use '" + remoteEphemeral!!.getDHName() + "' 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")
}
/**
@ -364,7 +359,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
*/
fun start() {
check(action == NO_ACTION) { "Handshake has already started; cannot start again" }
if ((pattern!![0].toInt() and Pattern.FLAG_REMOTE_EPHEM_REQ.toInt()) != 0 &&
if ((pattern[0].toInt() and Pattern.FLAG_REMOTE_EPHEM_REQ.toInt()) != 0 &&
(requirements and FALLBACK_PREMSG) == 0
) {
throw UnsupportedOperationException("Cannot start a fallback pattern")
@ -384,48 +379,49 @@ 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!!.getProtocolName())
Log.d(TAG, "Protocol: " + symmetric!!.protocolName)
Log.d(TAG, "Role: " + (if (isInitiator) "INITIATOR" else "RESPONDER"))
Log.d(TAG, "Initial symmetric hash: " + bytesToHex(symmetric!!.getHandshakeHash()))
Log.d(TAG, "Initial symmetric hash: " + bytesToHex(symmetric!!.handshakeHash!!))
// Hash the prologue value.
Log.d(TAG, "Mixing empty prologue")
if (prologue != null) symmetric!!.mixHash(prologue, 0, prologue!!.size)
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!!.getHandshakeHash()))
Log.d(TAG, "Hash after empty prologue: " + bytesToHex(symmetric!!.handshakeHash!!))
// Hash the pre-shared key into the chaining key and handshake hash.
if (preSharedKey != null) symmetric!!.mixPreSharedKey(preSharedKey)
preSharedKey?.let { symmetric!!.mixPreSharedKey(it) }
// Mix the pre-supplied public keys into the handshake hash.
if (isInitiator) {
Log.d(TAG, "XX pattern - no pre-message keys to mix")
if ((requirements and LOCAL_PREMSG) != 0) symmetric!!.mixPublicKey(localKeyPair)
if ((requirements and LOCAL_PREMSG) != 0) symmetric!!.mixPublicKey(localKeyPair!!)
if ((requirements and FALLBACK_PREMSG) != 0) {
symmetric!!.mixPublicKey(remoteEphemeral)
if (remoteHybrid != null) symmetric!!.mixPublicKey(remoteHybrid)
if (preSharedKey != null) symmetric!!.mixPublicKeyIntoCK(remoteEphemeral)
symmetric!!.mixPublicKey(remoteEphemeral!!)
remoteHybrid?.let { symmetric!!.mixPublicKey(it) }
if (preSharedKey != null) symmetric!!.mixPublicKeyIntoCK(remoteEphemeral!!)
}
if ((requirements and REMOTE_PREMSG) != 0) symmetric!!.mixPublicKey(remotePublicKey)
if ((requirements and REMOTE_PREMSG) != 0) symmetric!!.mixPublicKey(remotePublicKey!!)
} else {
Log.d(TAG, "XX pattern - no pre-message keys to mix")
if ((requirements and REMOTE_PREMSG) != 0) symmetric!!.mixPublicKey(remotePublicKey)
if ((requirements and REMOTE_PREMSG) != 0) symmetric!!.mixPublicKey(remotePublicKey!!)
if ((requirements and FALLBACK_PREMSG) != 0) {
symmetric!!.mixPublicKey(localEphemeral)
if (localHybrid != null) symmetric!!.mixPublicKey(localHybrid)
if (preSharedKey != null) symmetric!!.mixPublicKeyIntoCK(localEphemeral)
symmetric!!.mixPublicKey(localEphemeral!!)
localHybrid?.let { symmetric!!.mixPublicKey(it) }
if (preSharedKey != null) symmetric!!.mixPublicKeyIntoCK(localEphemeral!!)
}
if ((requirements and LOCAL_PREMSG) != 0) symmetric!!.mixPublicKey(localKeyPair)
if ((requirements and LOCAL_PREMSG) != 0) symmetric!!.mixPublicKey(localKeyPair!!)
}
// Log final state after all initialization (matching iOS)
Log.d(TAG, "=== ANDROID HANDSHAKE START - FINAL STATE ===")
Log.d(
TAG,
"Final symmetric hash after mixPreMessageKeys(): " + bytesToHex(symmetric!!.getHandshakeHash())
"Final symmetric hash after mixPreMessageKeys(): " + bytesToHex(symmetric!!.handshakeHash!!)
)
Log.d(TAG, "===========================================")
@ -533,7 +529,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
// Format a new protocol name for the fallback variant
// and recreate the SymmetricState object.
val components =
symmetric!!.getProtocolName().split("_".toRegex()).dropLastWhile { it.isEmpty() }
symmetric!!.protocolName.split("_".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
components[1] = patternName
val builder = StringBuilder()
@ -563,7 +559,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
action = NO_ACTION
pattern = newPattern
patternIndex = 1
var flags = pattern!![0]
var flags = pattern[0]
if (!isInitiator) {
// Reverse the pattern flags so that the responder is "local".
flags = reverseFlags(flags)
@ -584,7 +580,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
*/
private fun mixDH(local: DHState, remote: DHState) {
check(!(local == null || remote == null)) { "Pattern definition error" }
val len = local.getSharedKeyLength()
val len = local.sharedKeyLength
val shared = ByteArray(len)
try {
local.calculate(shared, 0, remote)
@ -643,12 +639,12 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
try {
// Process tokens until the direction changes or the patten ends.
while (true) {
if (patternIndex >= pattern!!.size) {
if (patternIndex >= pattern.size) {
// The pattern has finished, so the next action is "split".
action = SPLIT
break
}
val token = pattern!![patternIndex++]
val token = pattern[patternIndex++]
if (token == Pattern.FLIP_DIR) {
// Change directions, so this message is complete and the
// next action is "read message".
@ -664,9 +660,10 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
// key to the message. If we are running fixed vector tests,
// then the ephemeral key may have already been provided.
checkNotNull(localEphemeral) { "Pattern definition error" }
if (fixedEphemeral == null) localEphemeral!!.generateKeyPair()
else localEphemeral!!.copyFrom(fixedEphemeral)
len = localEphemeral!!.getPublicKeyLength()
val fe = fixedEphemeral
if (fe == null) localEphemeral!!.generateKeyPair()
else localEphemeral!!.copyFrom(fe)
len = localEphemeral!!.publicKeyLength
if (space < len) throw ShortBufferException()
localEphemeral!!.getPublicKey(message, messagePosn)
symmetric!!.mixHash(message, messagePosn, len)
@ -680,8 +677,8 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
Pattern.S -> {
// Encrypt the local static public key and add it to the message.
checkNotNull(localKeyPair) { "Pattern definition error" }
len = localKeyPair!!.getPublicKeyLength()
macLen = symmetric!!.getMACLength()
len = localKeyPair!!.publicKeyLength
macLen = symmetric!!.macLength
if (space < (len + macLen)) throw ShortBufferException()
localKeyPair!!.getPublicKey(message, messagePosn)
messagePosn += symmetric!!.encryptAndHash(
@ -720,19 +717,20 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
// key to the message. If we are running fixed vector tests,
// then a fixed hybrid key may have already been provided.
checkNotNull(localHybrid) { "Pattern definition error" }
if (localHybrid is DHStateHybrid) {
val fh = fixedHybrid
val lh = localHybrid!!
if (lh is DHStateHybrid) {
// The DH object is something like New Hope which needs to
// generate keys relative to the other party's public key.
val hybrid = localHybrid as DHStateHybrid
if (fixedHybrid == null) hybrid.generateKeyPair(remoteHybrid)
else hybrid.copyFrom(fixedHybrid, remoteHybrid)
if (fh == null) lh.generateKeyPair(remoteHybrid)
else lh.copyFrom(fh, remoteHybrid)
} else {
if (fixedHybrid == null) localHybrid!!.generateKeyPair()
else localHybrid!!.copyFrom(fixedHybrid)
if (fh == null) lh.generateKeyPair()
else lh.copyFrom(fh)
}
len = localHybrid!!.getPublicKeyLength()
len = localHybrid!!.publicKeyLength
if (space < len) throw ShortBufferException()
macLen = symmetric!!.getMACLength()
macLen = symmetric!!.macLength
if (space < (len + macLen)) throw ShortBufferException()
localHybrid!!.getPublicKey(message, messagePosn)
messagePosn += symmetric!!.encryptAndHash(
@ -838,12 +836,12 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
try {
// Process tokens until the direction changes or the patten ends.
while (true) {
if (patternIndex >= pattern!!.size) {
if (patternIndex >= pattern.size) {
// The pattern has finished, so the next action is "split".
action = SPLIT
break
}
val token = pattern!![patternIndex++]
val token = pattern[patternIndex++]
if (token == Pattern.FLIP_DIR) {
// Change directions, so this message is complete and the
// next action is "write message".
@ -857,7 +855,7 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
Pattern.E -> {
// Save the remote ephemeral key and hash it.
checkNotNull(remoteEphemeral) { "Pattern definition error" }
len = remoteEphemeral!!.getPublicKeyLength()
len = remoteEphemeral!!.publicKeyLength
if (space < len) throw ShortBufferException()
symmetric!!.mixHash(message, messageOffset, len)
remoteEphemeral!!.setPublicKey(message, messageOffset)
@ -878,8 +876,8 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
Pattern.S -> {
// Decrypt and read the remote static key.
checkNotNull(remotePublicKey) { "Pattern definition error" }
len = remotePublicKey!!.getPublicKeyLength()
macLen = symmetric!!.getMACLength()
len = remotePublicKey!!.publicKeyLength
macLen = symmetric!!.macLength
if (space < (len + macLen)) throw ShortBufferException()
val temp = ByteArray(len)
try {
@ -929,8 +927,8 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
// generated a local hybrid keypair or not.
(remoteHybrid as DHStateHybrid).specifyPeer(localHybrid)
}
len = remoteHybrid!!.getPublicKeyLength()
macLen = symmetric!!.getMACLength()
len = remoteHybrid!!.publicKeyLength
macLen = symmetric!!.macLength
if (space < (len + macLen)) throw ShortBufferException()
val temp = ByteArray(len)
try {
@ -1026,17 +1024,15 @@ class HandshakeState(protocolName: String, role: Int) : Destroyable {
return pair
}
/**
* Current value of the handshake hash, which must not be modified by the caller.
*
* @throws IllegalStateException The action is not SPLIT or COMPLETE.
*/
val handshakeHash: ByteArray?
/**
* Gets the current value of the handshake hash.
*
* @return The handshake hash. This must not be modified by the caller.
*
* @throws IllegalStateException The action is not SPLIT or COMPLETE.
*/
get() {
check(!(action != SPLIT && action != COMPLETE)) { "Handshake has not completed" }
return symmetric!!.getHandshakeHash()
check(action == SPLIT || action == COMPLETE) { "Handshake has not completed" }
return symmetric!!.handshakeHash
}
override fun destroy() {