Compare commits
8 Commits
master
...
github-act
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68e4aeb9d6 | ||
|
|
3782f912d2 | ||
|
|
47e4b6670c | ||
|
|
e6e7db4f37 | ||
|
|
b9777a8848 | ||
|
|
8cbbca6107 | ||
|
|
e72031e4ee | ||
|
|
7a466392bb |
17
.github/workflows/test.yml
vendored
17
.github/workflows/test.yml
vendored
@ -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
|
||||||
|
|||||||
@ -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
118
pom.xml
@ -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>
|
||||||
|
|||||||
@ -130,6 +130,7 @@ 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.
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -190,6 +190,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
try {
|
||||||
digest.digest(result, 0, result.length);
|
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));
|
||||||
|
try {
|
||||||
digest.digest(result, 0, result.length);
|
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);
|
||||||
|
try {
|
||||||
digest = Noise.createHash(name);
|
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");
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
@ -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,18 +289,14 @@ 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.
|
||||||
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return testVectors;
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user