Test harness for running JSON format vector tests
This commit is contained in:
parent
cb9ea54fd7
commit
cb4e98cdd6
@ -267,7 +267,7 @@ class AESGCMOnCtrCipherState implements CipherState {
|
||||
int temp = 0;
|
||||
for (int index = 0; index < 16; ++index)
|
||||
temp |= (hashKey[index] ^ iv[index] ^ ciphertext[ciphertextOffset + dataLen + index]);
|
||||
if (temp != 0)
|
||||
if ((temp & 0xFF) != 0)
|
||||
throw new AEADBadTagException();
|
||||
try {
|
||||
int result = cipher.update(ciphertext, ciphertextOffset, dataLen, plaintext, plaintextOffset);
|
||||
|
||||
@ -270,7 +270,7 @@ class ChaChaPolyCipherState implements CipherState {
|
||||
int temp = 0;
|
||||
for (int index = 0; index < 16; ++index)
|
||||
temp |= (polyKey[index] ^ ciphertext[ciphertextOffset + dataLen + index]);
|
||||
if (temp != 0)
|
||||
if ((temp & 0xFF) != 0)
|
||||
throw new AEADBadTagException();
|
||||
encrypt(ciphertext, ciphertextOffset, plaintext, plaintextOffset, dataLen);
|
||||
return dataLen;
|
||||
|
||||
@ -96,7 +96,7 @@ class Curve25519DHState implements DHState {
|
||||
|
||||
@Override
|
||||
public void setPrivateKey(byte[] key, int offset) {
|
||||
System.arraycopy(key, offset, publicKey, 0, 32);
|
||||
System.arraycopy(key, offset, privateKey, 0, 32);
|
||||
Curve25519.eval(publicKey, 0, privateKey, null);
|
||||
mode = 0x03;
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ class Curve448DHState implements DHState {
|
||||
|
||||
@Override
|
||||
public void setPrivateKey(byte[] key, int offset) {
|
||||
System.arraycopy(key, offset, publicKey, 0, 56);
|
||||
System.arraycopy(key, offset, privateKey, 0, 56);
|
||||
Curve448.eval(publicKey, 0, privateKey, null);
|
||||
mode = 0x03;
|
||||
}
|
||||
|
||||
@ -437,7 +437,7 @@ public class HandshakeState implements Destroyable {
|
||||
throw new IllegalStateException("Remote static key required");
|
||||
}
|
||||
if ((requirements & PSK_REQUIRED) != 0) {
|
||||
if (preSharedKey != null)
|
||||
if (preSharedKey == null)
|
||||
throw new IllegalStateException("Pre-shared key required");
|
||||
}
|
||||
|
||||
@ -468,7 +468,7 @@ public class HandshakeState implements Destroyable {
|
||||
symmetric.mixPublicKey(localEphemeral);
|
||||
}
|
||||
|
||||
// The handshake has official started - set the first action.
|
||||
// The handshake has officially started - set the first action.
|
||||
if (isInitiator)
|
||||
action = WRITE_MESSAGE;
|
||||
else
|
||||
@ -552,7 +552,7 @@ public class HandshakeState implements Destroyable {
|
||||
// Reverse the pattern flags so that the responder is "local".
|
||||
flags = Pattern.reverseFlags(flags);
|
||||
}
|
||||
requirements = computeRequirements(flags, components[0], isInitiator ? INITIATOR : RESPONDER, false);
|
||||
requirements = computeRequirements(flags, components[0], isInitiator ? INITIATOR : RESPONDER, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -952,14 +952,11 @@ public class HandshakeState implements Destroyable {
|
||||
*
|
||||
* @return The handshake hash. This must not be modified by the caller.
|
||||
*
|
||||
* The handshake hash value is only of use to the application after
|
||||
* split() has been called.
|
||||
*
|
||||
* @throws IllegalStateException The action is not COMPLETE.
|
||||
* @throws IllegalStateException The action is not SPLIT or COMPLETE.
|
||||
*/
|
||||
public byte[] getHandshakeHash()
|
||||
{
|
||||
if (action != COMPLETE) {
|
||||
if (action != SPLIT && action != COMPLETE) {
|
||||
throw new IllegalStateException
|
||||
("Handshake has not completed");
|
||||
}
|
||||
|
||||
@ -34,13 +34,14 @@ import javax.crypto.ShortBufferException;
|
||||
/**
|
||||
* Symmetric state for helping manage a Noise handshake.
|
||||
*/
|
||||
public class SymmetricState implements Destroyable {
|
||||
class SymmetricState implements Destroyable {
|
||||
|
||||
private String name;
|
||||
private CipherState cipher;
|
||||
private MessageDigest hash;
|
||||
private byte[] ck;
|
||||
private byte[] h;
|
||||
private byte[] prev_h;
|
||||
|
||||
/**
|
||||
* Constructs a new symmetric state object.
|
||||
@ -57,10 +58,10 @@ public class SymmetricState implements Destroyable {
|
||||
name = protocolName;
|
||||
cipher = Noise.createCipher(cipherName);
|
||||
hash = Noise.createHash(hashName);
|
||||
int keyLength = cipher.getKeyLength();
|
||||
ck = new byte [keyLength];
|
||||
int hashLength = hash.getDigestLength();
|
||||
ck = new byte [hashLength];
|
||||
h = new byte [hashLength];
|
||||
prev_h = new byte [hashLength];
|
||||
|
||||
byte[] protocolNameBytes;
|
||||
try {
|
||||
@ -77,7 +78,7 @@ public class SymmetricState implements Destroyable {
|
||||
hashOne(protocolNameBytes, 0, protocolNameBytes.length, h, 0, h.length);
|
||||
}
|
||||
|
||||
System.arraycopy(h, 0, ck, 0, keyLength);
|
||||
System.arraycopy(h, 0, ck, 0, hashLength);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -218,8 +219,9 @@ public class SymmetricState implements Destroyable {
|
||||
*/
|
||||
public int decryptAndHash(byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, int length) throws ShortBufferException, AEADBadTagException
|
||||
{
|
||||
System.arraycopy(h, 0, prev_h, 0, h.length);
|
||||
mixHash(ciphertext, ciphertextOffset, length);
|
||||
return cipher.decryptWithAd(h, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||
return cipher.decryptWithAd(prev_h, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,6 +316,10 @@ public class SymmetricState implements Destroyable {
|
||||
Noise.destroy(h);
|
||||
h = null;
|
||||
}
|
||||
if (prev_h != null) {
|
||||
Noise.destroy(prev_h);
|
||||
prev_h = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -406,7 +412,7 @@ public class SymmetricState implements Destroyable {
|
||||
hash.reset();
|
||||
hash.update(block, 0, blockLength);
|
||||
hash.update(data, dataOffset, dataLength);
|
||||
hash.digest(output, outputOffset, outputLength);
|
||||
hash.digest(output, outputOffset, hashLength);
|
||||
for (index = 0; index < blockLength; ++index)
|
||||
block[index] ^= (byte)(0x36 ^ 0x5C);
|
||||
hash.reset();
|
||||
|
||||
370
NoiseJavaTests/src/com/southernstorm/json/JsonReader.java
Normal file
370
NoiseJavaTests/src/com/southernstorm/json/JsonReader.java
Normal file
@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.southernstorm.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* Recursive-descent parser for JSON streams.
|
||||
*
|
||||
* Intentionally compatible with android.util.JsonReader.
|
||||
*/
|
||||
public class JsonReader {
|
||||
|
||||
private Reader in;
|
||||
private JsonToken token;
|
||||
private boolean lenient;
|
||||
private boolean booleanValue;
|
||||
private String stringValue;
|
||||
private int ungetCh;
|
||||
|
||||
public JsonReader(Reader in) {
|
||||
this.in = in;
|
||||
this.token = JsonToken.READ_FIRST;
|
||||
this.lenient = false;
|
||||
this.ungetCh = -2;
|
||||
}
|
||||
|
||||
public void beginArray() throws IOException {
|
||||
expectNext(JsonToken.BEGIN_ARRAY, "JSON begin array expected");
|
||||
}
|
||||
|
||||
public void beginObject() throws IOException {
|
||||
expectNext(JsonToken.BEGIN_OBJECT, "JSON begin object expected");
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
}
|
||||
|
||||
public void endArray() throws IOException {
|
||||
expectNext(JsonToken.END_ARRAY, "JSON end array expected");
|
||||
}
|
||||
|
||||
public void endObject() throws IOException {
|
||||
expectNext(JsonToken.END_OBJECT, "JSON end array expected");
|
||||
}
|
||||
|
||||
public boolean hasNext() throws IOException {
|
||||
if (this.token == JsonToken.READ_FIRST)
|
||||
nextToken();
|
||||
while (this.token == JsonToken.COMMA)
|
||||
nextToken(); // Very lenient - multiple separating commas allowed.
|
||||
return this.token != JsonToken.END_ARRAY
|
||||
&& this.token != JsonToken.END_OBJECT;
|
||||
}
|
||||
|
||||
public boolean isLenient() {
|
||||
return this.lenient;
|
||||
}
|
||||
|
||||
public void setLenient(boolean lenient) {
|
||||
// Lenient mode as described in the Android documentation is not implemented.
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
public boolean nextBoolean() throws IOException, IllegalStateException {
|
||||
if (this.token == JsonToken.READ_FIRST)
|
||||
nextToken();
|
||||
if (this.token != JsonToken.BOOLEAN)
|
||||
throw new IllegalStateException("JSON boolean value expected");
|
||||
boolean value = booleanValue;
|
||||
nextToken();
|
||||
return value;
|
||||
}
|
||||
|
||||
public double nextDouble() throws IOException, IllegalStateException, NumberFormatException {
|
||||
if (this.token == JsonToken.READ_FIRST)
|
||||
nextToken();
|
||||
double value;
|
||||
if (this.token == JsonToken.STRING || token == JsonToken.NUMBER) {
|
||||
value = Double.parseDouble(stringValue);
|
||||
} else {
|
||||
throw new IllegalStateException("JSON double value expected");
|
||||
}
|
||||
nextToken();
|
||||
return value;
|
||||
}
|
||||
|
||||
public int nextInt() throws IOException, IllegalStateException, NumberFormatException {
|
||||
if (this.token == JsonToken.READ_FIRST)
|
||||
nextToken();
|
||||
int value;
|
||||
if (this.token == JsonToken.STRING || this.token == JsonToken.NUMBER) {
|
||||
value = Integer.parseInt(stringValue);
|
||||
} else {
|
||||
throw new IllegalStateException("JSON int value expected");
|
||||
}
|
||||
nextToken();
|
||||
return value;
|
||||
}
|
||||
|
||||
public long nextLong() throws IOException, IllegalStateException, NumberFormatException {
|
||||
if (this.token == JsonToken.READ_FIRST)
|
||||
nextToken();
|
||||
long value;
|
||||
if (this.token == JsonToken.STRING || this.token == JsonToken.NUMBER) {
|
||||
value = Long.parseLong(stringValue);
|
||||
} else {
|
||||
throw new MalformedJsonException("JSON long value expected");
|
||||
}
|
||||
nextToken();
|
||||
return value;
|
||||
}
|
||||
|
||||
public String nextName() throws IOException, IllegalStateException {
|
||||
if (this.token == JsonToken.READ_FIRST)
|
||||
nextToken();
|
||||
if (this.token != JsonToken.NAME)
|
||||
throw new IllegalStateException("JSON property name expected");
|
||||
String value = stringValue;
|
||||
nextToken();
|
||||
return value;
|
||||
}
|
||||
|
||||
public void nextNull() throws IOException, IllegalStateException {
|
||||
if (this.token == JsonToken.READ_FIRST)
|
||||
nextToken();
|
||||
if (this.token != JsonToken.NULL)
|
||||
throw new IllegalStateException("JSON null value expected");
|
||||
nextToken();
|
||||
}
|
||||
|
||||
public String nextString() throws IOException, IllegalStateException {
|
||||
if (this.token == JsonToken.READ_FIRST)
|
||||
nextToken();
|
||||
String value;
|
||||
if (this.token == JsonToken.STRING || this.token == JsonToken.NUMBER)
|
||||
value = stringValue;
|
||||
else if (this.token == JsonToken.BOOLEAN)
|
||||
value = Boolean.toString(booleanValue);
|
||||
else if (this.token == JsonToken.NULL)
|
||||
value = null;
|
||||
else
|
||||
throw new IllegalStateException("JSON string value expected");
|
||||
nextToken();
|
||||
return value;
|
||||
}
|
||||
|
||||
public JsonToken peek() throws IOException {
|
||||
if (this.token == JsonToken.READ_FIRST)
|
||||
nextToken();
|
||||
return this.token;
|
||||
}
|
||||
|
||||
public void skipValue() throws IOException {
|
||||
switch (peek()) {
|
||||
case BEGIN_ARRAY:
|
||||
beginArray();
|
||||
while (hasNext())
|
||||
skipValue();
|
||||
endArray();
|
||||
break;
|
||||
case BEGIN_OBJECT:
|
||||
beginObject();
|
||||
while (hasNext()) {
|
||||
nextName();
|
||||
skipValue();
|
||||
}
|
||||
endObject();
|
||||
break;
|
||||
case END_DOCUMENT:
|
||||
break;
|
||||
default:
|
||||
nextToken();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseNumber(int ch) throws IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append((char)ch);
|
||||
ch = in.read();
|
||||
while ((ch >= '0' && ch <= '9') || ch == '-' || ch == '+' || ch == 'e' || ch == 'E' || ch == '.') {
|
||||
builder.append((char)ch);
|
||||
ch = in.read();
|
||||
}
|
||||
ungetCh = ch;
|
||||
stringValue = builder.toString();
|
||||
}
|
||||
|
||||
private void parseString() throws IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int ch = in.read();
|
||||
outer: while (ch != '"') {
|
||||
if (ch == -1) {
|
||||
ungetCh = ch;
|
||||
break;
|
||||
} else if (ch == '\\') {
|
||||
ch = in.read();
|
||||
if (ch == 'b')
|
||||
builder.append('\u0008');
|
||||
else if (ch == 'f')
|
||||
builder.append('\u000C');
|
||||
else if (ch == 'n')
|
||||
builder.append('\n');
|
||||
else if (ch == 'r')
|
||||
builder.append('\r');
|
||||
else if (ch == 't')
|
||||
builder.append('\t');
|
||||
else if (ch != 'u')
|
||||
builder.append((char)ch);
|
||||
else if (ch == -1) {
|
||||
ungetCh = ch;
|
||||
break;
|
||||
} else {
|
||||
int value = 0;
|
||||
int digits = 0;
|
||||
while (digits < 4) {
|
||||
ch = in.read();
|
||||
if (ch >= '0' && ch <= '9')
|
||||
value = value * 16 + (ch - '0');
|
||||
else if (ch >= 'A' && ch <= 'F')
|
||||
value = value * 16 + (ch - 'A' + 10);
|
||||
else if (ch >= 'a' && ch <= 'f')
|
||||
value = value * 16 + (ch - 'a' + 10);
|
||||
else {
|
||||
builder.append((char)value);
|
||||
continue outer;
|
||||
}
|
||||
++digits;
|
||||
}
|
||||
builder.append((char)value);
|
||||
}
|
||||
} else {
|
||||
builder.append((char)ch);
|
||||
}
|
||||
ch = in.read();
|
||||
}
|
||||
stringValue = builder.toString();
|
||||
}
|
||||
|
||||
private boolean checkForColon() throws IOException {
|
||||
int ch = ungetCh;
|
||||
if (ch == -2)
|
||||
ch = in.read();
|
||||
while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
|
||||
ch = in.read();
|
||||
if (ch == ':') {
|
||||
ungetCh = -2;
|
||||
return true;
|
||||
} else {
|
||||
ungetCh = ch;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNamedToken(String name) throws IOException {
|
||||
int index = 1;
|
||||
int ch = in.read();
|
||||
for (; index < name.length(); ++index) {
|
||||
if (ch != name.charAt(index))
|
||||
break;
|
||||
ch = in.read();
|
||||
}
|
||||
if (index < name.length() || (ch >= 'a' && ch <= 'z')
|
||||
|| (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')
|
||||
|| ch == '_') {
|
||||
throw new MalformedJsonException("Invalid keyword, expected: '"
|
||||
+ name + "'");
|
||||
}
|
||||
ungetCh = ch;
|
||||
}
|
||||
|
||||
private void nextToken() throws IOException {
|
||||
int ch = ungetCh;
|
||||
if (ch == -2)
|
||||
ch = in.read();
|
||||
while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
|
||||
ch = in.read();
|
||||
ungetCh = -2;
|
||||
switch (ch) {
|
||||
case -1:
|
||||
token = JsonToken.END_DOCUMENT;
|
||||
ungetCh = -1;
|
||||
break;
|
||||
case '{':
|
||||
token = JsonToken.BEGIN_OBJECT;
|
||||
break;
|
||||
case '}':
|
||||
token = JsonToken.END_OBJECT;
|
||||
break;
|
||||
case '[':
|
||||
token = JsonToken.BEGIN_ARRAY;
|
||||
break;
|
||||
case ']':
|
||||
token = JsonToken.END_ARRAY;
|
||||
break;
|
||||
case ',':
|
||||
token = JsonToken.COMMA;
|
||||
break;
|
||||
case '-':
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
parseNumber(ch);
|
||||
token = JsonToken.NUMBER;
|
||||
break;
|
||||
case '"':
|
||||
parseString();
|
||||
if (checkForColon())
|
||||
token = JsonToken.NAME;
|
||||
else
|
||||
token = JsonToken.STRING;
|
||||
break;
|
||||
case 'n':
|
||||
checkNamedToken("null");
|
||||
token = JsonToken.NULL;
|
||||
break;
|
||||
case 't':
|
||||
checkNamedToken("true");
|
||||
token = JsonToken.BOOLEAN;
|
||||
booleanValue = true;
|
||||
break;
|
||||
case 'f':
|
||||
checkNamedToken("false");
|
||||
token = JsonToken.BOOLEAN;
|
||||
booleanValue = false;
|
||||
break;
|
||||
default:
|
||||
throw new MalformedJsonException(
|
||||
"Invalid character in JSON stream: '" + (char) ch + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private void expectNext(JsonToken token, String message) throws IOException {
|
||||
if (this.token == JsonToken.READ_FIRST)
|
||||
nextToken();
|
||||
if (this.token != token)
|
||||
throw new MalformedJsonException(message);
|
||||
nextToken();
|
||||
}
|
||||
}
|
||||
45
NoiseJavaTests/src/com/southernstorm/json/JsonToken.java
Normal file
45
NoiseJavaTests/src/com/southernstorm/json/JsonToken.java
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.southernstorm.json;
|
||||
|
||||
/**
|
||||
* Types of JSON tokens.
|
||||
*
|
||||
* Intentionally compatible with android.util.JsonToken.
|
||||
*/
|
||||
public enum JsonToken {
|
||||
BEGIN_ARRAY,
|
||||
BEGIN_OBJECT,
|
||||
BOOLEAN,
|
||||
END_ARRAY,
|
||||
END_DOCUMENT,
|
||||
END_OBJECT,
|
||||
NAME,
|
||||
NULL,
|
||||
NUMBER,
|
||||
STRING,
|
||||
|
||||
// Internal to this implementation of JsonReader - not part of the public interface.
|
||||
READ_FIRST,
|
||||
COMMA
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.southernstorm.json;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MalformedJsonException extends IOException {
|
||||
|
||||
private static final long serialVersionUID = -1645564375062756336L;
|
||||
|
||||
public MalformedJsonException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,462 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.southernstorm.noise.tests;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.AEADBadTagException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import com.southernstorm.json.JsonReader;
|
||||
import com.southernstorm.noise.protocol.CipherState;
|
||||
import com.southernstorm.noise.protocol.CipherStatePair;
|
||||
import com.southernstorm.noise.protocol.HandshakeState;
|
||||
|
||||
/**
|
||||
* Executes Noise vector tests in JSON format.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private class TestMessage
|
||||
{
|
||||
public byte[] payload;
|
||||
public byte[] ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a Noise test vector that was parsed from a JSON stream.
|
||||
*/
|
||||
private class TestVector
|
||||
{
|
||||
public String name;
|
||||
public String pattern;
|
||||
public String dh;
|
||||
public String cipher;
|
||||
public String hash;
|
||||
public byte[] init_prologue;
|
||||
public byte[] init_ephemeral;
|
||||
public byte[] init_static;
|
||||
public byte[] init_remote_static;
|
||||
public byte[] init_psk;
|
||||
public byte[] init_ssk;
|
||||
public byte[] resp_prologue;
|
||||
public byte[] resp_ephemeral;
|
||||
public byte[] resp_static;
|
||||
public byte[] resp_remote_static;
|
||||
public byte[] resp_psk;
|
||||
public byte[] resp_ssk;
|
||||
public byte[] handshake_hash;
|
||||
public boolean failure_expected;
|
||||
public boolean fallback_expected;
|
||||
public TestMessage[] messages;
|
||||
|
||||
public void addMessage(TestMessage msg)
|
||||
{
|
||||
TestMessage[] newMessages;
|
||||
if (messages != null) {
|
||||
newMessages = new TestMessage [messages.length + 1];
|
||||
System.arraycopy(messages, 0, newMessages, 0, messages.length);
|
||||
newMessages[messages.length] = msg;
|
||||
} else {
|
||||
newMessages = new TestMessage [1];
|
||||
newMessages[0] = msg;
|
||||
}
|
||||
messages = newMessages;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSubArrayEquals(String msg, byte[] expected, byte[] actual)
|
||||
{
|
||||
for (int index = 0; index < expected.length; ++index)
|
||||
assertEquals(msg + "[" + Integer.toString(index) + "]", expected[index], actual[index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a Noise test vector.
|
||||
*
|
||||
* @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, AEADBadTagException, NoSuchAlgorithmException
|
||||
{
|
||||
// Set all keys and special values that we need.
|
||||
if (vec.init_prologue != null)
|
||||
initiator.setPrologue(vec.init_prologue, 0, vec.init_prologue.length);
|
||||
if (vec.init_static != null)
|
||||
initiator.getLocalKeyPair().setPrivateKey(vec.init_static, 0);
|
||||
if (vec.init_remote_static != null)
|
||||
initiator.getRemotePublicKey().setPublicKey(vec.init_remote_static, 0);
|
||||
if (vec.init_ephemeral != null)
|
||||
initiator.getFixedEphemeralKey().setPrivateKey(vec.init_ephemeral, 0);
|
||||
if (vec.init_psk != null)
|
||||
initiator.setPreSharedKey(vec.init_psk, 0, vec.init_psk.length);
|
||||
if (vec.resp_prologue != null)
|
||||
responder.setPrologue(vec.resp_prologue, 0, vec.resp_prologue.length);
|
||||
if (vec.resp_static != null)
|
||||
responder.getLocalKeyPair().setPrivateKey(vec.resp_static, 0);
|
||||
if (vec.resp_remote_static != null)
|
||||
responder.getRemotePublicKey().setPublicKey(vec.resp_remote_static, 0);
|
||||
if (vec.resp_ephemeral != null) {
|
||||
// Note: The test data contains responder ephemeral keys for one-way
|
||||
// patterns which doesn't actually make sense. Ignore those keys.
|
||||
if (vec.pattern.length() != 1)
|
||||
responder.getFixedEphemeralKey().setPrivateKey(vec.resp_ephemeral, 0);
|
||||
}
|
||||
if (vec.resp_psk != null)
|
||||
responder.setPreSharedKey(vec.resp_psk, 0, vec.resp_psk.length);
|
||||
|
||||
// Start both sides of the handshake.
|
||||
assertEquals(HandshakeState.NO_ACTION, initiator.getAction());
|
||||
assertEquals(HandshakeState.NO_ACTION, responder.getAction());
|
||||
initiator.start();
|
||||
responder.start();
|
||||
assertEquals(HandshakeState.WRITE_MESSAGE, initiator.getAction());
|
||||
assertEquals(HandshakeState.READ_MESSAGE, responder.getAction());
|
||||
|
||||
// Work through the messages one by one until both sides "split".
|
||||
int role = HandshakeState.INITIATOR;
|
||||
int index = 0;
|
||||
HandshakeState send, recv;
|
||||
boolean isOneWay = (vec.pattern.length() == 1);
|
||||
boolean fallback = vec.fallback_expected;
|
||||
byte[] message = new byte [1024];
|
||||
byte[] plaintext = new byte [1024];
|
||||
for (; index < vec.messages.length; ++index) {
|
||||
if (initiator.getAction() == HandshakeState.SPLIT &&
|
||||
responder.getAction() == HandshakeState.SPLIT) {
|
||||
break;
|
||||
}
|
||||
if (role == HandshakeState.INITIATOR) {
|
||||
// Send on the initiator, receive on the responder.
|
||||
send = initiator;
|
||||
recv = responder;
|
||||
if (!isOneWay)
|
||||
role = HandshakeState.RESPONDER;
|
||||
} else {
|
||||
// Send on the responder, receive on the initiator.
|
||||
send = responder;
|
||||
recv = initiator;
|
||||
role = HandshakeState.INITIATOR;
|
||||
}
|
||||
assertEquals(HandshakeState.WRITE_MESSAGE, send.getAction());
|
||||
assertEquals(HandshakeState.READ_MESSAGE, recv.getAction());
|
||||
TestMessage msg = vec.messages[index];
|
||||
int len = send.writeMessage(message, 0, msg.payload, 0, msg.payload.length);
|
||||
assertEquals(msg.ciphertext.length, len);
|
||||
assertSubArrayEquals(Integer.toString(index) + ": ciphertext", msg.ciphertext, message);
|
||||
if (fallback) {
|
||||
// Perform a read on the responder, which will fail.
|
||||
try {
|
||||
recv.readMessage(message, 0, len, plaintext, 0);
|
||||
fail("read should have triggered fallback");
|
||||
} catch (AEADBadTagException e) {
|
||||
// Success!
|
||||
}
|
||||
|
||||
// Initiate fallback on both sides.
|
||||
initiator.fallback();
|
||||
responder.fallback();
|
||||
|
||||
// Restart the protocols.
|
||||
initiator.start();
|
||||
responder.start();
|
||||
|
||||
// Only need to fallback once.
|
||||
fallback = false;
|
||||
} else {
|
||||
int plen = recv.readMessage(message, 0, len, plaintext, 0);
|
||||
assertEquals(msg.payload.length, plen);
|
||||
assertSubArrayEquals(Integer.toString(index) + ": payload", msg.payload, plaintext);
|
||||
}
|
||||
}
|
||||
if (vec.fallback_expected) {
|
||||
// The roles will have reversed during the handshake.
|
||||
assertEquals(HandshakeState.RESPONDER, initiator.getRole());
|
||||
assertEquals(HandshakeState.INITIATOR, responder.getRole());
|
||||
} else {
|
||||
assertEquals(HandshakeState.INITIATOR, initiator.getRole());
|
||||
assertEquals(HandshakeState.RESPONDER, responder.getRole());
|
||||
}
|
||||
|
||||
// Handshake finished. Check the handshake hash values.
|
||||
if (vec.handshake_hash != null) {
|
||||
assertArrayEquals(vec.handshake_hash, initiator.getHandshakeHash());
|
||||
assertArrayEquals(vec.handshake_hash, responder.getHandshakeHash());
|
||||
}
|
||||
|
||||
// Split the two sides to get the transport ciphers.
|
||||
CipherStatePair initPair;
|
||||
CipherStatePair respPair;
|
||||
assertEquals(HandshakeState.SPLIT, initiator.getAction());
|
||||
assertEquals(HandshakeState.SPLIT, responder.getAction());
|
||||
if (vec.init_ssk != null)
|
||||
initPair = initiator.split(vec.init_ssk, 0, vec.init_ssk.length);
|
||||
else
|
||||
initPair = initiator.split();
|
||||
if (vec.resp_ssk != null)
|
||||
respPair = responder.split(vec.resp_ssk, 0, vec.resp_ssk.length);
|
||||
else
|
||||
respPair = responder.split();
|
||||
assertEquals(HandshakeState.COMPLETE, initiator.getAction());
|
||||
assertEquals(HandshakeState.COMPLETE, responder.getAction());
|
||||
|
||||
// Now handle the data transport.
|
||||
CipherState csend, crecv;
|
||||
for (; index < vec.messages.length; ++index) {
|
||||
TestMessage msg = vec.messages[index];
|
||||
if (role == HandshakeState.INITIATOR) {
|
||||
// Send on the initiator, receive on the responder.
|
||||
csend = initPair.getSender();
|
||||
crecv = respPair.getReceiver();
|
||||
if (!isOneWay)
|
||||
role = HandshakeState.RESPONDER;
|
||||
} else {
|
||||
// Send on the responder, receive on the initiator.
|
||||
csend = respPair.getSender();
|
||||
crecv = initPair.getReceiver();
|
||||
role = HandshakeState.INITIATOR;
|
||||
}
|
||||
int len = csend.encryptWithAd(null, msg.payload, 0, message, 0, msg.payload.length);
|
||||
assertEquals(msg.ciphertext.length, len);
|
||||
assertSubArrayEquals(Integer.toString(index) + ": ciphertext", msg.ciphertext, message);
|
||||
int plen = crecv.decryptWithAd(null, message, 0, plaintext, 0, len);
|
||||
assertEquals(msg.payload.length, plen);
|
||||
assertSubArrayEquals(Integer.toString(index) + ": payload", msg.payload, plaintext);
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
initiator.destroy();
|
||||
responder.destroy();
|
||||
initPair.destroy();
|
||||
respPair.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a single test vector from an input stream.
|
||||
*
|
||||
* @param reader The JSON reader for the input stream.
|
||||
*
|
||||
* The reader is positioned on the first field of the vector object.
|
||||
*/
|
||||
private void processVector(JsonReader reader) throws IOException
|
||||
{
|
||||
// Parse the contents of the test vector.
|
||||
TestVector vec = new TestVector();
|
||||
while (reader.hasNext()) {
|
||||
String name = reader.nextName();
|
||||
if (name.equals("name"))
|
||||
vec.name = reader.nextString();
|
||||
else if (name.equals("pattern"))
|
||||
vec.pattern = reader.nextString();
|
||||
else if (name.equals("dh"))
|
||||
vec.dh = reader.nextString();
|
||||
else if (name.equals("cipher"))
|
||||
vec.cipher = reader.nextString();
|
||||
else if (name.equals("hash"))
|
||||
vec.hash = reader.nextString();
|
||||
else if (name.equals("init_prologue"))
|
||||
vec.init_prologue = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("init_ephemeral"))
|
||||
vec.init_ephemeral = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("init_static"))
|
||||
vec.init_static = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("init_remote_static"))
|
||||
vec.init_remote_static = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("init_psk"))
|
||||
vec.init_psk = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("init_ssk"))
|
||||
vec.init_ssk = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("resp_prologue"))
|
||||
vec.resp_prologue = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("resp_ephemeral"))
|
||||
vec.resp_ephemeral = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("resp_static"))
|
||||
vec.resp_static = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("resp_remote_static"))
|
||||
vec.resp_remote_static = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("resp_psk"))
|
||||
vec.resp_psk = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("resp_ssk"))
|
||||
vec.resp_ssk = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("handshake_hash"))
|
||||
vec.handshake_hash = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("fail"))
|
||||
vec.failure_expected = reader.nextBoolean();
|
||||
else if (name.equals("fallback"))
|
||||
vec.fallback_expected = reader.nextBoolean();
|
||||
else if (name.equals("messages")) {
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
TestMessage msg = new TestMessage();
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
name = reader.nextName();
|
||||
if (name.equals("payload"))
|
||||
msg.payload = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else if (name.equals("ciphertext"))
|
||||
msg.ciphertext = DatatypeConverter.parseHexBinary(reader.nextString());
|
||||
else
|
||||
reader.skipValue();
|
||||
}
|
||||
vec.addMessage(msg);
|
||||
reader.endObject();
|
||||
}
|
||||
reader.endArray();
|
||||
} else {
|
||||
reader.skipValue();
|
||||
}
|
||||
}
|
||||
|
||||
// Format the complete protocol name.
|
||||
String protocolName = "Noise";
|
||||
if (vec.init_psk != null || vec.resp_psk != null)
|
||||
protocolName += "PSK";
|
||||
protocolName += "_" + vec.pattern + "_" + vec.dh + "_" + vec.cipher + "_" + vec.hash;
|
||||
if (vec.name == null)
|
||||
vec.name = protocolName;
|
||||
|
||||
// Execute the test vector.
|
||||
++total;
|
||||
System.out.print(vec.name);
|
||||
System.out.print(" ... ");
|
||||
System.out.flush();
|
||||
try {
|
||||
HandshakeState initiator = new HandshakeState(protocolName, HandshakeState.INITIATOR);
|
||||
HandshakeState responder = new HandshakeState(protocolName, HandshakeState.RESPONDER);
|
||||
assertEquals(HandshakeState.INITIATOR, initiator.getRole());
|
||||
assertEquals(HandshakeState.RESPONDER, responder.getRole());
|
||||
assertEquals(protocolName, initiator.getProtocolName());
|
||||
assertEquals(protocolName, responder.getProtocolName());
|
||||
runTest(vec, initiator, responder);
|
||||
if (!vec.failure_expected) {
|
||||
System.out.println("ok");
|
||||
} else {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a file from the command-line.
|
||||
*
|
||||
* @param filename The name of the file to process.
|
||||
*/
|
||||
public void processFile(String filename)
|
||||
{
|
||||
total = 0;
|
||||
skipped = 0;
|
||||
failed = 0;
|
||||
try {
|
||||
FileInputStream fileStream = new FileInputStream(filename);
|
||||
try {
|
||||
InputStreamReader streamReader = new InputStreamReader(fileStream);
|
||||
BufferedReader bufferedReader = new BufferedReader(streamReader);
|
||||
JsonReader reader = new JsonReader(bufferedReader);
|
||||
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();
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
} finally {
|
||||
fileStream.close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
System.err.println(filename + ": File not found");
|
||||
} catch (IOException e) {
|
||||
System.err.println("Exception while parsing JSON: " + e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.print(filename + ": ");
|
||||
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 static void main(String[] args) {
|
||||
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