Compare commits

..

8 Commits

Author SHA1 Message Date
Jon Chambers
68e4aeb9d6 Update artifact coordinates and README.
Some checks failed
Build/test / JDK ${{ matrix.java }} (11, ubuntu-20.04) (push) Has been cancelled
Build/test / JDK ${{ matrix.java }} (13, ubuntu-20.04) (push) Has been cancelled
Build/test / JDK ${{ matrix.java }} (15, ubuntu-20.04) (push) Has been cancelled
Build/test / JDK ${{ matrix.java }} (8, ubuntu-20.04) (push) Has been cancelled
2021-07-21 10:54:54 -04:00
Jon Chambers
3782f912d2 Build/test with GitHub Actions. 2021-07-21 10:45:57 -04:00
Jon Chambers
47e4b6670c Move Javadoc generation to Maven. 2021-07-21 10:27:00 -04:00
Jon Chambers
e6e7db4f37 Include test vectors as a local resource.
This eliminates an implicit test dependency on network connectivity.
2021-07-21 10:17:29 -04:00
Jon Chambers
b9777a8848 Move jaxb-api to the test scope. 2021-07-21 10:11:37 -04:00
Jon Chambers
8cbbca6107 Update to the latest version of maven-surefire-plugin. 2021-07-21 10:11:32 -04:00
Jon Chambers
e72031e4ee Fix an undeclared dependency on JUnit 5. 2021-07-21 09:53:19 -04:00
Jon Chambers
7a466392bb Add IntelliJ project files/directories to .gitignore. 2021-07-21 09:50:17 -04:00
16 changed files with 340 additions and 262 deletions

View File

@ -7,18 +7,23 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-22.04] os: [ubuntu-20.04]
java: [8, 11, 17, 21] java: [8, 11, 13, 15]
fail-fast: false fail-fast: false
name: JDK ${{ matrix.java }} name: JDK ${{ matrix.java }}
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@v1
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 uses: actions/setup-java@v1
with: with:
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
distribution: 'temurin' - name: Cache local Maven repository
cache: maven uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Test with Maven - name: Test with Maven
run: mvn verify -B --file pom.xml run: mvn verify -B --file pom.xml

View File

@ -4,8 +4,8 @@ this fork is to make Noise-Java available via common artifact repositories like
as pull requests upstream wherever possible. as pull requests upstream wherever possible.
To avoid namespace collisions, the group identifier in this project's POM has To avoid namespace collisions, the group identifier in this project's POM has
been changed to `org.signal.forks`, though package names within the artifact been changed to `org.signal`, though package names within the artifact have not
have not changed. changed.
The original README continues below. The original README continues below.

118
pom.xml
View File

@ -1,43 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<modelVersion>4.0.0</modelVersion> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>org.signal.forks</groupId>
<artifactId>noise-java</artifactId> <artifactId>noise-java</artifactId>
<version>0.1.2-SNAPSHOT</version> <groupId>org.signal</groupId>
<version>0.1-SNAPSHOT</version>
<name>Noise-Java</name> <modelVersion>4.0.0</modelVersion>
<description>Plain Java implementation of the Noise protocol</description>
<inceptionYear>2016</inceptionYear>
<url>https://github.com/signalapp/noise-java</url>
<scm>
<connection>scm:git:https://github.com/signalapp/noise-java.git</connection>
<developerConnection>scm:git:git@github.com:signalapp/noise-java.git</developerConnection>
<url>https://github.com/signalapp/noise-java</url>
<tag>HEAD</tag>
</scm>
<developers>
<developer>
<id>rweather</id>
<name>Rhys Weatherley</name>
<email>rhys.weatherley@gmail.com</email>
<url>https://github.com/rweather/</url>
<roles>
<role>developer</role>
</roles>
</developer>
</developers>
<licenses>
<license>
<name>The MIT License (MIT)</name>
<url>http://opensource.org/licenses/MIT</url>
<distribution>repo</distribution>
</license>
</licenses>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -45,9 +13,9 @@
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>junit</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>junit</artifactId>
<version>5.10.2</version> <version>4.13.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -57,7 +25,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<pluginManagement> <pluginManagement>
<plugins> <plugins>
@ -93,74 +60,7 @@
<excludePackageNames>com.southernstorm.noise.crypto</excludePackageNames> <excludePackageNames>com.southernstorm.noise.crypto</excludePackageNames>
<windowtitle>Noise-Java</windowtitle> <windowtitle>Noise-Java</windowtitle>
</configuration> </configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.13</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>false</autoReleaseAfterClose>
</configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<profiles>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project> </project>

View File

