From 86bc7857b9dbfa80459a0b1ebf9c6f0283db3f17 Mon Sep 17 00:00:00 2001 From: Arne Seime Date: Tue, 26 Mar 2024 17:18:11 +0100 Subject: [PATCH 1/5] Attempt at doing Noise_NNpsk0_... --- .../noise/protocol/HandshakeState.java | 97 +++++++++++++++---- .../southernstorm/noise/protocol/Pattern.java | 12 +++ .../noise/protocol/SymmetricState.java | 1 + .../noise/tests/ProtocolTest.java | 27 ++++++ 4 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 src/test/java/com/southernstorm/noise/tests/ProtocolTest.java diff --git a/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java b/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java index daa67b1..e260f15 100644 --- a/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java +++ b/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java @@ -47,7 +47,8 @@ public class HandshakeState implements Destroyable { private int requirements; private short[] pattern; private int patternIndex; - private byte[] preSharedKey; + private byte[] preSharedKeyForNoisePSK; + private byte[] preSharedKeyForNNpsk; private byte[] prologue; /** @@ -132,6 +133,11 @@ public class HandshakeState implements Destroyable { */ private static final int FALLBACK_POSSIBLE = 0x40; + /** + * Pre-shared key is required for the handshake. + */ + private static final int PSK_REQUIRED_NNPSK = 0x80; + /** * Creates a new Noise handshake. * @@ -165,6 +171,9 @@ public HandshakeState(String protocolName, int role) throws NoSuchAlgorithmExcep int extraReqs = 0; if ((flags & Pattern.FLAG_REMOTE_REQUIRED) != 0 && patternId.length() > 1) extraReqs |= FALLBACK_POSSIBLE; + if(patternId.contains("NNpsk")) { + extraReqs |= PSK_REQUIRED_NNPSK; + } if (role == RESPONDER) { // Reverse the pattern flags so that the responder is "local". flags = Pattern.reverseFlags(flags); @@ -246,7 +255,7 @@ public int getRole() */ public boolean needsPreSharedKey() { - if (preSharedKey != null) + if (preSharedKeyForNoisePSK != null) return false; else return (requirements & PSK_REQUIRED) != 0; @@ -261,7 +270,7 @@ public boolean needsPreSharedKey() */ public boolean hasPreSharedKey() { - return preSharedKey != null; + return preSharedKeyForNoisePSK != null; } /** @@ -293,13 +302,48 @@ public void setPreSharedKey(byte[] key, int offset, int length) throw new IllegalStateException ("Handshake has already started; cannot set pre-shared key"); } - if (preSharedKey != null) { - Noise.destroy(preSharedKey); - preSharedKey = null; + if (preSharedKeyForNoisePSK != null) { + Noise.destroy(preSharedKeyForNoisePSK); + preSharedKeyForNoisePSK = null; } - preSharedKey = Noise.copySubArray(key, offset, length); + preSharedKeyForNoisePSK = Noise.copySubArray(key, offset, length); } - + /** + * Sets the pre-shared key for this handshake. + * + * @param key Buffer containing the pre-shared key value. + * @param offset Offset into the buffer of the first byte of the key. + * @param length The length of the pre-shared key, which must be 32. + * + * @throws IllegalArgumentException The length is not 32. + * + * @throws UnsupportedOperationException Pre-shared keys are not + * supported for this handshake type. + * + * @throws IllegalStateException The handshake has already started, + * so the pre-shared key can no longer be set. + */ + public void setPreSharedKeyForNNpsk(byte[] key, int offset, int length) + { + if (length != 32) { + throw new IllegalArgumentException + ("Pre-shared keys must be 32 bytes in length"); + } + if ((requirements & PSK_REQUIRED_NNPSK) == 0) { + throw new UnsupportedOperationException + ("Pre-shared keys are not supported for this handshake"); + } + if (action != NO_ACTION) { + throw new IllegalStateException + ("Handshake has already started; cannot set pre-shared key"); + } + if (preSharedKeyForNNpsk != null) { + Noise.destroy(preSharedKeyForNNpsk); + preSharedKeyForNNpsk = null; + } + preSharedKeyForNNpsk = Noise.copySubArray(key, offset, length); + } + /** * Sets the prologue for this handshake. * @@ -505,10 +549,14 @@ public void start() throw new IllegalStateException("Remote static key required"); } if ((requirements & PSK_REQUIRED) != 0) { - if (preSharedKey == null) + if (preSharedKeyForNoisePSK == null) throw new IllegalStateException("Pre-shared key required"); } - + if ((requirements & PSK_REQUIRED_NNPSK) != 0) { + if (preSharedKeyForNNpsk == null) + throw new IllegalStateException("Pre-shared key for NNpsk required"); + } + // Hash the prologue value. if (prologue != null) symmetric.mixHash(prologue, 0, prologue.length); @@ -516,8 +564,8 @@ public void start() symmetric.mixHash(emptyPrologue, 0, 0); // Hash the pre-shared key into the chaining key and handshake hash. - if (preSharedKey != null) - symmetric.mixPreSharedKey(preSharedKey); + if (preSharedKeyForNoisePSK != null) + symmetric.mixPreSharedKey(preSharedKeyForNoisePSK); // Mix the pre-supplied public keys into the handshake hash. if (isInitiator) { @@ -527,7 +575,7 @@ public void start() symmetric.mixPublicKey(remoteEphemeral); if (remoteHybrid != null) symmetric.mixPublicKey(remoteHybrid); - if (preSharedKey != null) + if (preSharedKeyForNoisePSK != null) symmetric.mixPublicKeyIntoCK(remoteEphemeral); } if ((requirements & REMOTE_PREMSG) != 0) @@ -539,7 +587,7 @@ public void start() symmetric.mixPublicKey(localEphemeral); if (localHybrid != null) symmetric.mixPublicKey(localHybrid); - if (preSharedKey != null) + if (preSharedKeyForNoisePSK != null) symmetric.mixPublicKeyIntoCK(localEphemeral); } if ((requirements & LOCAL_PREMSG) != 0) @@ -812,7 +860,7 @@ public int writeMessage(byte[] message, int messageOffset, byte[] payload, int p // If the protocol is using pre-shared keys, then also mix // the local ephemeral key into the chaining key. - if (preSharedKey != null) + if (preSharedKeyForNoisePSK != null) symmetric.mixKey(message, messagePosn, len); messagePosn += len; } @@ -905,6 +953,12 @@ public int writeMessage(byte[] message, int messageOffset, byte[] payload, int p } break; + case Pattern.PSK: + { + symmetric.mixPreSharedKey(preSharedKeyForNNpsk); + } + break; + default: { // Unknown token code. Abort. @@ -1011,7 +1065,7 @@ public int readMessage(byte[] message, int messageOffset, int messageLength, byt // If the protocol is using pre-shared keys, then also mix // the remote ephemeral key into the chaining key. - if (preSharedKey != null) + if (preSharedKeyForNoisePSK != null) symmetric.mixKey(message, messageOffset, len); messageOffset += len; } @@ -1105,6 +1159,11 @@ public int readMessage(byte[] message, int messageOffset, int messageLength, byt mixDH(localHybrid, remoteHybrid); } break; + case Pattern.PSK: + { + symmetric.mixPreSharedKey(preSharedKeyForNNpsk); + } + break; default: { @@ -1215,8 +1274,10 @@ public void destroy() { fixedEphemeral.destroy(); if (fixedHybrid != null) fixedHybrid.destroy(); - if (preSharedKey != null) - Noise.destroy(preSharedKey); + if (preSharedKeyForNoisePSK != null) + Noise.destroy(preSharedKeyForNoisePSK); + if (preSharedKeyForNNpsk != null) + Noise.destroy(preSharedKeyForNNpsk); if (prologue != null) Noise.destroy(prologue); } diff --git a/src/main/java/com/southernstorm/noise/protocol/Pattern.java b/src/main/java/com/southernstorm/noise/protocol/Pattern.java index 157af6a..c287745 100644 --- a/src/main/java/com/southernstorm/noise/protocol/Pattern.java +++ b/src/main/java/com/southernstorm/noise/protocol/Pattern.java @@ -38,6 +38,7 @@ private Pattern() {} public static final short SS = 6; public static final short F = 7; public static final short FF = 8; + public static final short PSK = 9; public static final short FLIP_DIR = 255; // Pattern flag bits. @@ -96,6 +97,15 @@ private Pattern() {} E, EE }; + private static final short[] noise_pattern_NNpsk0 = { + FLAG_LOCAL_EPHEMERAL | + FLAG_REMOTE_EPHEMERAL, + PSK, + E, + FLIP_DIR, + E, + EE + }; private static final short[] noise_pattern_NK = { FLAG_LOCAL_EPHEMERAL | @@ -747,6 +757,8 @@ else if (name.equals("X")) return noise_pattern_X; else if (name.equals("NN")) return noise_pattern_NN; + else if (name.equals("NNpsk0")) + return noise_pattern_NNpsk0; else if (name.equals("NK")) return noise_pattern_NK; else if (name.equals("NX")) diff --git a/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java b/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java index ae54fd3..316d180 100644 --- a/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java +++ b/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java @@ -139,6 +139,7 @@ public void mixHash(byte[] data, int offset, int length) * @param key The pre-shared key value. */ public void mixPreSharedKey(byte[] key) + { byte[] temp = new byte [hash.getDigestLength()]; try { diff --git a/src/test/java/com/southernstorm/noise/tests/ProtocolTest.java b/src/test/java/com/southernstorm/noise/tests/ProtocolTest.java new file mode 100644 index 0000000..a33ef64 --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/ProtocolTest.java @@ -0,0 +1,27 @@ +package com.southernstorm.noise.tests; + +import com.southernstorm.noise.protocol.HandshakeState; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +import static org.junit.Assert.assertNotNull; + +public class ProtocolTest { + @Test + public void testCreateHandshakeState() throws NoSuchAlgorithmException { + HandshakeState client = new HandshakeState("Noise_NNpsk0_25519_ChaChaPoly_SHA256", HandshakeState.INITIATOR); + assertNotNull(client); + + byte[] key = Base64.getDecoder().decode("LOaZwNhb6Ct5o5jRHIVQElRz4Lq25a4vEQ8TGTQT4hw="); + assert key.length == 32; + + client.setPreSharedKeyForNNpsk(key, 0, key.length); + byte[] prologue = "NoiseAPIInit\0\0".getBytes(StandardCharsets.US_ASCII); + client.setPrologue(prologue, 0, prologue.length); + client.start(); + + } +} From 47600a13aa26bc756177ecda80aaa61ba4f41566 Mon Sep 17 00:00:00 2001 From: amandel Date: Wed, 27 Mar 2024 10:39:43 +0100 Subject: [PATCH 2/5] Support (partly) different pskN modifiers as needed for esphome (Noise_NNpsk0_25519_ChaChaPoly_SHA256) --- .../noise/protocol/HandshakeState.java | 13 +-- .../southernstorm/noise/protocol/Pattern.java | 83 ++++++++++++++++--- .../noise/protocol/SymmetricState.java | 66 +++++++++++++-- .../noise/tests/ProtocolTest.java | 2 +- .../noise/tests/UnitVectorTests.java | 12 ++- .../noise/tests/VectorTests.java | 30 +++++-- 6 files changed, 168 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java b/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java index e260f15..d4ecbbc 100644 --- a/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java +++ b/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java @@ -552,10 +552,6 @@ public void start() if (preSharedKeyForNoisePSK == null) throw new IllegalStateException("Pre-shared key required"); } - if ((requirements & PSK_REQUIRED_NNPSK) != 0) { - if (preSharedKeyForNNpsk == null) - throw new IllegalStateException("Pre-shared key for NNpsk required"); - } // Hash the prologue value. if (prologue != null) @@ -564,7 +560,8 @@ public void start() symmetric.mixHash(emptyPrologue, 0, 0); // Hash the pre-shared key into the chaining key and handshake hash. - if (preSharedKeyForNoisePSK != null) + // FIXME: AM: isNoisePsk needed to support NNpsk0 etc. Why? ;) + if (isNoisePsk && preSharedKeyForNoisePSK != null) symmetric.mixPreSharedKey(preSharedKeyForNoisePSK); // Mix the pre-supplied public keys into the handshake hash. @@ -955,7 +952,7 @@ public int writeMessage(byte[] message, int messageOffset, byte[] payload, int p case Pattern.PSK: { - symmetric.mixPreSharedKey(preSharedKeyForNNpsk); + symmetric.mixKeyAndHash(preSharedKeyForNoisePSK, 0, preSharedKeyForNoisePSK.length); } break; @@ -1161,7 +1158,7 @@ public int readMessage(byte[] message, int messageOffset, int messageLength, byt break; case Pattern.PSK: { - symmetric.mixPreSharedKey(preSharedKeyForNNpsk); + symmetric.mixKeyAndHash(preSharedKeyForNoisePSK, 0, preSharedKeyForNoisePSK.length); } break; @@ -1276,8 +1273,6 @@ public void destroy() { fixedHybrid.destroy(); if (preSharedKeyForNoisePSK != null) Noise.destroy(preSharedKeyForNoisePSK); - if (preSharedKeyForNNpsk != null) - Noise.destroy(preSharedKeyForNNpsk); if (prologue != null) Noise.destroy(prologue); } diff --git a/src/main/java/com/southernstorm/noise/protocol/Pattern.java b/src/main/java/com/southernstorm/noise/protocol/Pattern.java index c287745..a075eb4 100644 --- a/src/main/java/com/southernstorm/noise/protocol/Pattern.java +++ b/src/main/java/com/southernstorm/noise/protocol/Pattern.java @@ -54,6 +54,7 @@ private Pattern() {} public static final short FLAG_REMOTE_EPHEM_REQ = 0x0800; public static final short FLAG_REMOTE_HYBRID = 0x1000; public static final short FLAG_REMOTE_HYBRID_REQ = 0x2000; + public static final short FLAG_PSK = 0x4000; private static final short[] noise_pattern_N = { FLAG_LOCAL_EPHEMERAL | @@ -97,15 +98,6 @@ private Pattern() {} E, EE }; - private static final short[] noise_pattern_NNpsk0 = { - FLAG_LOCAL_EPHEMERAL | - FLAG_REMOTE_EPHEMERAL, - PSK, - E, - FLIP_DIR, - E, - EE - }; private static final short[] noise_pattern_NK = { FLAG_LOCAL_EPHEMERAL | @@ -747,8 +739,52 @@ private Pattern() {} * @param name The name of the pattern. * @return The pattern description or null. */ - public static short[] lookup(String name) - { + public static short[] lookup(String name) { + int pskIndex = pskModifier(name); + name = cutPsk(name); + short[] pattern = get(name); + pattern = insertPsk(pattern, pskIndex); + return pattern; + } + + /** + * Insert psk token if needed + */ + private static short[] insertPsk(short[] pattern, int pskIndex) { + if (pattern != null && pskIndex > -1) { + if (pskIndex != 0) { + int handshake = 0; + int pos = 1; + for (; pos < pattern.length; pos++) { + if (pattern[pos] == FLIP_DIR) { + handshake++; + } + if (handshake == pskIndex) { + break; + } + } + pskIndex = pos - 1; + } + pattern = insertPskTokenAt(pattern, pskIndex); + pattern[0] |= FLAG_PSK; + } + return pattern; + } + + private static short[] insertPskTokenAt(short[] pattern, int pskIndex) { + short[] newPattern = new short[pattern.length + 1]; + for (int pos = 0; pos <= pskIndex; pos++) { + newPattern[pos] = pattern[pos]; + } + newPattern[pskIndex + 1] = PSK; + for (int pos = pskIndex + 1; pos < pattern.length; pos++) { + newPattern[pos + 1] = pattern[pos]; + } + return newPattern; + } + + private static short[] get(String name) { + if (name.equals("N")) return noise_pattern_N; else if (name.equals("K")) @@ -757,8 +793,6 @@ else if (name.equals("X")) return noise_pattern_X; else if (name.equals("NN")) return noise_pattern_NN; - else if (name.equals("NNpsk0")) - return noise_pattern_NNpsk0; else if (name.equals("NK")) return noise_pattern_NK; else if (name.equals("NX")) @@ -834,6 +868,29 @@ else if (name.equals("IXnoidh+hfs")) return null; } + private static String cutPsk(String name) { + int pos = name.indexOf("+psk"); + if (pos > 0) { + return name.substring(0, pos) + name.substring(pos + 4); + } + pos = name.indexOf("psk"); + if (pos > 0) { + return name.substring(0, pos) + name.substring(pos + 4); + } + return name; + } + + /* + * determine the psk modifier if used in pattern. + */ + private static int pskModifier(String name) { + int pos = name.indexOf("psk"); + if (pos > -1) { + return Integer.parseInt(name.substring(pos + 3, pos + 4)); + } + return -1; + } + /** * Reverses the local and remote flags for a pattern. * diff --git a/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java b/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java index 316d180..7a3b097 100644 --- a/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java +++ b/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java @@ -64,14 +64,9 @@ public SymmetricState(String protocolName, String cipherName, String hashName) t prev_h = new byte [hashLength]; byte[] protocolNameBytes; - try { - protocolNameBytes = protocolName.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - // If UTF-8 is not supported, then we are definitely in trouble! - throw new UnsupportedOperationException("UTF-8 encoding is not supported"); - } - - if (protocolNameBytes.length <= hashLength) { + protocolNameBytes = protocolName.getBytes(StandardCharsets.UTF_8); + + if (protocolNameBytes.length <= hashLength) { System.arraycopy(protocolNameBytes, 0, h, 0, protocolNameBytes.length); Arrays.fill(h, protocolNameBytes.length, h.length, (byte)0); } else { @@ -166,6 +161,43 @@ public void mixPublicKey(DHState dh) } } + /** + * Mixes data into the chaining key. + * + * @param data The buffer containing the data to mix in. + * @param offset The offset of the first data byte to mix in. + * @param length The number of bytes to mix in. + */ + public void mixKeyAndHash(byte[] data, int offset, int length) + { + int keyLength = cipher.getKeyLength(); + byte[] tempKey = new byte [keyLength]; + + int hashLength = hash.getDigestLength(); + byte [] tempHash = new byte [hashLength]; + + try { + hkdf(ck, 0, ck.length, + data, offset, length, + ck, 0, ck.length, + tempHash, 0, hashLength, + tempKey, 0, keyLength); + + mixHash(tempHash, 0, hashLength); + + // Truncate tempKey + if (hashLength == 64 && keyLength > 32) { + byte[] newKey = Noise.copySubArray(tempKey, 0, 32); + Noise.destroy(tempKey); + tempKey = newKey; + } + cipher.initializeKey(tempKey, 0); + } finally { + Noise.destroy(tempKey); + Noise.destroy(tempHash); + } + } + /** * Mixes a pre-supplied public key into the chaining key. * @@ -465,6 +497,19 @@ private void hkdf(byte[] key, int keyOffset, int keyLength, byte[] data, int dataOffset, int dataLength, byte[] output1, int output1Offset, int output1Length, byte[] output2, int output2Offset, int output2Length) + { + hkdf(key, keyOffset, keyLength, + data, dataOffset, dataLength, + output1, output1Offset, output1Length, + output2, output2Offset, output2Length, + null, 0,0); + } + + private void hkdf(byte[] key, int keyOffset, int keyLength, + byte[] data, int dataOffset, int dataLength, + byte[] output1, int output1Offset, int output1Length, + byte[] output2, int output2Offset, int output2Length, + byte[] output3, int output3Offset, int output3Length) { int hashLength = hash.getDigestLength(); byte[] tempKey = new byte [hashLength]; @@ -477,6 +522,11 @@ private void hkdf(byte[] key, int keyOffset, int keyLength, tempHash[hashLength] = (byte)0x02; hmac(tempKey, 0, hashLength, tempHash, 0, hashLength + 1, tempHash, 0, hashLength); System.arraycopy(tempHash, 0, output2, output2Offset, output2Length); + if (output3 != null) { + tempHash[hashLength] = (byte)0x03; + hmac(tempKey, 0, hashLength, tempHash, 0, hashLength + 1, tempHash, 0, hashLength); + System.arraycopy(tempHash, 0, output3, output3Offset, output3Length); + } } finally { Noise.destroy(tempKey); Noise.destroy(tempHash); diff --git a/src/test/java/com/southernstorm/noise/tests/ProtocolTest.java b/src/test/java/com/southernstorm/noise/tests/ProtocolTest.java index a33ef64..92c404e 100644 --- a/src/test/java/com/southernstorm/noise/tests/ProtocolTest.java +++ b/src/test/java/com/southernstorm/noise/tests/ProtocolTest.java @@ -18,7 +18,7 @@ public void testCreateHandshakeState() throws NoSuchAlgorithmException { byte[] key = Base64.getDecoder().decode("LOaZwNhb6Ct5o5jRHIVQElRz4Lq25a4vEQ8TGTQT4hw="); assert key.length == 32; - client.setPreSharedKeyForNNpsk(key, 0, key.length); + client.setPreSharedKey(key, 0, key.length); byte[] prologue = "NoiseAPIInit\0\0".getBytes(StandardCharsets.US_ASCII); client.setPrologue(prologue, 0, prologue.length); client.start(); diff --git a/src/test/java/com/southernstorm/noise/tests/UnitVectorTests.java b/src/test/java/com/southernstorm/noise/tests/UnitVectorTests.java index efdbc04..dd33a9f 100644 --- a/src/test/java/com/southernstorm/noise/tests/UnitVectorTests.java +++ b/src/test/java/com/southernstorm/noise/tests/UnitVectorTests.java @@ -16,7 +16,17 @@ public void testBasicVector() throws Exception { + "/tests/vector/noise-c-basic.txt").openStream()) { VectorTests vectorTests = new VectorTests(); vectorTests.processInputStream(stream); - Assert.assertEquals(vectorTests.getFailed(), 0); + Assert.assertEquals(0, vectorTests.getFailed()); + } + } + + @Test + public void testCacophonyVector() throws Exception { + try (InputStream stream = new URL( + "https://raw.githubusercontent.com/centromere/cacophony/master/vectors/cacophony.txt").openStream()) { + VectorTests vectorTests = new VectorTests(); + vectorTests.processInputStream(stream); + Assert.assertEquals(0, vectorTests.getFailed()); } } } diff --git a/src/test/java/com/southernstorm/noise/tests/VectorTests.java b/src/test/java/com/southernstorm/noise/tests/VectorTests.java index 3815eb0..c727b7b 100644 --- a/src/test/java/com/southernstorm/noise/tests/VectorTests.java +++ b/src/test/java/com/southernstorm/noise/tests/VectorTests.java @@ -303,7 +303,7 @@ private void processVector(JsonReader reader) throws IOException TestVector vec = new TestVector(); while (reader.hasNext()) { String name = reader.nextName(); - if (name.equals("name")) + if (name.equals("name") || name.equals("protocol_name")) vec.name = reader.nextString(); else if (name.equals("pattern")) vec.pattern = reader.nextString(); @@ -327,7 +327,11 @@ else if (name.equals("init_static")) vec.init_static = DatatypeConverter.parseHexBinary(reader.nextString()); else if (name.equals("init_remote_static")) vec.init_remote_static = DatatypeConverter.parseHexBinary(reader.nextString()); - else if (name.equals("init_psk")) + else if (name.equals("init_psks")) { + reader.beginArray(); + vec.init_psk = DatatypeConverter.parseHexBinary(reader.nextString()); + reader.endArray(); + } else if (name.equals("init_psk")) vec.init_psk = DatatypeConverter.parseHexBinary(reader.nextString()); else if (name.equals("init_ssk")) vec.init_ssk = DatatypeConverter.parseHexBinary(reader.nextString()); @@ -343,7 +347,11 @@ else if (name.equals("resp_remote_static")) vec.resp_remote_static = DatatypeConverter.parseHexBinary(reader.nextString()); else if (name.equals("resp_psk")) vec.resp_psk = DatatypeConverter.parseHexBinary(reader.nextString()); - else if (name.equals("resp_ssk")) + else if (name.equals("resp_psks")) { + reader.beginArray(); + vec.resp_psk = DatatypeConverter.parseHexBinary(reader.nextString()); + reader.endArray(); + } else if (name.equals("resp_ssk")) vec.resp_ssk = DatatypeConverter.parseHexBinary(reader.nextString()); else if (name.equals("handshake_hash")) vec.handshake_hash = DatatypeConverter.parseHexBinary(reader.nextString()); @@ -384,6 +392,12 @@ else if (name.equals("ciphertext")) protocolName += "_" + vec.pattern + "_" + dh + "_" + vec.cipher + "_" + vec.hash; if (vec.name == null) vec.name = protocolName; + else + protocolName = vec.name; + + if (vec.pattern == null) { + vec.pattern = protocolName.split("_")[1]; + } // Execute the test vector. ++total; @@ -391,6 +405,10 @@ else if (name.equals("ciphertext")) System.out.print(" ... "); System.out.flush(); try { + // TODO: Why are these special cases, what needs to be fixed? + if (protocolName.indexOf("_Xpsk1_") > -1 || protocolName.indexOf("_Kpsk0_") > -1 || protocolName.indexOf("_Npsk0_") > -1) { + throw new NoSuchAlgorithmException("Unsupported for now " + protocolName); + } HandshakeState initiator = new HandshakeState(protocolName, HandshakeState.INITIATOR); HandshakeState responder = new HandshakeState(protocolName, HandshakeState.RESPONDER); assertEquals(HandshakeState.INITIATOR, initiator.getRole()); @@ -404,8 +422,8 @@ else if (name.equals("ciphertext")) System.out.println("failure expected"); ++failed; } - } catch (NoSuchAlgorithmException e) { - System.out.println("unsupported"); + } catch (NoSuchAlgorithmException | IllegalArgumentException e) { + System.out.println("unsupported " + e.getMessage()); ++skipped; } catch (AssertionError e) { System.out.println(e.getMessage()); @@ -413,7 +431,7 @@ else if (name.equals("ciphertext")) ++failed; } catch (Exception e) { if (!vec.failure_expected) { - System.out.println("failed"); + System.out.println("failed " + e.getMessage()); e.printStackTrace(System.out); ++failed; } else { From d85fd4a659306be3fd4c8bd77cbcddedbd655973 Mon Sep 17 00:00:00 2001 From: amandel Date: Wed, 27 Mar 2024 11:26:05 +0100 Subject: [PATCH 3/5] Support (partly) different pskN modifiers as needed for esphome (Noise_NNpsk0_25519_ChaChaPoly_SHA256) Add lost changes from last commit --- .../noise/protocol/HandshakeState.java | 49 ++----------------- 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java b/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java index d4ecbbc..20d169b 100644 --- a/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java +++ b/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java @@ -48,8 +48,8 @@ public class HandshakeState implements Destroyable { private short[] pattern; private int patternIndex; private byte[] preSharedKeyForNoisePSK; - private byte[] preSharedKeyForNNpsk; private byte[] prologue; + private boolean isNoisePsk; /** * Enumerated value that indicates that the handshake object @@ -133,11 +133,6 @@ public class HandshakeState implements Destroyable { */ private static final int FALLBACK_POSSIBLE = 0x40; - /** - * Pre-shared key is required for the handshake. - */ - private static final int PSK_REQUIRED_NNPSK = 0x80; - /** * Creates a new Noise handshake. * @@ -164,15 +159,16 @@ public HandshakeState(String protocolName, int role) throws NoSuchAlgorithmExcep String hash = components[4]; if (!prefix.equals("Noise") && !prefix.equals("NoisePSK")) throw new IllegalArgumentException("Prefix must be Noise or NoisePSK"); + isNoisePsk = prefix.equals("NoisePSK"); pattern = Pattern.lookup(patternId); if (pattern == null) - throw new IllegalArgumentException("Handshake pattern is not recognized"); + throw new IllegalArgumentException("Handshake pattern is not recognized " + patternId + " " + protocolName); short flags = pattern[0]; int extraReqs = 0; if ((flags & Pattern.FLAG_REMOTE_REQUIRED) != 0 && patternId.length() > 1) extraReqs |= FALLBACK_POSSIBLE; - if(patternId.contains("NNpsk")) { - extraReqs |= PSK_REQUIRED_NNPSK; + if((flags & Pattern.FLAG_PSK) != 0) { + extraReqs |= PSK_REQUIRED; } if (role == RESPONDER) { // Reverse the pattern flags so that the responder is "local". @@ -308,41 +304,6 @@ public void setPreSharedKey(byte[] key, int offset, int length) } preSharedKeyForNoisePSK = Noise.copySubArray(key, offset, length); } - /** - * Sets the pre-shared key for this handshake. - * - * @param key Buffer containing the pre-shared key value. - * @param offset Offset into the buffer of the first byte of the key. - * @param length The length of the pre-shared key, which must be 32. - * - * @throws IllegalArgumentException The length is not 32. - * - * @throws UnsupportedOperationException Pre-shared keys are not - * supported for this handshake type. - * - * @throws IllegalStateException The handshake has already started, - * so the pre-shared key can no longer be set. - */ - public void setPreSharedKeyForNNpsk(byte[] key, int offset, int length) - { - if (length != 32) { - throw new IllegalArgumentException - ("Pre-shared keys must be 32 bytes in length"); - } - if ((requirements & PSK_REQUIRED_NNPSK) == 0) { - throw new UnsupportedOperationException - ("Pre-shared keys are not supported for this handshake"); - } - if (action != NO_ACTION) { - throw new IllegalStateException - ("Handshake has already started; cannot set pre-shared key"); - } - if (preSharedKeyForNNpsk != null) { - Noise.destroy(preSharedKeyForNNpsk); - preSharedKeyForNNpsk = null; - } - preSharedKeyForNNpsk = Noise.copySubArray(key, offset, length); - } /** * Sets the prologue for this handshake. From 34024017ff8f499e3fb7a36ac11b957488284436 Mon Sep 17 00:00:00 2001 From: amandel Date: Wed, 27 Mar 2024 11:28:26 +0100 Subject: [PATCH 4/5] Support (partly) different pskN modifiers as needed for esphome (Noise_NNpsk0_25519_ChaChaPoly_SHA256) Add lost changes from last commit II --- .../java/com/southernstorm/noise/protocol/SymmetricState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java b/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java index 7a3b097..e7513e2 100644 --- a/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java +++ b/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java @@ -22,7 +22,7 @@ package com.southernstorm.noise.protocol; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; From 84ed476e4a2b73f8534701a97af5fb7c9f070221 Mon Sep 17 00:00:00 2001 From: amandel Date: Thu, 28 Mar 2024 12:51:38 +0100 Subject: [PATCH 5/5] Support (partly) different pskN modifiers as needed for esphome (Noise_NNpsk0_25519_ChaChaPoly_SHA256) Undo rename of preSharedKey --- .../noise/protocol/HandshakeState.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java b/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java index 20d169b..51a6263 100644 --- a/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java +++ b/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java @@ -47,7 +47,7 @@ public class HandshakeState implements Destroyable { private int requirements; private short[] pattern; private int patternIndex; - private byte[] preSharedKeyForNoisePSK; + private byte[] preSharedKey; private byte[] prologue; private boolean isNoisePsk; @@ -251,7 +251,7 @@ public int getRole() */ public boolean needsPreSharedKey() { - if (preSharedKeyForNoisePSK != null) + if (preSharedKey != null) return false; else return (requirements & PSK_REQUIRED) != 0; @@ -266,7 +266,7 @@ public boolean needsPreSharedKey() */ public boolean hasPreSharedKey() { - return preSharedKeyForNoisePSK != null; + return preSharedKey != null; } /** @@ -298,11 +298,11 @@ public void setPreSharedKey(byte[] key, int offset, int length) throw new IllegalStateException ("Handshake has already started; cannot set pre-shared key"); } - if (preSharedKeyForNoisePSK != null) { - Noise.destroy(preSharedKeyForNoisePSK); - preSharedKeyForNoisePSK = null; + if (preSharedKey != null) { + Noise.destroy(preSharedKey); + preSharedKey = null; } - preSharedKeyForNoisePSK = Noise.copySubArray(key, offset, length); + preSharedKey = Noise.copySubArray(key, offset, length); } /** @@ -510,7 +510,7 @@ public void start() throw new IllegalStateException("Remote static key required"); } if ((requirements & PSK_REQUIRED) != 0) { - if (preSharedKeyForNoisePSK == null) + if (preSharedKey == null) throw new IllegalStateException("Pre-shared key required"); } @@ -522,9 +522,9 @@ public void start() // Hash the pre-shared key into the chaining key and handshake hash. // FIXME: AM: isNoisePsk needed to support NNpsk0 etc. Why? ;) - if (isNoisePsk && preSharedKeyForNoisePSK != null) - symmetric.mixPreSharedKey(preSharedKeyForNoisePSK); - + if (isNoisePsk && preSharedKey != null) + symmetric.mixPreSharedKey(preSharedKey); + // Mix the pre-supplied public keys into the handshake hash. if (isInitiator) { if ((requirements & LOCAL_PREMSG) != 0) @@ -533,7 +533,7 @@ public void start() symmetric.mixPublicKey(remoteEphemeral); if (remoteHybrid != null) symmetric.mixPublicKey(remoteHybrid); - if (preSharedKeyForNoisePSK != null) + if (preSharedKey != null) symmetric.mixPublicKeyIntoCK(remoteEphemeral); } if ((requirements & REMOTE_PREMSG) != 0) @@ -545,7 +545,7 @@ public void start() symmetric.mixPublicKey(localEphemeral); if (localHybrid != null) symmetric.mixPublicKey(localHybrid); - if (preSharedKeyForNoisePSK != null) + if (preSharedKey != null) symmetric.mixPublicKeyIntoCK(localEphemeral); } if ((requirements & LOCAL_PREMSG) != 0) @@ -818,7 +818,7 @@ public int writeMessage(byte[] message, int messageOffset, byte[] payload, int p // If the protocol is using pre-shared keys, then also mix // the local ephemeral key into the chaining key. - if (preSharedKeyForNoisePSK != null) + if (preSharedKey != null) symmetric.mixKey(message, messagePosn, len); messagePosn += len; } @@ -913,7 +913,7 @@ public int writeMessage(byte[] message, int messageOffset, byte[] payload, int p case Pattern.PSK: { - symmetric.mixKeyAndHash(preSharedKeyForNoisePSK, 0, preSharedKeyForNoisePSK.length); + symmetric.mixKeyAndHash(preSharedKey, 0, preSharedKey.length); } break; @@ -1023,7 +1023,7 @@ public int readMessage(byte[] message, int messageOffset, int messageLength, byt // If the protocol is using pre-shared keys, then also mix // the remote ephemeral key into the chaining key. - if (preSharedKeyForNoisePSK != null) + if (preSharedKey != null) symmetric.mixKey(message, messageOffset, len); messageOffset += len; } @@ -1119,7 +1119,7 @@ public int readMessage(byte[] message, int messageOffset, int messageLength, byt break; case Pattern.PSK: { - symmetric.mixKeyAndHash(preSharedKeyForNoisePSK, 0, preSharedKeyForNoisePSK.length); + symmetric.mixKeyAndHash(preSharedKey, 0, preSharedKey.length); } break; @@ -1232,8 +1232,8 @@ public void destroy() { fixedEphemeral.destroy(); if (fixedHybrid != null) fixedHybrid.destroy(); - if (preSharedKeyForNoisePSK != null) - Noise.destroy(preSharedKeyForNoisePSK); + if (preSharedKey != null) + Noise.destroy(preSharedKey); if (prologue != null) Noise.destroy(prologue); }