/* vim: set expandtab ts=4 sw=4: */
/*
* You may redistribute this program and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "crypto/random/Random.h"
#include "crypto/Ca.h"
#include "crypto/CryptoAuth.h"
#include "benc/String.h"
#include "memory/MallocAllocator.h"
#include "util/events/EventBase.h"
#include "util/Assert.h"
#include "util/Bits.h"
#include "util/Hex.h"
#include "util/Endian.h"
#include "util/log/FileWriterLog.h"
#include "wire/CryptoHeader.h"
#include "crypto/test/TestCa.h"
#include "crypto/random/test/DeterminentRandomSeed.h"
#include "util/CString.h"
#define PRIVATEKEY_A \
Constant_stringForHex("53ff22b2eb94ce8c5f1852c0f557eb901f067e5273d541e0a21e143c20dff9da")
#define PUBLICKEY_A \
Constant_stringForHex("e3ff75af6e4414494df22f200ffeaa56e7976d991d33cc87f52427e27f83235d")
#define PRIVATEKEY_B \
Constant_stringForHex("b71c4f43e3d4b1879b5065d44a1cb43eaf07ddba96de6a72ca761c4ef4bd2988")
#define PUBLICKEY_B \
Constant_stringForHex("27c303cdc1f96e4b28d51c75130aff6cad52098f2d752615b7b6509ed6a89477")
#define USEROBJ "This represents a user"
struct Node
{
TestCa_t* ca;
TestCa_Session_t* sess;
struct Iface plaintext;
struct Iface ciphertext;
const char* expectPlaintext;
enum CryptoAuth_DecryptErr expectErr;
struct Log* log;
Identity
};
struct Context
{
struct Node node1;
struct Node node2;
struct Allocator* alloc;
struct Log* log;
struct EventBase* base;
Identity
};
static struct Random* evilRandom(struct Allocator* alloc, struct Log* logger, const char* seed)
{
uint8_t buf[64] = {0};
Assert_true(CString_strlen(seed) < 60);
CString_strcpy(buf, seed);
RandomSeed_t* evilSeed = DeterminentRandomSeed_new(alloc, buf);
return Random_newWithSeed(alloc, logger, evilSeed, NULL);
}
static Iface_DEFUN afterDecrypt(struct Message* msg, struct Iface* if1)
{
struct Node* n = Identity_containerOf(if1, struct Node, plaintext);
Log_debug(n->log, "Got message from afterDecrypt");
enum CryptoAuth_DecryptErr e = Er_assert(Message_epop32h(msg));
if (e != n->expectErr) {
Assert_failure("expected decrypt error [%d], got [%d]\n", n->expectErr, e);
}
n->expectErr = CryptoAuth_DecryptErr_NONE;
if (!n->expectPlaintext) {
if (e) {
return NULL;
}
Assert_failure("expected , got [%s](%d)\n", msg->msgbytes, Message_getLength(msg));
}
if ((int)CString_strlen(n->expectPlaintext) != Message_getLength(msg) ||
CString_strncmp(msg->msgbytes, n->expectPlaintext, Message_getLength(msg)))
{
Assert_failure("expected [%s](%d), got [%s](%d)\n",
n->expectPlaintext, (int)CString_strlen(n->expectPlaintext), msg->msgbytes, Message_getLength(msg));
}
n->expectPlaintext = NULL;
return NULL;
}
static Iface_DEFUN afterEncrypt(struct Message* msg, struct Iface* if1)
{
return NULL;
}
static struct Context* init(uint8_t* privateKeyA,
uint8_t* publicKeyA,
uint8_t* password,
uint8_t* privateKeyB,
uint8_t* publicKeyB,
enum TestCa_Config cfg)
{
struct Allocator* alloc = MallocAllocator_new(1048576);
struct Context* ctx = Allocator_calloc(alloc, sizeof(struct Context), 1);
Identity_set(ctx);
Identity_set(&ctx->node1);
Identity_set(&ctx->node2);
ctx->alloc = alloc;
ctx->node1.plaintext.send = afterDecrypt;
ctx->node2.plaintext.send = afterDecrypt;
ctx->node1.ciphertext.send = afterEncrypt;
ctx->node2.ciphertext.send = afterEncrypt;
struct Log* logger = ctx->log = FileWriterLog_new(stdout, alloc);
struct Random* randA = evilRandom(alloc, logger, "ALPHA");
struct Random* randB = evilRandom(alloc, logger, "ALPHA");
struct Random* randC = evilRandom(alloc, logger, "BRAVO");
struct Random* randD = evilRandom(alloc, logger, "BRAVO");
struct EventBase* base = ctx->base = EventBase_new(alloc);
ctx->node1.log = logger;
ctx->node2.log = logger;
ctx->node1.ca = TestCa_new(alloc, privateKeyA, base, logger, randA, randB, cfg);
ctx->node1.sess = TestCa_newSession(ctx->node1.ca, alloc, publicKeyB, false, "cif1", true);
ctx->node2.ca = TestCa_new(alloc, privateKeyB, base, logger, randC, randD, cfg);
if (password) {
String* passStr = String_CONST(password);
TestCa_setAuth(passStr, NULL, ctx->node1.sess);
TestCa_addUser_ipv6(passStr, String_new(USEROBJ, alloc), NULL, ctx->node2.ca);
}
ctx->node2.sess = TestCa_newSession(ctx->node2.ca, alloc, publicKeyA, false, "cif2", true);
Iface_plumb(&ctx->node1.sess->plaintext, &ctx->node1.plaintext);
Iface_plumb(&ctx->node1.sess->ciphertext, &ctx->node1.ciphertext);
Iface_plumb(&ctx->node2.sess->plaintext, &ctx->node2.plaintext);
Iface_plumb(&ctx->node2.sess->ciphertext, &ctx->node2.ciphertext);
return ctx;
}
static struct Context* simpleInit(enum TestCa_Config cfg)
{
return init(PRIVATEKEY_A, PUBLICKEY_A, NULL, PRIVATEKEY_B, PUBLICKEY_B, cfg);
}
static struct Message* encryptMsg(struct Context* ctx,
struct Node* n,
const char* x)
{
struct Allocator* alloc = Allocator_child(ctx->alloc);
int len = (((CString_strlen(x)+1) / 8) + 1) * 8;
struct Message* msg = Message_new(len, CryptoHeader_SIZE + 32, alloc);
CString_strcpy(msg->msgbytes, x);
Er_assert(Message_truncate(msg, CString_strlen(x)));
//msg->bytes[Message_getLength(msg)] = 0;
struct RTypes_Error_t* e = Iface_send(&n->plaintext, msg);
if (e) {
printf("%s\n", Rffi_printError(e, ctx->alloc));
Assert_failure("error was not null");
}
Assert_true(Message_getLength(msg) > ((int)CString_strlen(x) + 4));
return msg;
}
static struct Message* decryptMsg(struct Context* ctx,
struct Message* msg,
struct Node* n,
const char* expectResult,
enum CryptoAuth_DecryptErr expectErr)
{
Assert_true(!n->expectPlaintext && !n->expectErr);
n->expectPlaintext = expectResult;
n->expectErr = expectErr;
Er_assert(Message_epush(msg, NULL, 16)); // peer ipv6
Iface_send(&n->ciphertext, msg);
Assert_true(!n->expectPlaintext && !n->expectErr);
return msg;
}
static void sendToIf1(struct Context* ctx, const char* x)
{
struct Message* msg = encryptMsg(ctx, &ctx->node2, x);
decryptMsg(ctx, msg, &ctx->node1, x, CryptoAuth_DecryptErr_NONE);
Allocator_free(Message_getAlloc(msg));
}
static void sendToIf2(struct Context* ctx, const char* x)
{
struct Message* msg = encryptMsg(ctx, &ctx->node1, x);
decryptMsg(ctx, msg, &ctx->node2, x, CryptoAuth_DecryptErr_NONE);
Allocator_free(Message_getAlloc(msg));
}
static void normal(enum TestCa_Config cfg)
{
struct Context* ctx = simpleInit(cfg);
sendToIf2(ctx, "hello world");
sendToIf1(ctx, "hello cjdns");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "goodbye");
Allocator_free(ctx->alloc);
}
static void repeatKey(enum TestCa_Config cfg)
{
struct Context* ctx = simpleInit(cfg);
sendToIf2(ctx, "hello world");
sendToIf2(ctx, "r u thar?");
sendToIf1(ctx, "hello cjdns");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "goodbye");
Allocator_free(ctx->alloc);
}
static void repeatHello(enum TestCa_Config cfg)
{
struct Context* ctx = simpleInit(cfg);
sendToIf2(ctx, "hello world");
sendToIf2(ctx, "r u thar?");
sendToIf1(ctx, "hello cjdns");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "goodbye");
Allocator_free(ctx->alloc);
}
static void chatter(enum TestCa_Config cfg)
{
struct Context* ctx = simpleInit(cfg);
sendToIf2(ctx, "hello world");
sendToIf1(ctx, "hello cjdns");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "goodbye");
sendToIf1(ctx, "hello cjdns");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "goodbye");
sendToIf1(ctx, "hello cjdns");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "goodbye");
sendToIf1(ctx, "hello cjdns");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "goodbye");
Allocator_free(ctx->alloc);
}
static void auth(enum TestCa_Config cfg)
{
struct Context* ctx = init(
PRIVATEKEY_A, PUBLICKEY_A, "password", PRIVATEKEY_B, PUBLICKEY_B, cfg);
sendToIf2(ctx, "hello world");
sendToIf1(ctx, "hello cjdns");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "goodbye");
Allocator_free(ctx->alloc);
}
static void replayKeyPacket(int scenario, enum TestCa_Config cfg)
{
struct Context* ctx = simpleInit(cfg);
sendToIf2(ctx, "hello world");
struct Message* msg = encryptMsg(ctx, &ctx->node2, "hello replay key");
struct Message* toReplay = Message_clone(msg, ctx->alloc);
decryptMsg(ctx, msg, &ctx->node1, "hello replay key", CryptoAuth_DecryptErr_NONE);
if (scenario == 1) {
// the packet is failed because we know it's a dupe from the temp key.
decryptMsg(ctx, toReplay, &ctx->node1, NULL, CryptoAuth_DecryptErr_INVALID_PACKET);
}
sendToIf2(ctx, "first traffic packet");
if (scenario == 2) {
decryptMsg(ctx, toReplay, &ctx->node1, NULL, CryptoAuth_DecryptErr_INVALID_PACKET);
}
sendToIf1(ctx, "second traffic packet");
if (scenario == 3) {
// If we replay at this stage, the packet is dropped as a stray key
decryptMsg(ctx, toReplay, &ctx->node1, NULL, CryptoAuth_DecryptErr_KEY_PKT_ESTABLISHED_SESSION);
}
Allocator_free(ctx->alloc);
}
/**
* Alice and Bob both decided they wanted to talk to eachother at precisely the same time.
* This means two Hello packets crossed on the wire. Both arrived at their destination but
* if each triggers a re-initialization of the CA session, nobody will be synchronized!
*/
static void hellosCrossedOnTheWire(enum TestCa_Config cfg)
{
struct Context* ctx = simpleInit(cfg);
uint8_t pk1[32];
TestCa_getPubKey(ctx->node1.ca, pk1);
uint8_t hpk2[32];
TestCa_getHerPubKey(ctx->node2.sess, hpk2);
Assert_true(!Bits_memcmp(pk1, hpk2, 32));
struct Message* hello2 = encryptMsg(ctx, &ctx->node2, "hello2");
struct Message* hello1 = encryptMsg(ctx, &ctx->node1, "hello1");
decryptMsg(ctx, hello2, &ctx->node1, "hello2", 0);
decryptMsg(ctx, hello1, &ctx->node2, "hello1", 0);
sendToIf2(ctx, "hello world");
sendToIf1(ctx, "hello cjdns");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "goodbye");
Allocator_free(ctx->alloc);
}
static void reset(enum TestCa_Config cfg)
{
struct Context* ctx = simpleInit(cfg);
sendToIf2(ctx, "hello world");
sendToIf1(ctx, "hello cjdns");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "brb");
Assert_true(TestCa_getState(ctx->node1.sess) == CryptoAuth_State_ESTABLISHED);
Assert_true(TestCa_getState(ctx->node2.sess) == CryptoAuth_State_ESTABLISHED);
TestCa_reset(ctx->node1.sess);
// sess2 still talking to sess1 but sess1 is reset and cannot read the packets.
decryptMsg(ctx, encryptMsg(ctx, &ctx->node2, "will be lost"), &ctx->node1, NULL,
CryptoAuth_DecryptErr_NO_SESSION);
decryptMsg(ctx, encryptMsg(ctx, &ctx->node2, "lost"), &ctx->node1, NULL,
CryptoAuth_DecryptErr_NO_SESSION);
// This is because we want to prevent replay attacks from tearing down a session.
decryptMsg(ctx, encryptMsg(ctx, &ctx->node1, "hello"), &ctx->node2, "hello", 0);
sendToIf1(ctx, "hello again");
sendToIf2(ctx, "hai");
sendToIf1(ctx, "ok works");
sendToIf2(ctx, "yup");
Assert_true(TestCa_getState(ctx->node1.sess) == CryptoAuth_State_ESTABLISHED);
Assert_true(TestCa_getState(ctx->node2.sess) == CryptoAuth_State_ESTABLISHED);
Allocator_free(ctx->alloc);
}
// This is slightly different from replayKeyPacket because the second key packet is valid,
// it's just delayed.
static void twoKeyPackets(int scenario, enum TestCa_Config cfg)
{
struct Context* ctx = simpleInit(cfg);
sendToIf2(ctx, "hello world");
sendToIf1(ctx, "key packet 1");
struct Message* key2 = encryptMsg(ctx, &ctx->node2, "key packet 2");
if (scenario == 1) {
sendToIf1(ctx, "key packet 3");
decryptMsg(ctx, key2, &ctx->node1, "key packet 2", 0);
} else if (scenario == 2) {
sendToIf2(ctx, "initial data packet");
decryptMsg(ctx, key2, &ctx->node1, "key packet 2", 0);
sendToIf1(ctx, "second data packet");
sendToIf2(ctx, "third data packet");
} else if (scenario == 3) {
sendToIf2(ctx, "initial data packet");
sendToIf1(ctx, "second data packet");
decryptMsg(ctx, key2, &ctx->node1, NULL, CryptoAuth_DecryptErr_KEY_PKT_ESTABLISHED_SESSION);
}
Allocator_free(ctx->alloc);
}
static void iteration(enum TestCa_Config cfg)
{
normal(cfg);
repeatKey(cfg);
repeatHello(cfg);
chatter(cfg);
auth(cfg);
replayKeyPacket(1, cfg);
replayKeyPacket(2, cfg);
replayKeyPacket(3, cfg);
hellosCrossedOnTheWire(cfg);
reset(cfg);
twoKeyPackets(1, cfg);
twoKeyPackets(2, cfg);
twoKeyPackets(3, cfg);
}
int main()
{
iteration(TestCa_Config_OLD);
iteration(TestCa_Config_OLD_NEW);
//iteration(TestCa_Config_NOISE); // TODO(cjd): re-enable this
return 0;
}