@ -130,7 +130,8 @@ class AESGCMFallbackCipherState implements CipherState {
iv[13] = 0; iv[13] = 0;
iv[14] = 0; iv[14] = 0;
iv[15] = 1; iv[15] = 1;
++n;
// Encrypt a block of zeroes to generate the hash key to XOR // Encrypt a block of zeroes to generate the hash key to XOR
// the GHASH tag with at the end of the encrypt/decrypt operation. // the GHASH tag with at the end of the encrypt/decrypt operation.
Arrays.fill(hashKey, (byte)0); Arrays.fill(hashKey, (byte)0);
@ -206,7 +207,6 @@ class AESGCMFallbackCipherState implements CipherState {
ghash.finish(ciphertext, ciphertextOffset + length, 16); ghash.finish(ciphertext, ciphertextOffset + length, 16);
for (int index = 0; index < 16; ++index) for (int index = 0; index < 16; ++index)
ciphertext[ciphertextOffset + length + index] ^= hashKey[index]; ciphertext[ciphertextOffset + length + index] ^= hashKey[index];
n += 1;
return length + 16; return length + 16;
} }
@ -247,7 +247,6 @@ class AESGCMFallbackCipherState implements CipherState {
if ((temp & 0xFF) != 0) if ((temp & 0xFF) != 0)
Noise.throwBadTagException(); Noise.throwBadTagException();
encryptCTR(ciphertext, ciphertextOffset, plaintext, plaintextOffset, dataLen); encryptCTR(ciphertext, ciphertextOffset, plaintext, plaintextOffset, dataLen);
n += 1;
return dataLen; return dataLen;
} }

View File

@ -190,7 +190,8 @@ class AESGCMOnCtrCipherState implements CipherState {
iv[13] = 0; iv[13] = 0;
iv[14] = 0; iv[14] = 0;
iv[15] = 1; iv[15] = 1;
++n;
// Initialize the CTR mode cipher with the key and IV. // Initialize the CTR mode cipher with the key and IV.
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv)); cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv));
@ -254,7 +255,6 @@ class AESGCMOnCtrCipherState implements CipherState {
ghash.finish(ciphertext, ciphertextOffset + length, 16); ghash.finish(ciphertext, ciphertextOffset + length, 16);
for (int index = 0; index < 16; ++index) for (int index = 0; index < 16; ++index)
ciphertext[ciphertextOffset + length + index] ^= hashKey[index]; ciphertext[ciphertextOffset + length + index] ^= hashKey[index];
n += 1;
return length + 16; return length + 16;
} }
@ -312,7 +312,6 @@ class AESGCMOnCtrCipherState implements CipherState {
// Shouldn't happen. // Shouldn't happen.
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
n += 1;
return dataLen; return dataLen;
} }

View File

