mirror of
https://github.com/taigrr/arduinolibs
synced 2025-01-18 04:33:12 -08:00
793 lines
29 KiB
C++
793 lines
29 KiB
C++
/*
|
|
* Copyright (C) 2016,2018 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.
|
|
*/
|
|
|
|
#include <NoiseProtocol.h>
|
|
#include "json-reader.h"
|
|
#include <setjmp.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#define MAX_MESSAGES 32
|
|
#define MAX_MESSAGE_SIZE 4096
|
|
#define MAX_PSKS 8
|
|
|
|
/**
|
|
* \brief Information about a single test vector.
|
|
*/
|
|
typedef struct
|
|
{
|
|
long line_number; /**< Line number for the "name" */
|
|
char *name; /**< Full name of the test case */
|
|
char *protocol_name; /**< Full name of the protocol */
|
|
uint8_t *init_static; /**< Initiator's static private key */
|
|
size_t init_static_len; /**< Length of init_static in bytes */
|
|
uint8_t *init_public_static; /**< Initiator's public key known to responder */
|
|
size_t init_public_static_len; /**< Length of init_public_static in bytes */
|
|
uint8_t *resp_static; /**< Responder's static private key */
|
|
size_t resp_static_len; /**< Length of resp_static in bytes */
|
|
uint8_t *resp_public_static; /**< Responder's public key known to initiator */
|
|
size_t resp_public_static_len; /**< Length of resp_public_static in bytes */
|
|
uint8_t *init_ephemeral; /**< Initiator's ephemeral key */
|
|
size_t init_ephemeral_len; /**< Length of init_ephemeral in bytes */
|
|
uint8_t *resp_ephemeral; /**< Responder's ephemeral key */
|
|
size_t resp_ephemeral_len; /**< Length of resp_ephemeral in bytes */
|
|
uint8_t *init_hybrid; /**< Initiator's hybrid ephemeral key */
|
|
size_t init_hybrid_len; /**< Length of init_hybrid in bytes */
|
|
uint8_t *resp_hybrid; /**< Responder's hybrid ephemeral key */
|
|
size_t resp_hybrid_len; /**< Length of resp_hybrid in bytes */
|
|
uint8_t *init_prologue; /**< Initiator's prologue data */
|
|
size_t init_prologue_len; /**< Length of init_prologue in bytes */
|
|
uint8_t *resp_prologue; /**< Responder's prologue data */
|
|
size_t resp_prologue_len; /**< Length of resp_prologue in bytes */
|
|
uint8_t init_psks[MAX_PSKS][32];/**< Initiator pre-shared keys */
|
|
size_t num_init_psks; /**< Number of initiator PSK's */
|
|
uint8_t resp_psks[MAX_PSKS][32];/**< Responder pre-shared keys */
|
|
size_t num_resp_psks; /**< Number of responder PSK's */
|
|
uint8_t *handshake_hash; /**< Hash at the end of the handshake */
|
|
size_t handshake_hash_len; /**< Length of handshake_hash in bytes */
|
|
int fail; /**< Failure expected on last message */
|
|
int fallback; /**< Handshake involves IK to XXfallback */
|
|
char *fallback_pattern; /**< Name of the pattern to fall back to */
|
|
int is_one_way; /**< True if the base pattern is one-way */
|
|
struct {
|
|
uint8_t *payload; /**< Payload for this message */
|
|
size_t payload_len; /**< Length of payload in bytes */
|
|
uint8_t *ciphertext; /**< Ciphertext for this message */
|
|
size_t ciphertext_len; /**< Length of ciphertext in bytes */
|
|
} messages[MAX_MESSAGES]; /**< All test messages */
|
|
size_t num_messages; /**< Number of test messages */
|
|
|
|
} TestVector;
|
|
|
|
/**
|
|
* \brief Frees the memory for a test vector.
|
|
*
|
|
* \param vec The test vector.
|
|
*/
|
|
static void test_vector_free(TestVector *vec)
|
|
{
|
|
size_t index;
|
|
#define free_field(name) do { if (vec->name) free(vec->name); } while (0)
|
|
free_field(name);
|
|
free_field(protocol_name);
|
|
free_field(init_static);
|
|
free_field(init_public_static);
|
|
free_field(resp_static);
|
|
free_field(resp_public_static);
|
|
free_field(init_ephemeral);
|
|
free_field(resp_ephemeral);
|
|
free_field(init_hybrid);
|
|
free_field(resp_hybrid);
|
|
free_field(init_prologue);
|
|
free_field(resp_prologue);
|
|
free_field(handshake_hash);
|
|
free_field(fallback_pattern);
|
|
for (index = 0; index < vec->num_messages; ++index) {
|
|
if (vec->messages[index].payload)
|
|
free(vec->messages[index].payload);
|
|
if (vec->messages[index].ciphertext)
|
|
free(vec->messages[index].ciphertext);
|
|
}
|
|
memset(vec, 0, sizeof(TestVector));
|
|
}
|
|
|
|
static jmp_buf test_jump_back;
|
|
|
|
/**
|
|
* \brief Immediate fail of the test.
|
|
*
|
|
* \param message The failure message to print.
|
|
*/
|
|
#define _fail(message) \
|
|
do { \
|
|
printf("%s, failed at " __FILE__ ":%d\n", (message), __LINE__); \
|
|
longjmp(test_jump_back, 1); \
|
|
} while (0)
|
|
#define fail(message) _fail((message))
|
|
|
|
/**
|
|
* \brief Skips the current test.
|
|
*/
|
|
#define skip() longjmp(test_jump_back, 2)
|
|
|
|
/**
|
|
* \brief Verifies that a condition is true, failing the test if not.
|
|
*
|
|
* \param condition The boolean condition to test.
|
|
*/
|
|
#define _verify(condition) \
|
|
do { \
|
|
if (!(condition)) { \
|
|
printf(#condition " failed at " __FILE__ ":%d\n", __LINE__); \
|
|
longjmp(test_jump_back, 1); \
|
|
} \
|
|
} while (0)
|
|
#define verify(condition) _verify((condition))
|
|
|
|
/**
|
|
* \brief Compares two integer values for equality, failing the test if not.
|
|
*
|
|
* \param actual The actual value that was computed by the code under test.
|
|
* \param expected The value that is expected.
|
|
*/
|
|
#define compare(actual, expected) \
|
|
do { \
|
|
long long _actual = (long long)(actual); \
|
|
long long _expected = (long long)(expected); \
|
|
if (_actual != _expected) { \
|
|
printf(#actual " != " #expected " at " __FILE__ ":%d\n", __LINE__); \
|
|
printf(" actual : %lld (0x%llx)\n", _actual, _actual); \
|
|
printf(" expected: %lld (0x%llx)\n", _expected, _expected); \
|
|
longjmp(test_jump_back, 1); \
|
|
} \
|
|
} while (0)
|
|
|
|
static void dump_block(uint8_t *block, size_t len)
|
|
{
|
|
size_t index;
|
|
if (len > 16)
|
|
printf("\n ");
|
|
for (index = 0; index < len; ++index) {
|
|
printf(" %02x", block[index]);
|
|
if ((index % 16) == 15 && len > 16)
|
|
printf("\n ");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
#define compare_blocks(name, actual, actual_len, expected, expected_len) \
|
|
do { \
|
|
if ((actual_len) != (expected_len) || \
|
|
memcmp((actual), (expected), (actual_len)) != 0) { \
|
|
printf("%s wrong at " __FILE__ ":%d\n", (name), __LINE__); \
|
|
printf(" actual :"); \
|
|
dump_block((actual), (actual_len)); \
|
|
printf(" expected:"); \
|
|
dump_block((expected), (expected_len)); \
|
|
longjmp(test_jump_back, 1); \
|
|
} \
|
|
} while (0)
|
|
|
|
/**
|
|
* \brief Creates a handshake object for a specific protocol.
|
|
*
|
|
* \pararm protocol The name of the protocol.
|
|
*/
|
|
static NoiseHandshakeState *create_handshake(const char *protocol)
|
|
{
|
|
if (!strcmp(protocol, "Noise_IK_25519_AESGCM_SHA256"))
|
|
return new NoiseHandshakeState_IK_25519_AESGCM_SHA256();
|
|
if (!strcmp(protocol, "Noise_IK_25519_ChaChaPoly_BLAKE2s"))
|
|
return new NoiseHandshakeState_IK_25519_ChaChaPoly_BLAKE2s();
|
|
if (!strcmp(protocol, "Noise_IK_25519_ChaChaPoly_SHA256"))
|
|
return new NoiseHandshakeState_IK_25519_ChaChaPoly_SHA256();
|
|
|
|
if (!strcmp(protocol, "Noise_NNpsk0_25519_AESGCM_SHA256"))
|
|
return new NoiseHandshakeState_NNpsk0_25519_AESGCM_SHA256();
|
|
if (!strcmp(protocol, "Noise_NNpsk0_25519_ChaChaPoly_BLAKE2s"))
|
|
return new NoiseHandshakeState_NNpsk0_25519_ChaChaPoly_BLAKE2s();
|
|
if (!strcmp(protocol, "Noise_NNpsk0_25519_ChaChaPoly_SHA256"))
|
|
return new NoiseHandshakeState_NNpsk0_25519_ChaChaPoly_SHA256();
|
|
|
|
if (!strcmp(protocol, "Noise_XX_25519_AESGCM_SHA256"))
|
|
return new NoiseHandshakeState_XX_25519_AESGCM_SHA256();
|
|
if (!strcmp(protocol, "Noise_XX_25519_ChaChaPoly_BLAKE2s"))
|
|
return new NoiseHandshakeState_XX_25519_ChaChaPoly_BLAKE2s();
|
|
if (!strcmp(protocol, "Noise_XX_25519_ChaChaPoly_SHA256"))
|
|
return new NoiseHandshakeState_XX_25519_ChaChaPoly_SHA256();
|
|
|
|
fail(protocol);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Test a connection between an initiator and a responder.
|
|
*
|
|
* \param vec The test vector.
|
|
*/
|
|
static void test_connection(const TestVector *vec)
|
|
{
|
|
NoiseHandshakeState *initiator;
|
|
NoiseHandshakeState *responder;
|
|
NoiseHandshakeState *send;
|
|
NoiseHandshakeState *recv;
|
|
NoiseCipherState *c1init;
|
|
NoiseCipherState *c2init;
|
|
NoiseCipherState *c1resp;
|
|
NoiseCipherState *c2resp;
|
|
NoiseCipherState *csend;
|
|
NoiseCipherState *crecv;
|
|
uint8_t message[MAX_MESSAGE_SIZE];
|
|
uint8_t payload[MAX_MESSAGE_SIZE];
|
|
int result;
|
|
size_t index;
|
|
size_t mac_len;
|
|
Noise::Party role;
|
|
|
|
/* Create the two ends of the connection */
|
|
initiator = create_handshake(vec->protocol_name);
|
|
responder = create_handshake(vec->protocol_name);
|
|
|
|
/* Should be able to start the handshake now on both sides */
|
|
initiator->start
|
|
(Noise::Initiator, vec->init_prologue, vec->init_prologue_len);
|
|
responder->start
|
|
(Noise::Responder, vec->resp_prologue, vec->resp_prologue_len);
|
|
compare(initiator->state(), Noise::Write);
|
|
compare(responder->state(), Noise::Read);
|
|
|
|
/* Set all keys that we need to use. We do this after start()
|
|
because otherwise it will clear the test ephemeral keys we're
|
|
about to set on the objects */
|
|
if (vec->init_static) {
|
|
verify(initiator->setParameter
|
|
(Noise::LocalStaticPrivateKey,
|
|
vec->init_static, vec->init_static_len));
|
|
}
|
|
if (vec->init_public_static) {
|
|
verify(responder->setParameter
|
|
(Noise::RemoteStaticPublicKey,
|
|
vec->init_public_static, vec->init_public_static_len));
|
|
}
|
|
if (vec->resp_static) {
|
|
verify(responder->setParameter
|
|
(Noise::LocalStaticPrivateKey,
|
|
vec->resp_static, vec->resp_static_len));
|
|
}
|
|
if (vec->resp_public_static) {
|
|
verify(initiator->setParameter
|
|
(Noise::RemoteStaticPublicKey,
|
|
vec->resp_public_static, vec->resp_public_static_len));
|
|
}
|
|
if (vec->init_ephemeral) {
|
|
verify(initiator->setParameter
|
|
(Noise::LocalEphemPrivateKey,
|
|
vec->init_ephemeral, vec->init_ephemeral_len));
|
|
}
|
|
if (vec->resp_ephemeral) {
|
|
verify(responder->setParameter
|
|
(Noise::LocalEphemPrivateKey,
|
|
vec->resp_ephemeral, vec->resp_ephemeral_len));
|
|
}
|
|
if (vec->num_init_psks) {
|
|
/* For now we only support one PSK per handshake */
|
|
verify(initiator->setParameter
|
|
(Noise::PreSharedKey, vec->init_psks[0], 32));
|
|
}
|
|
if (vec->num_resp_psks) {
|
|
verify(responder->setParameter
|
|
(Noise::PreSharedKey, vec->resp_psks[0], 32));
|
|
}
|
|
|
|
/* Work through the messages one by one until both sides "split" */
|
|
role = Noise::Initiator;
|
|
for (index = 0; index < vec->num_messages; ++index) {
|
|
if (initiator->state() == Noise::Split &&
|
|
responder->state() == Noise::Split) {
|
|
break;
|
|
}
|
|
if (role == Noise::Initiator) {
|
|
/* Send on the initiator, receive on the responder */
|
|
send = initiator;
|
|
recv = responder;
|
|
role = Noise::Responder;
|
|
} else {
|
|
/* Send on the responder, receive on the initiator */
|
|
send = responder;
|
|
recv = initiator;
|
|
role = Noise::Initiator;
|
|
}
|
|
compare(send->state(), Noise::Write);
|
|
compare(recv->state(), Noise::Read);
|
|
|
|
memset(message, 0xAA, sizeof(message));
|
|
result = send->write(message, sizeof(message),
|
|
vec->messages[index].payload,
|
|
vec->messages[index].payload_len);
|
|
verify(result >= 0);
|
|
compare_blocks("ciphertext", message, (size_t)result,
|
|
vec->messages[index].ciphertext,
|
|
vec->messages[index].ciphertext_len);
|
|
|
|
memset(payload, 0xBB, sizeof(payload));
|
|
result = recv->read(payload, sizeof(payload), message, result);
|
|
verify(result >= 0);
|
|
compare_blocks("plaintext", payload, (size_t)result,
|
|
vec->messages[index].payload,
|
|
vec->messages[index].payload_len);
|
|
}
|
|
|
|
/* Handshake finished. Check the handshake hash values */
|
|
if (vec->handshake_hash_len) {
|
|
memset(payload, 0xAA, sizeof(payload));
|
|
verify(initiator->getHandshakeHash(payload, vec->handshake_hash_len));
|
|
compare_blocks("handshake_hash", payload, vec->handshake_hash_len,
|
|
vec->handshake_hash, vec->handshake_hash_len);
|
|
memset(payload, 0xAA, sizeof(payload));
|
|
verify(responder->getHandshakeHash(payload, vec->handshake_hash_len));
|
|
compare_blocks("handshake_hash", payload, vec->handshake_hash_len,
|
|
vec->handshake_hash, vec->handshake_hash_len);
|
|
}
|
|
|
|
/* Now handle the data transport */
|
|
verify(initiator->split(&c1init, &c2init));
|
|
verify(responder->split(&c2resp, &c1resp));
|
|
mac_len = 16;
|
|
for (; index < vec->num_messages; ++index) {
|
|
if (role == Noise::Initiator) {
|
|
/* Send on the initiator, receive on the responder */
|
|
csend = c1init;
|
|
crecv = c1resp;
|
|
role = Noise::Responder;
|
|
} else {
|
|
/* Send on the responder, receive on the initiator */
|
|
csend = c2resp;
|
|
crecv = c2init;
|
|
role = Noise::Initiator;
|
|
}
|
|
verify(sizeof(message) >= (vec->messages[index].payload_len + mac_len));
|
|
verify(sizeof(payload) >= (vec->messages[index].payload_len + mac_len));
|
|
result = csend->encryptPacket
|
|
(message, sizeof(message),
|
|
vec->messages[index].payload, vec->messages[index].payload_len);
|
|
verify(result >= 0);
|
|
compare_blocks("ciphertext", message, (size_t)result,
|
|
vec->messages[index].ciphertext,
|
|
vec->messages[index].ciphertext_len);
|
|
result = crecv->decryptPacket
|
|
(payload, sizeof(payload), message, result);
|
|
verify(result >= 0);
|
|
compare_blocks("plaintext", payload, (size_t)result,
|
|
vec->messages[index].payload,
|
|
vec->messages[index].payload_len);
|
|
}
|
|
|
|
/* Clean up */
|
|
delete initiator;
|
|
delete responder;
|
|
delete c1init;
|
|
delete c2init;
|
|
delete c1resp;
|
|
delete c2resp;
|
|
}
|
|
|
|
/**
|
|
* \brief Runs a fully parsed test vector.
|
|
*
|
|
* \param reader The input stream, for error reporting.
|
|
* \param vec The test vector.
|
|
*
|
|
* \return Non-zero if the test succeeded, zero if it failed.
|
|
*/
|
|
static int test_vector_run(JSONReader *reader, const TestVector *vec)
|
|
{
|
|
int value;
|
|
printf("%s ... ", vec->name);
|
|
fflush(stdout);
|
|
if ((value = setjmp(test_jump_back)) == 0) {
|
|
test_connection(vec);
|
|
printf("ok\n");
|
|
return 1;
|
|
} else if (value == 2) {
|
|
printf("skipped\n");
|
|
return 1;
|
|
} else {
|
|
printf("-> test data at %s:%ld\n", reader->filename, vec->line_number);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Look for a specific token next in the input stream.
|
|
*
|
|
* \param reader The input stream.
|
|
* \param token The token code.
|
|
* \param name The token name for error reporting.
|
|
*/
|
|
static void expect_token(JSONReader *reader, JSONToken token, const char *name)
|
|
{
|
|
if (reader->errors)
|
|
return;
|
|
if (reader->token == token)
|
|
json_next_token(reader);
|
|
else
|
|
json_error(reader, "Expecting '%s'", name);
|
|
}
|
|
|
|
/**
|
|
* \brief Look for a specific field name next in the input stream,
|
|
* followed by a colon.
|
|
*
|
|
* \param reader The input stream.
|
|
* \param name The field name.
|
|
*/
|
|
static void expect_name(JSONReader *reader, const char *name)
|
|
{
|
|
if (reader->errors)
|
|
return;
|
|
if (json_is_name(reader, name)) {
|
|
json_next_token(reader);
|
|
expect_token(reader, JSON_TOKEN_COLON, ":");
|
|
} else {
|
|
json_error(reader, "Expecting \"%s\"", name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Look for a field with a string value.
|
|
*
|
|
* \param reader The input stream.
|
|
* \param value The location where to place the string value.
|
|
*/
|
|
static void expect_string_field(JSONReader *reader, char **value)
|
|
{
|
|
json_next_token(reader);
|
|
expect_token(reader, JSON_TOKEN_COLON, ":");
|
|
if (!reader->errors && reader->token == JSON_TOKEN_STRING) {
|
|
*value = reader->str_value;
|
|
reader->str_value = 0;
|
|
json_next_token(reader);
|
|
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
|
|
json_next_token(reader);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Converts an ASCII character into a hexadecimal digit.
|
|
*
|
|
* \param ch The ASCII character.
|
|
*
|
|
* \return The digit between 0 and 15, or -1 if \a ch is not hexadecimal.
|
|
*/
|
|
static int from_hex_digit(int ch)
|
|
{
|
|
if (ch >= '0' && ch <= '9')
|
|
return ch - '0';
|
|
else if (ch >= 'A' && ch <= 'F')
|
|
return ch - 'A' + 10;
|
|
else if (ch >= 'a' && ch <= 'f')
|
|
return ch - 'a' + 10;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* \brief Look for a field with a binary value.
|
|
*
|
|
* \param reader The input stream.
|
|
* \param value The location where to place the binary value.
|
|
*
|
|
* \return The size of the binary value in bytes.
|
|
*/
|
|
static size_t expect_binary_field(JSONReader *reader, uint8_t **value)
|
|
{
|
|
size_t size = 0;
|
|
size_t posn;
|
|
const char *hex;
|
|
int digit1, digit2;
|
|
json_next_token(reader);
|
|
expect_token(reader, JSON_TOKEN_COLON, ":");
|
|
if (!reader->errors && reader->token == JSON_TOKEN_STRING) {
|
|
size = strlen(reader->str_value) / 2;
|
|
*value = (uint8_t *)calloc(1, size + 1);
|
|
if (!(*value)) {
|
|
json_error(reader, "Out of memory");
|
|
return 0;
|
|
}
|
|
hex = reader->str_value;
|
|
for (posn = 0; posn < size; ++posn) {
|
|
digit1 = from_hex_digit(hex[posn * 2]);
|
|
digit2 = from_hex_digit(hex[posn * 2 + 1]);
|
|
if (digit1 < 0 || digit2 < 0) {
|
|
json_error(reader, "Invalid hexadecimal data");
|
|
return 0;
|
|
}
|
|
(*value)[posn] = digit1 * 16 + digit2;
|
|
}
|
|
json_next_token(reader);
|
|
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
|
|
json_next_token(reader);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* \brief Look for a field with a boolean value.
|
|
*
|
|
* \param reader The input stream.
|
|
* \return The boolean value.
|
|
*/
|
|
static int expect_boolean_field(JSONReader *reader)
|
|
{
|
|
int result = 0;
|
|
json_next_token(reader);
|
|
expect_token(reader, JSON_TOKEN_COLON, ":");
|
|
if (!reader->errors && (reader->token == JSON_TOKEN_TRUE ||
|
|
reader->token == JSON_TOKEN_FALSE)) {
|
|
result = (reader->token == JSON_TOKEN_TRUE);
|
|
json_next_token(reader);
|
|
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
|
|
json_next_token(reader);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* \brief Parse a list of PSK's from a JSON input stream.
|
|
*
|
|
* \param reader The input stream.
|
|
* \param Array to receive the PSK's.
|
|
* \return The number of PSK's that were parsed.
|
|
*/
|
|
static size_t parse_psk_list(JSONReader *reader, uint8_t psks[MAX_PSKS][32])
|
|
{
|
|
size_t count = 0;
|
|
json_next_token(reader);
|
|
expect_token(reader, JSON_TOKEN_COLON, ":");
|
|
expect_token(reader, JSON_TOKEN_LSQUARE, "[");
|
|
while (!reader->errors && reader->token == JSON_TOKEN_STRING) {
|
|
const char *hex = reader->str_value;
|
|
size_t size = strlen(hex) / 2;
|
|
size_t posn;
|
|
if (size != 32) {
|
|
json_error(reader, "PSK is not 32 bytes in size");
|
|
return 0;
|
|
}
|
|
if (count >= MAX_PSKS) {
|
|
json_error(reader, "Too many PSK's");
|
|
return 0;
|
|
}
|
|
for (posn = 0; posn < size; ++posn) {
|
|
int digit1 = from_hex_digit(hex[posn * 2]);
|
|
int digit2 = from_hex_digit(hex[posn * 2 + 1]);
|
|
if (digit1 < 0 || digit2 < 0) {
|
|
json_error(reader, "Invalid hexadecimal data");
|
|
return 0;
|
|
}
|
|
psks[count][posn] = digit1 * 16 + digit2;
|
|
}
|
|
++count;
|
|
json_next_token(reader);
|
|
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
|
|
json_next_token(reader);
|
|
}
|
|
expect_token(reader, JSON_TOKEN_RSQUARE, "]");
|
|
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
|
|
json_next_token(reader);
|
|
return reader->errors ? 0 : count;
|
|
}
|
|
|
|
/**
|
|
* \brief Processes a single test vector from an input stream.
|
|
*
|
|
* \param reader The reader representing the input stream.
|
|
*
|
|
* \return Non-zero if the test succeeded, zero if it failed.
|
|
*/
|
|
static int process_test_vector(JSONReader *reader)
|
|
{
|
|
TestVector vec;
|
|
int retval = 1;
|
|
memset(&vec, 0, sizeof(TestVector));
|
|
while (!reader->errors && reader->token == JSON_TOKEN_STRING) {
|
|
if (json_is_name(reader, "name")) {
|
|
vec.line_number = reader->line_number;
|
|
expect_string_field(reader, &(vec.name));
|
|
} else if (json_is_name(reader, "protocol_name")) {
|
|
vec.line_number = reader->line_number;
|
|
expect_string_field(reader, &(vec.protocol_name));
|
|
} else if (json_is_name(reader, "init_static")) {
|
|
vec.init_static_len =
|
|
expect_binary_field(reader, &(vec.init_static));
|
|
} else if (json_is_name(reader, "init_remote_static")) {
|
|
/* Refers to the initiator have pre-knowledge of the responder's
|
|
public key, which is "resp_public_static" in TestVector */
|
|
vec.resp_public_static_len =
|
|
expect_binary_field(reader, &(vec.resp_public_static));
|
|
} else if (json_is_name(reader, "resp_static")) {
|
|
vec.resp_static_len =
|
|
expect_binary_field(reader, &(vec.resp_static));
|
|
} else if (json_is_name(reader, "resp_remote_static")) {
|
|
/* Refers to the responder have pre-knowledge of the initiator's
|
|
public key, which is "init_public_static" in TestVector */
|
|
vec.init_public_static_len =
|
|
expect_binary_field(reader, &(vec.init_public_static));
|
|
} else if (json_is_name(reader, "init_ephemeral")) {
|
|
vec.init_ephemeral_len =
|
|
expect_binary_field(reader, &(vec.init_ephemeral));
|
|
} else if (json_is_name(reader, "resp_ephemeral")) {
|
|
vec.resp_ephemeral_len =
|
|
expect_binary_field(reader, &(vec.resp_ephemeral));
|
|
} else if (json_is_name(reader, "init_hybrid_ephemeral")) {
|
|
vec.init_hybrid_len =
|
|
expect_binary_field(reader, &(vec.init_hybrid));
|
|
} else if (json_is_name(reader, "resp_hybrid_ephemeral")) {
|
|
vec.resp_hybrid_len =
|
|
expect_binary_field(reader, &(vec.resp_hybrid));
|
|
} else if (json_is_name(reader, "init_prologue")) {
|
|
vec.init_prologue_len =
|
|
expect_binary_field(reader, &(vec.init_prologue));
|
|
} else if (json_is_name(reader, "resp_prologue")) {
|
|
vec.resp_prologue_len =
|
|
expect_binary_field(reader, &(vec.resp_prologue));
|
|
} else if (json_is_name(reader, "init_psks")) {
|
|
vec.num_init_psks = parse_psk_list(reader, vec.init_psks);
|
|
} else if (json_is_name(reader, "resp_psks")) {
|
|
vec.num_resp_psks = parse_psk_list(reader, vec.resp_psks);
|
|
} else if (json_is_name(reader, "handshake_hash")) {
|
|
vec.handshake_hash_len =
|
|
expect_binary_field(reader, &(vec.handshake_hash));
|
|
} else if (json_is_name(reader, "fail")) {
|
|
vec.fail = expect_boolean_field(reader);
|
|
} else if (json_is_name(reader, "fallback")) {
|
|
vec.fallback = expect_boolean_field(reader);
|
|
} else if (json_is_name(reader, "fallback_pattern")) {
|
|
expect_string_field(reader, &(vec.fallback_pattern));
|
|
} else if (json_is_name(reader, "messages")) {
|
|
json_next_token(reader);
|
|
expect_token(reader, JSON_TOKEN_COLON, ":");
|
|
expect_token(reader, JSON_TOKEN_LSQUARE, "[");
|
|
while (!reader->errors && reader->token == JSON_TOKEN_LBRACE) {
|
|
if (vec.num_messages >= MAX_MESSAGES) {
|
|
json_error(reader, "Too many messages for test vector");
|
|
break;
|
|
}
|
|
expect_token(reader, JSON_TOKEN_LBRACE, "{");
|
|
while (!reader->errors && reader->token == JSON_TOKEN_STRING) {
|
|
if (json_is_name(reader, "payload")) {
|
|
vec.messages[vec.num_messages].payload_len =
|
|
expect_binary_field
|
|
(reader, &(vec.messages[vec.num_messages].payload));
|
|
} else if (json_is_name(reader, "ciphertext")) {
|
|
vec.messages[vec.num_messages].ciphertext_len =
|
|
expect_binary_field
|
|
(reader, &(vec.messages[vec.num_messages].ciphertext));
|
|
} else {
|
|
json_error(reader, "Unknown message field '%s'",
|
|
reader->str_value);
|
|
}
|
|
}
|
|
if (!vec.messages[vec.num_messages].payload)
|
|
json_error(reader, "Missing payload for message");
|
|
if (!vec.messages[vec.num_messages].ciphertext)
|
|
json_error(reader, "Missing ciphertext for message");
|
|
++(vec.num_messages);
|
|
expect_token(reader, JSON_TOKEN_RBRACE, "}");
|
|
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
|
|
json_next_token(reader);
|
|
}
|
|
expect_token(reader, JSON_TOKEN_RSQUARE, "]");
|
|
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
|
|
json_next_token(reader);
|
|
} else {
|
|
json_error(reader, "Unknown field '%s'", reader->str_value);
|
|
}
|
|
}
|
|
if (!vec.protocol_name) {
|
|
json_error(reader, "Missing 'protocol_name' field");
|
|
} else if (!vec.name) {
|
|
vec.name = strdup(vec.protocol_name);
|
|
}
|
|
if (!reader->errors) {
|
|
retval = test_vector_run(reader, &vec);
|
|
}
|
|
test_vector_free(&vec);
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* \brief Processes the test vectors from an input stream.
|
|
*
|
|
* \param reader The reader representing the input stream.
|
|
*/
|
|
static void process_test_vectors(JSONReader *reader)
|
|
{
|
|
int ok = 1;
|
|
printf("--------------------------------------------------------------\n");
|
|
printf("Processing vectors from %s\n", reader->filename);
|
|
json_next_token(reader);
|
|
expect_token(reader, JSON_TOKEN_LBRACE, "{");
|
|
expect_name(reader, "vectors");
|
|
expect_token(reader, JSON_TOKEN_LSQUARE, "[");
|
|
while (!reader->errors && reader->token != JSON_TOKEN_RSQUARE) {
|
|
expect_token(reader, JSON_TOKEN_LBRACE, "{");
|
|
if (!process_test_vector(reader))
|
|
ok = 0;
|
|
expect_token(reader, JSON_TOKEN_RBRACE, "}");
|
|
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
|
|
expect_token(reader, JSON_TOKEN_COMMA, ",");
|
|
}
|
|
expect_token(reader, JSON_TOKEN_RSQUARE, "]");
|
|
expect_token(reader, JSON_TOKEN_RBRACE, "}");
|
|
expect_token(reader, JSON_TOKEN_END, "EOF");
|
|
printf("--------------------------------------------------------------\n");
|
|
if (!ok) {
|
|
/* Some of the test vectors failed, so report a global failure */
|
|
++(reader->errors);
|
|
}
|
|
}
|
|
|
|
static int process_file(const char *filename)
|
|
{
|
|
int retval = 0;
|
|
FILE *file = fopen(filename, "r");
|
|
if (file) {
|
|
JSONReader reader;
|
|
json_init(&reader, filename, file);
|
|
process_test_vectors(&reader);
|
|
if (reader.errors > 0)
|
|
retval = 1;
|
|
json_free(&reader);
|
|
fclose(file);
|
|
} else {
|
|
perror(filename);
|
|
retval = 1;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int retval = 0;
|
|
char *srcdir = getenv("srcdir");
|
|
if (argc <= 1 && !srcdir) {
|
|
fprintf(stderr, "Usage: %s vectors1.txt vectors2.txt ...\n", argv[0]);
|
|
return 1;
|
|
} else if (argc > 1) {
|
|
while (argc > 1) {
|
|
retval |= process_file(argv[1]);
|
|
--argc;
|
|
++argv;
|
|
}
|
|
} else {
|
|
if (chdir(srcdir) < 0) {
|
|
perror(srcdir);
|
|
return 1;
|
|
}
|
|
retval |= process_file("cacophony.txt");
|
|
retval |= process_file("noise-c-basic.txt");
|
|
retval |= process_file("noise-c-fallback.txt");
|
|
retval |= process_file("noise-c-hybrid.txt");
|
|
}
|
|
return retval;
|
|
}
|