@ -138,7 +138,7 @@ class ChaChaPolyCipherState implements CipherState {
{ {
if (n == -1L) if (n == -1L)
throw new IllegalStateException("Nonce has wrapped around"); throw new IllegalStateException("Nonce has wrapped around");
ChaChaCore.initIV(input, n); ChaChaCore.initIV(input, n++);
ChaChaCore.hash(output, input); ChaChaCore.hash(output, input);
Arrays.fill(polyKey, (byte)0); Arrays.fill(polyKey, (byte)0);
xorBlock(polyKey, 0, polyKey, 0, 32, output); xorBlock(polyKey, 0, polyKey, 0, 32, output);
@ -234,7 +234,6 @@ class ChaChaPolyCipherState implements CipherState {
poly.update(ciphertext, ciphertextOffset, length); poly.update(ciphertext, ciphertextOffset, length);
finish(ad, length); finish(ad, length);
System.arraycopy(polyKey, 0, ciphertext, ciphertextOffset + length, 16); System.arraycopy(polyKey, 0, ciphertext, ciphertextOffset + length, 16);
n += 1;
return length + 16; return length + 16;
} }
@ -274,7 +273,6 @@ class ChaChaPolyCipherState implements CipherState {
if ((temp & 0xFF) != 0) if ((temp & 0xFF) != 0)
Noise.throwBadTagException(); Noise.throwBadTagException();
encrypt(ciphertext, ciphertextOffset, plaintext, plaintextOffset, dataLen); encrypt(ciphertext, ciphertextOffset, plaintext, plaintextOffset, dataLen);
n += 1;
return dataLen; return dataLen;
} }

View File

@ -30,7 +30,7 @@ import java.io.Reader;
* *
* Intentionally compatible with android.util.JsonReader. * Intentionally compatible with android.util.JsonReader.
*/ */
public class JsonReader implements AutoCloseable { public class JsonReader {
private Reader in; private Reader in;
private JsonToken token; private JsonToken token;
@ -54,7 +54,6 @@ public class JsonReader implements AutoCloseable {
expectNext(JsonToken.BEGIN_OBJECT, "JSON begin object expected"); expectNext(JsonToken.BEGIN_OBJECT, "JSON begin object expected");
} }
@Override
public void close() throws IOException { public void close() throws IOException {
in.close(); in.close();
} }

View File

@ -22,17 +22,18 @@
package com.southernstorm.noise.tests; package com.southernstorm.noise.tests;
import static org.junit.Assert.*;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.Arrays;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.ShortBufferException; import javax.crypto.ShortBufferException;
import org.junit.Test;
import com.southernstorm.noise.protocol.CipherState; import com.southernstorm.noise.protocol.CipherState;
import com.southernstorm.noise.protocol.Noise; import com.southernstorm.noise.protocol.Noise;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* Perform tests on the cipher algorithms used by Noise. * Perform tests on the cipher algorithms used by Noise.
@ -42,35 +43,51 @@ public class CipherStateTests {
private void testCipher(String name, int keyLen, int macLen, private void testCipher(String name, int keyLen, int macLen,
String key, long nonce, String ad, String key, long nonce, String ad,
String plaintext, String ciphertext, String plaintext, String ciphertext,
String mac, boolean forceFallbacks) throws NoSuchAlgorithmException, ShortBufferException, BadPaddingException { String mac, boolean forceFallbacks)
{
byte[] keyBytes = TestUtils.stringToData(key); byte[] keyBytes = TestUtils.stringToData(key);
byte[] adBytes = TestUtils.stringToData(ad); byte[] adBytes = TestUtils.stringToData(ad);
byte[] plaintextBytes = TestUtils.stringToData(plaintext); byte[] plaintextBytes = TestUtils.stringToData(plaintext);
byte[] ciphertextBytes; byte[] ciphertextBytes;
byte[] buffer;
if (ciphertext.length() > 0) if (ciphertext.length() > 0)
ciphertextBytes = TestUtils.stringToData(ciphertext + mac.substring(2)); ciphertextBytes = TestUtils.stringToData(ciphertext + mac.substring(2));
else else
ciphertextBytes = TestUtils.stringToData(mac); ciphertextBytes = TestUtils.stringToData(mac);
final byte[] plaintextBuffer = new byte[plaintextBytes.length];
final byte[] ciphertextBuffer = new byte[ciphertextBytes.length];
// Create the cipher object and check its properties. // Create the cipher object and check its properties.
CipherState cipher = null;
Noise.setForceFallbacks(forceFallbacks); Noise.setForceFallbacks(forceFallbacks);
final CipherState cipher = Noise.createCipher(name); try {
cipher = Noise.createCipher(name);
} catch (NoSuchAlgorithmException e) {
fail(name + " cipher is not supported");
}
assertEquals(name, cipher.getCipherName()); assertEquals(name, cipher.getCipherName());
assertEquals(keyLen, cipher.getKeyLength()); assertEquals(keyLen, cipher.getKeyLength());
assertEquals(0, cipher.getMACLength()); // Key has not been set yet. assertEquals(0, cipher.getMACLength()); // Key has not been set yet.
// Try to encrypt. Because the key is not set yet, this will // Try to encrypt. Because the key is not set yet, this will
// return the plaintext as-is. // return the plaintext as-is.
Arrays.fill(plaintextBuffer, (byte)0xAA); try {
assertEquals(plaintextBytes.length, cipher.encryptWithAd(adBytes, plaintextBytes, 0, plaintextBuffer, 0, plaintextBytes.length)); buffer = new byte [plaintextBytes.length];
assertArrayEquals(plaintextBytes, plaintextBuffer); Arrays.fill(buffer, (byte)0xAA);
assertEquals(plaintextBytes.length, cipher.encryptWithAd(adBytes, plaintextBytes, 0, buffer, 0, plaintextBytes.length));
assertArrayEquals(plaintextBytes, buffer);
} catch (ShortBufferException e) {
fail("Buffer should have been big enough");
}
// Try to decrypt. Will return the ciphertext and MAC as-is. // Try to decrypt. Will return the ciphertext and MAC as-is.
Arrays.fill(ciphertextBuffer, (byte)0xAA); buffer = new byte [ciphertextBytes.length];
assertEquals(ciphertextBytes.length, cipher.decryptWithAd(adBytes, ciphertextBytes, 0, ciphertextBuffer, 0, ciphertextBytes.length)); Arrays.fill(buffer, (byte)0xAA);
try {
assertEquals(ciphertextBytes.length, cipher.decryptWithAd(adBytes, ciphertextBytes, 0, buffer, 0, ciphertextBytes.length));
} catch (BadPaddingException e) {
fail();
} catch (ShortBufferException e) {
fail();
}
// Set the key and fast-forward the nonce. // Set the key and fast-forward the nonce.
cipher.initializeKey(keyBytes, 0); cipher.initializeKey(keyBytes, 0);
@ -78,24 +95,39 @@ public class CipherStateTests {
assertEquals(macLen, cipher.getMACLength()); assertEquals(macLen, cipher.getMACLength());
// Encrypt the data. // Encrypt the data.
Arrays.fill(ciphertextBuffer, (byte)0xAA); try {
assertEquals(ciphertextBytes.length, cipher.encryptWithAd(adBytes, plaintextBytes, 0, ciphertextBuffer, 0, plaintextBytes.length)); buffer = new byte [ciphertextBytes.length];
assertArrayEquals(ciphertextBytes, ciphertextBuffer); Arrays.fill(buffer, (byte)0xAA);
assertEquals(ciphertextBytes.length, cipher.encryptWithAd(adBytes, plaintextBytes, 0, buffer, 0, plaintextBytes.length));
assertArrayEquals(ciphertextBytes, buffer);
} catch (ShortBufferException e) {
fail("Buffer should have been big enough");
}
// Try to decrypt. The MAC check should fail because the internal // Try to decrypt. The MAC check should fail because the internal
// nonce was incremented and no longer matches the parameter. // nonce was incremented and no longer matches the parameter.
assertThrows(BadPaddingException.class, () -> try {
cipher.decryptWithAd(adBytes, ciphertextBytes, 0, ciphertextBuffer, 0, ciphertextBytes.length)); cipher.decryptWithAd(adBytes, ciphertextBytes, 0, buffer, 0, ciphertextBytes.length);
fail();
} catch (BadPaddingException e) {
// Success!
} catch (ShortBufferException e) {
fail();
}
// Fast-forward the nonce to just before the rollover. We will be able // Fast-forward the nonce to just before the rollover. We will be able
// to encrypt one more block, and then the next request will be rejected. // to encrypt one more block, and then the next request will be rejected.
cipher.setNonce(-2L); cipher.setNonce(-2L);
try { try {
Arrays.fill(ciphertextBuffer, (byte)0xAA); buffer = new byte [ciphertextBytes.length];
cipher.encryptWithAd(adBytes, plaintextBytes, 0, ciphertextBuffer, 0, plaintextBytes.length); Arrays.fill(buffer, (byte)0xAA);
cipher.encryptWithAd(adBytes, plaintextBytes, 0, buffer, 0, plaintextBytes.length);
assertThrows(IllegalStateException.class, () -> try {
cipher.encryptWithAd(adBytes, plaintextBytes, 0, ciphertextBuffer, 0, plaintextBytes.length)); cipher.encryptWithAd(adBytes, plaintextBytes, 0, buffer, 0, plaintextBytes.length);
fail();
} catch (IllegalStateException e) {
// Success!
}
} catch (ShortBufferException e) { } catch (ShortBufferException e) {
fail("Buffer should have been big enough"); fail("Buffer should have been big enough");
} }
@ -106,34 +138,53 @@ public class CipherStateTests {
assertEquals(macLen, cipher.getMACLength()); assertEquals(macLen, cipher.getMACLength());
// Decrypt the test ciphertext and MAC. // Decrypt the test ciphertext and MAC.
Arrays.fill(plaintextBuffer, (byte)0xAA); try {
assertEquals(plaintextBytes.length, cipher.decryptWithAd(adBytes, ciphertextBytes, 0, plaintextBuffer, 0, ciphertextBytes.length)); buffer = new byte [plaintextBytes.length];
assertArrayEquals(plaintextBytes, plaintextBuffer); Arrays.fill(buffer, (byte)0xAA);
assertEquals(plaintextBytes.length, cipher.decryptWithAd(adBytes, ciphertextBytes, 0, buffer, 0, ciphertextBytes.length));
assertArrayEquals(plaintextBytes, buffer);
} catch (BadPaddingException e) {
fail();
} catch (ShortBufferException e) {
fail();
}
// Fast-forward the nonce to just before the rollover. We will be able // Fast-forward the nonce to just before the rollover. We will be able
// to decrypt one more block, and then the next request will be rejected. // to decrypt one more block, and then the next request will be rejected.
cipher.setNonce(-2L); cipher.setNonce(-2L);
try {
Arrays.fill(plaintextBuffer, (byte)0xAA); buffer = new byte [plaintextBytes.length];
assertThrows(BadPaddingException.class, () -> Arrays.fill(buffer, (byte)0xAA);
cipher.decryptWithAd(adBytes, ciphertextBytes, 0, plaintextBuffer, 0, ciphertextBytes.length)); try {
cipher.decryptWithAd(adBytes, ciphertextBytes, 0, buffer, 0, ciphertextBytes.length);
cipher.setNonce(-1L); fail();
} catch (BadPaddingException e) {
assertThrows(IllegalStateException.class, () -> // Success!
cipher.decryptWithAd(adBytes, ciphertextBytes, 0, plaintextBuffer, 0, ciphertextBytes.length)); }
try {
cipher.decryptWithAd(adBytes, ciphertextBytes, 0, buffer, 0, ciphertextBytes.length);
fail();
} catch (IllegalStateException e) {
// Success!
} catch (BadPaddingException e) {
fail();
}
} catch (ShortBufferException e) {
fail("Buffer should have been big enough");
}
} }
private void testCipher(String name, int keyLen, int macLen, private void testCipher(String name, int keyLen, int macLen,
String key, long nonce, String ad, String key, long nonce, String ad,
String plaintext, String ciphertext, String plaintext, String ciphertext,
String mac) throws ShortBufferException, NoSuchAlgorithmException, BadPaddingException { String mac)
{
testCipher(name, keyLen, macLen, key, nonce, ad, plaintext, ciphertext, mac, true); testCipher(name, keyLen, macLen, key, nonce, ad, plaintext, ciphertext, mac, true);
testCipher(name, keyLen, macLen, key, nonce, ad, plaintext, ciphertext, mac, false); testCipher(name, keyLen, macLen, key, nonce, ad, plaintext, ciphertext, mac, false);
} }
@Test @Test
public void AESGCM() throws ShortBufferException, NoSuchAlgorithmException, BadPaddingException { public void AESGCM() {
/* Test vectors for AES in GCM mode from Appendix B of: /* Test vectors for AES in GCM mode from Appendix B of:
http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
We can only use a few of the vectors because most of the IV's in the We can only use a few of the vectors because most of the IV's in the
@ -161,7 +212,7 @@ public class CipherStateTests {
} }
@Test @Test
public void ChaChaPoly() throws ShortBufferException, NoSuchAlgorithmException, BadPaddingException { public void ChaChaPoly() {
// ChaChaPoly test vectors from Appendix A.5 of RFC 7539. // ChaChaPoly test vectors from Appendix A.5 of RFC 7539.
testCipher testCipher
("ChaChaPoly", 32, 16, ("ChaChaPoly", 32, 16,

View File

@ -22,12 +22,13 @@
package com.southernstorm.noise.tests; package com.southernstorm.noise.tests;
import static org.junit.Assert.*;
import java.util.Arrays; import java.util.Arrays;
import com.southernstorm.noise.crypto.Curve25519; import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import com.southernstorm.noise.crypto.Curve25519;
public class Curve25519Tests { public class Curve25519Tests {

View File

@ -22,12 +22,13 @@
package com.southernstorm.noise.tests; package com.southernstorm.noise.tests;
import static org.junit.Assert.*;
import java.util.Arrays; import java.util.Arrays;
import com.southernstorm.noise.crypto.Curve448; import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import com.southernstorm.noise.crypto.Curve448;
public class Curve448Tests { public class Curve448Tests {

View File

@ -22,12 +22,13 @@
package com.southernstorm.noise.tests; package com.southernstorm.noise.tests;
import static org.junit.Assert.*;
import java.util.Arrays; import java.util.Arrays;
import com.southernstorm.noise.crypto.GHASH; import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import com.southernstorm.noise.crypto.GHASH;
public class GHASHTests { public class GHASHTests {

View File

@ -22,21 +22,23 @@
package com.southernstorm.noise.tests; package com.southernstorm.noise.tests;
import static org.junit.Assert.*;
import java.security.DigestException; import java.security.DigestException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import com.southernstorm.noise.protocol.Noise; import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import com.southernstorm.noise.protocol.Noise;
/** /**
* Perform tests on the hash algorithms used by Noise. * Perform tests on the hash algorithms used by Noise.
*/ */
public class HashTests { public class HashTests {
private void testHash(MessageDigest digest, String input, String hash) throws DigestException { private void testHash(MessageDigest digest, String input, String hash)
{
byte[] inputBytes = TestUtils.stringToData(input); byte[] inputBytes = TestUtils.stringToData(input);
byte[] hashBytes = TestUtils.stringToData(hash); byte[] hashBytes = TestUtils.stringToData(hash);
byte[] result = new byte [digest.getDigestLength()]; byte[] result = new byte [digest.getDigestLength()];
@ -44,36 +46,50 @@ public class HashTests {
// Hash the entire input in one request. // Hash the entire input in one request.
digest.reset(); digest.reset();
digest.update(inputBytes); digest.update(inputBytes);
digest.digest(result, 0, result.length); try {
digest.digest(result, 0, result.length);
} catch (DigestException e) {
fail("digest failed");
}
assertArrayEquals(hashBytes, result); assertArrayEquals(hashBytes, result);
// Hash the input in pieces to test split requests. // Hash the input in pieces to test split requests.
digest.reset(); digest.reset();
digest.update(inputBytes, 0, inputBytes.length / 2); digest.update(inputBytes, 0, inputBytes.length / 2);
digest.update(inputBytes, inputBytes.length / 2, inputBytes.length - (inputBytes.length / 2)); digest.update(inputBytes, inputBytes.length / 2, inputBytes.length - (inputBytes.length / 2));
digest.digest(result, 0, result.length); try {
digest.digest(result, 0, result.length);
} catch (DigestException e) {
fail("digest failed");
}
assertArrayEquals(hashBytes, result); assertArrayEquals(hashBytes, result);
} }
private void testHash(String name, String input, String hash) throws NoSuchAlgorithmException, DigestException { private void testHash(String name, String input, String hash)
{
MessageDigest digest = null; MessageDigest digest = null;
Noise.setForceFallbacks(true); Noise.setForceFallbacks(true);
try { try {
digest = Noise.createHash(name); digest = Noise.createHash(name);
} catch (NoSuchAlgorithmException e) {
fail("No crypto provider for " + name);
} finally { } finally {
Noise.setForceFallbacks(false); Noise.setForceFallbacks(false);
} }
testHash(digest, input, hash); testHash(digest, input, hash);
Noise.setForceFallbacks(false); Noise.setForceFallbacks(false);
digest = Noise.createHash(name); try {
digest = Noise.createHash(name);
} catch (NoSuchAlgorithmException e) {
fail("No crypto provider for " + name);
}
testHash(digest, input, hash); testHash(digest, input, hash);
} }
@Test @Test
public void blake2b() throws DigestException, NoSuchAlgorithmException { public void blake2b() {
testHash("BLAKE2b", "", "0x786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"); testHash("BLAKE2b", "", "0x786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce");
testHash("BLAKE2b", "abc", "0xba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"); testHash("BLAKE2b", "abc", "0xba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923");
testHash("BLAKE2b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "0x7285ff3e8bd768d69be62b3bf18765a325917fa9744ac2f582a20850bc2b1141ed1b3e4528595acc90772bdf2d37dc8a47130b44f33a02e8730e5ad8e166e888"); testHash("BLAKE2b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "0x7285ff3e8bd768d69be62b3bf18765a325917fa9744ac2f582a20850bc2b1141ed1b3e4528595acc90772bdf2d37dc8a47130b44f33a02e8730e5ad8e166e888");
@ -81,7 +97,7 @@ public class HashTests {
} }
@Test @Test
public void blake2s() throws DigestException, NoSuchAlgorithmException { public void blake2s() {
testHash("BLAKE2s", "", "0x69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9"); testHash("BLAKE2s", "", "0x69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9");
testHash("BLAKE2s", "abc", "0x508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982"); testHash("BLAKE2s", "abc", "0x508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982");
testHash("BLAKE2s", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "0x6f4df5116a6f332edab1d9e10ee87df6557beab6259d7663f3bcd5722c13f189"); testHash("BLAKE2s", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "0x6f4df5116a6f332edab1d9e10ee87df6557beab6259d7663f3bcd5722c13f189");
@ -89,14 +105,14 @@ public class HashTests {
} }
@Test @Test
public void sha256() throws DigestException, NoSuchAlgorithmException { public void sha256() {
testHash("SHA256", "", "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); testHash("SHA256", "", "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
testHash("SHA256", "abc", "0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); testHash("SHA256", "abc", "0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
testHash("SHA256", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "0x248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"); testHash("SHA256", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "0x248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");
} }
@Test @Test
public void sha512() throws DigestException, NoSuchAlgorithmException { public void sha512() {
testHash("SHA512", "", "0xcf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); testHash("SHA512", "", "0xcf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");
testHash("SHA512", "abc", "0xddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"); testHash("SHA512", "abc", "0xddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f");
testHash("SHA512", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", "0x8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"); testHash("SHA512", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", "0x8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909");

View File

@ -22,12 +22,13 @@
package com.southernstorm.noise.tests; package com.southernstorm.noise.tests;
import static org.junit.Assert.*;
import java.util.Arrays; import java.util.Arrays;
import com.southernstorm.noise.crypto.Poly1305; import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import com.southernstorm.noise.crypto.Poly1305;
/** /**
* Perform tests on the Poly1305 implementation in isolation from ChaChaPoly. * Perform tests on the Poly1305 implementation in isolation from ChaChaPoly.

View File

@ -22,12 +22,13 @@
package com.southernstorm.noise.tests; package com.southernstorm.noise.tests;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.Assert.*;
import java.util.Arrays; import java.util.Arrays;
import org.junit.Test;
import com.southernstorm.noise.crypto.RijndaelAES; import com.southernstorm.noise.crypto.RijndaelAES;
import org.junit.jupiter.api.Test;
/** /**
* AES test cases to verify the fallback RijndaelAES implementation. * AES test cases to verify the fallback RijndaelAES implementation.

View File

@ -0,0 +1,17 @@
package com.southernstorm.noise.tests;
import java.io.InputStream;
import org.junit.Assert;
import org.junit.Test;
public class UnitVectorTests {
@Test
public void testBasicVector() throws Exception {
try (final InputStream stream = getClass().getResourceAsStream("test-vectors.json")) {
VectorTests vectorTests = new VectorTests();
vectorTests.processInputStream(stream);
Assert.assertEquals(vectorTests.getFailed(), 0);
}
}
}

View File

@ -22,14 +22,16 @@
package com.southernstorm.noise.tests; package com.southernstorm.noise.tests;
import static org.junit.Assert.*;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.ShortBufferException; import javax.crypto.ShortBufferException;
@ -39,22 +41,27 @@ import com.southernstorm.json.JsonReader;
import com.southernstorm.noise.protocol.CipherState; import com.southernstorm.noise.protocol.CipherState;
import com.southernstorm.noise.protocol.CipherStatePair; import com.southernstorm.noise.protocol.CipherStatePair;
import com.southernstorm.noise.protocol.HandshakeState; import com.southernstorm.noise.protocol.HandshakeState;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* Executes Noise vector tests in JSON format. * Executes Noise vector tests in JSON format.
*/ */
public class VectorTests { public class VectorTests {
private int total;
private int failed;
private int skipped;
public VectorTests()
{
total = 0;
failed = 0;
skipped = 0;
}
/** /**
* Information about a handshake or transport message. * Information about a handshake or transport message.
*/ */
private static class TestMessage private class TestMessage
{ {
public byte[] payload; public byte[] payload;
public byte[] ciphertext; public byte[] ciphertext;
@ -63,7 +70,7 @@ public class VectorTests {
/** /**
* Information about a Noise test vector that was parsed from a JSON stream. * Information about a Noise test vector that was parsed from a JSON stream.
*/ */
private static class TestVector private class TestVector
{ {
public String name; public String name;
public String pattern; public String pattern;
@ -90,7 +97,7 @@ public class VectorTests {
public boolean failure_expected; public boolean failure_expected;
public boolean fallback_expected; public boolean fallback_expected;
public TestMessage[] messages; public TestMessage[] messages;
public void addMessage(TestMessage msg) public void addMessage(TestMessage msg)
{ {
TestMessage[] newMessages; TestMessage[] newMessages;
@ -109,20 +116,18 @@ public class VectorTests {
private void assertSubArrayEquals(String msg, byte[] expected, byte[] actual) private void assertSubArrayEquals(String msg, byte[] expected, byte[] actual)
{ {
for (int index = 0; index < expected.length; ++index) for (int index = 0; index < expected.length; ++index)
assertEquals(expected[index], actual[index], msg + "[" + index + "]"); assertEquals(msg + "[" + Integer.toString(index) + "]", expected[index], actual[index]);
} }
@ParameterizedTest /**
@MethodSource * Runs a Noise test vector.
void testVectors(TestVector vec) throws ShortBufferException, BadPaddingException, NoSuchAlgorithmException *
* @param vec The test vector.
* @param initiator Handshake object for the initiator.
* @param responder Handshake object for the responder.
*/
private void runTest(TestVector vec, HandshakeState initiator, HandshakeState responder) throws ShortBufferException, BadPaddingException, NoSuchAlgorithmException
{ {
HandshakeState initiator = new HandshakeState(vec.name, HandshakeState.INITIATOR);
HandshakeState responder = new HandshakeState(vec.name, HandshakeState.RESPONDER);
assertEquals(HandshakeState.INITIATOR, initiator.getRole());
assertEquals(HandshakeState.RESPONDER, responder.getRole());
assertEquals(vec.name, initiator.getProtocolName());
assertEquals(vec.name, responder.getProtocolName());
// Set all keys and special values that we need. // Set all keys and special values that we need.
if (vec.init_prologue != null) if (vec.init_prologue != null)
initiator.setPrologue(vec.init_prologue, 0, vec.init_prologue.length); initiator.setPrologue(vec.init_prologue, 0, vec.init_prologue.length);
@ -164,13 +169,12 @@ public class VectorTests {
// Work through the messages one by one until both sides "split". // Work through the messages one by one until both sides "split".
int role = HandshakeState.INITIATOR; int role = HandshakeState.INITIATOR;
int index = 0; int index = 0;
HandshakeState send, recv;
boolean isOneWay = (vec.pattern.length() == 1); boolean isOneWay = (vec.pattern.length() == 1);
boolean fallback = vec.fallback_expected; boolean fallback = vec.fallback_expected;
byte[] message = new byte [8192]; byte[] message = new byte [8192];
byte[] plaintext = new byte [8192]; byte[] plaintext = new byte [8192];
for (; index < vec.messages.length; ++index) { for (; index < vec.messages.length; ++index) {
final HandshakeState send, recv;
if (initiator.getAction() == HandshakeState.SPLIT && if (initiator.getAction() == HandshakeState.SPLIT &&
responder.getAction() == HandshakeState.SPLIT) { responder.getAction() == HandshakeState.SPLIT) {
break; break;
@ -192,11 +196,15 @@ public class VectorTests {
TestMessage msg = vec.messages[index]; TestMessage msg = vec.messages[index];
int len = send.writeMessage(message, 0, msg.payload, 0, msg.payload.length); int len = send.writeMessage(message, 0, msg.payload, 0, msg.payload.length);
assertEquals(msg.ciphertext.length, len); assertEquals(msg.ciphertext.length, len);
assertSubArrayEquals(index + ": ciphertext", msg.ciphertext, message); assertSubArrayEquals(Integer.toString(index) + ": ciphertext", msg.ciphertext, message);
if (fallback) { if (fallback) {
// Perform a read on the responder, which will fail. // Perform a read on the responder, which will fail.
assertThrows(BadPaddingException.class, () -> recv.readMessage(message, 0, len, plaintext, 0), try {
"read should have triggered fallback"); recv.readMessage(message, 0, len, plaintext, 0);
fail("read should have triggered fallback");
} catch (BadPaddingException e) {
// Success!
}
// Look up the pattern to fall back to. // Look up the pattern to fall back to.
String pattern = vec.fallback_pattern; String pattern = vec.fallback_pattern;
@ -216,7 +224,7 @@ public class VectorTests {
} else { } else {
int plen = recv.readMessage(message, 0, len, plaintext, 0); int plen = recv.readMessage(message, 0, len, plaintext, 0);
assertEquals(msg.payload.length, plen); assertEquals(msg.payload.length, plen);
assertSubArrayEquals(index + ": payload", msg.payload, plaintext); assertSubArrayEquals(Integer.toString(index) + ": payload", msg.payload, plaintext);
} }
} }
if (vec.fallback_expected) { if (vec.fallback_expected) {
@ -268,10 +276,10 @@ public class VectorTests {
} }
int len = csend.encryptWithAd(null, msg.payload, 0, message, 0, msg.payload.length); int len = csend.encryptWithAd(null, msg.payload, 0, message, 0, msg.payload.length);
assertEquals(msg.ciphertext.length, len); assertEquals(msg.ciphertext.length, len);
assertSubArrayEquals(index + ": ciphertext", msg.ciphertext, message); assertSubArrayEquals(Integer.toString(index) + ": ciphertext", msg.ciphertext, message);
int plen = crecv.decryptWithAd(null, message, 0, plaintext, 0, len); int plen = crecv.decryptWithAd(null, message, 0, plaintext, 0, len);
assertEquals(msg.payload.length, plen); assertEquals(msg.payload.length, plen);
assertSubArrayEquals(index + ": payload", msg.payload, plaintext); assertSubArrayEquals(Integer.toString(index) + ": payload", msg.payload, plaintext);
} }
// Clean up. // Clean up.
@ -281,20 +289,16 @@ public class VectorTests {
respPair.destroy(); respPair.destroy();
} }
private static Stream<Arguments> testVectors() throws IOException { /**
try (InputStream testVectorInputStream = VectorTests.class.getResourceAsStream("test-vectors.json")) { * Processes a single test vector from an input stream.
if (testVectorInputStream == null) { *
throw new IOException("Could not load test vectors"); * @param reader The JSON reader for the input stream.
} *
* The reader is positioned on the first field of the vector object.
return loadTestVectors(testVectorInputStream).stream() */
.map(testVector -> Arguments.of(Named.of(testVector.name, testVector))); private void processVector(JsonReader reader) throws IOException
}
}
private static TestVector getNextVector(final JsonReader reader) throws IOException
{ {
boolean res = true; boolean res = true;
// Parse the contents of the test vector. // Parse the contents of the test vector.
TestVector vec = new TestVector(); TestVector vec = new TestVector();
while (reader.hasNext()) { while (reader.hasNext()) {
@ -381,31 +385,116 @@ public class VectorTests {
if (vec.name == null) if (vec.name == null)
vec.name = protocolName; vec.name = protocolName;
return vec; // Execute the test vector.
} ++total;
System.out.print(vec.name);
private static List<TestVector> loadTestVectors(InputStream jsonInputStream) throws IOException { System.out.print(" ... ");
List<TestVector> testVectors = new ArrayList<>(); System.out.flush();
try {
try (JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(jsonInputStream)))) { HandshakeState initiator = new HandshakeState(protocolName, HandshakeState.INITIATOR);
jsonReader.beginObject(); HandshakeState responder = new HandshakeState(protocolName, HandshakeState.RESPONDER);
while (jsonReader.hasNext()) { assertEquals(HandshakeState.INITIATOR, initiator.getRole());
String name = jsonReader.nextName(); assertEquals(HandshakeState.RESPONDER, responder.getRole());
if (name.equals("vectors")) { assertEquals(protocolName, initiator.getProtocolName());
jsonReader.beginArray(); assertEquals(protocolName, responder.getProtocolName());
while (jsonReader.hasNext()) { runTest(vec, initiator, responder);
jsonReader.beginObject(); if (!vec.failure_expected) {
testVectors.add(getNextVector(jsonReader)); System.out.println("ok");
jsonReader.endObject();
}
jsonReader.endArray();
} else { } else {
jsonReader.skipValue(); System.out.println("failure expected");
++failed;
}
} catch (NoSuchAlgorithmException e) {
System.out.println("unsupported");
++skipped;
} catch (AssertionError e) {
System.out.println(e.getMessage());
e.printStackTrace(System.out);
++failed;
} catch (Exception e) {
if (!vec.failure_expected) {
System.out.println("failed");
e.printStackTrace(System.out);
++failed;
} else {
System.out.println("ok");
} }
} }
jsonReader.endObject(); }
public void processFile(String filename) throws IOException {
try {
try (FileInputStream fileStream = new FileInputStream(filename)) {
System.out.print(filename + ": ");
processInputStream(fileStream);
}
} catch (FileNotFoundException e) {
System.err.println(filename + ": File not found");
}
}
public void processInputStream(InputStream jsonInputStream) throws IOException {
try(Reader streamReader = new BufferedReader(new InputStreamReader(jsonInputStream))) {
processReader(streamReader);
}
}
public void processReader(Reader jsonStream) throws IOException {
total = 0;
skipped = 0;
failed = 0;
JsonReader reader = new JsonReader(jsonStream);
try {
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
if (name.equals("vectors")) {
reader.beginArray();
while (reader.hasNext() /*&& total < 50*/) {
reader.beginObject();
processVector(reader);
reader.endObject();
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
} catch (IOException e) {
System.err.println("Exception while parsing JSON: " + e.toString());
e.printStackTrace();
} finally {
reader.close();
}
System.out.print(total);
System.out.print(" tests, ");
System.out.print(skipped);
System.out.print(" skipped, ");
System.out.print(failed);
System.out.println(" failed");
} }
return testVectors;
public int getTotal() {
return total;
} }
public int getFailed() {
return failed;
}
public int getSkipped() {
return skipped;
}
public static void main(String[] args) throws IOException {
if (args.length == 0) {
System.out.println("Usage: VectorTests file1 file2 ...");
return;
}
VectorTests app = new VectorTests();
for (String filename : args)
app.processFile(filename);
}
} }