Rusty Russell [ARCHIVE] on Nostr: 📅 Original date posted:2015-10-22 📝 Original message: OK, once I started trying ...
📅 Original date posted:2015-10-22
📝 Original message:
OK, once I started trying to send data, I refined this a little so
we always send protobufs after the initial session key.
Feedback gratefully accepted!
Thanks,
Rusty.
--
After useful feedback from Anthony Towns and Mats Jerratsch (of
thunder.network fame), this is the third version of inter-node crypto.
1) First, each side sends a 33-byte session pubkey. This is a
bitcoin-style compressed EC key, unique for each session.
2) ECDH is used to derive a shared secret. From this we generate
the following transmission encoding parameters for each side:
Session AES-128 key: SHA256(shared-secret || my-sessionpubkey || 0)
Session HMAC key: SHA256(shared-secret || my-sessionpubkey || 1)
IV for AES: SHA256(shared-secret || my-sessionpubkey || 2)
3) All packets from then on are encrypted of form:
/* HMAC, covering totlen and data */
struct sha256 hmac;
/* Total data transmitted (including this). */
le64 totlen;
/* Encrypted contents, rounded up to 16 byte boundary. */
u8 data[];
4) The first packet is an Authenticate protobuf, containing this node's
pubkey, and a bitcoin-style EC signature of the other side's session
pubkey.
5) Unknown protobuf fields are handled in the protocol as follows
(including in the initial Authenticate packet):
1) Odd numbered fields are optional, and backwards compatible.
2) Even numbered fields are required; abort if you get one.
Currently both sides just send an error packet "hello" after the
handshake, and make sure they receive the same.
Signed-off-by: Rusty Russell <rusty at rustcorp.com.au>
diff --git a/daemon/Makefile b/daemon/Makefile
index 699cdc9..dfdcc0f 100644
--- a/daemon/Makefile
+++ b/daemon/Makefile
@@ -14,6 +14,7 @@ DAEMON_LIB_SRC := \
DAEMON_LIB_OBJS := $(DAEMON_LIB_SRC:.c=.o)
DAEMON_SRC := \
+ daemon/cryptopkt.c \
daemon/dns.c \
daemon/jsonrpc.c \
daemon/lightningd.c \
@@ -31,6 +32,7 @@ DAEMON_JSMN_HEADERS := daemon/jsmn/jsmn.h
DAEMON_HEADERS := \
daemon/configdir.h \
+ daemon/cryptopkt.h \
daemon/dns.h \
daemon/json.h \
daemon/jsonrpc.h \
diff --git a/daemon/cryptopkt.c b/daemon/cryptopkt.c
new file mode 100644
index 0000000..0667d6d
--- /dev/null
+++ b/daemon/cryptopkt.c
@@ -0,0 +1,511 @@
+#include "bitcoin/shadouble.h"
+#include "bitcoin/signature.h"
+#include "cryptopkt.h"
+#include "lightning.pb-c.h"
+#include "lightningd.h"
+#include "log.h"
+#include "peer.h"
+#include "protobuf_convert.h"
+#include "secrets.h"
+#include <ccan/build_assert/build_assert.h>
+#include <ccan/crypto/sha256/sha256.h>
+#include <ccan/endian/endian.h>
+#include <ccan/io/io_plan.h>
+#include <ccan/mem/mem.h>
+#include <ccan/short_types/short_types.h>
+#include <inttypes.h>
+#include <openssl/aes.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+#include <secp256k1.h>
+#include <secp256k1_ecdh.h>
+
+#define MAX_PKT_LEN (1024 * 1024)
+
+#define ROUNDUP(x,a) (((x) + ((a)-1)) & ~((a)-1))
+
+struct crypto_pkt {
+ /* HMAC */
+ struct sha256 hmac;
+ /* Total length transmitted. */
+ le64 totlen;
+ /* ... contents... */
+ u8 data[];
+};
+
+/* Temporary structure for negotiation (peer->io_data->neg) */
+struct key_negotiate {
+ /* Our session secret key. */
+ u8 seckey[32];
+
+ /* Our pubkey, their pubkey. */
+ u8 our_sessionpubkey[33], their_sessionpubkey[33];
+
+ /* Callback once it's all done. */
+ struct io_plan *(*cb)(struct io_conn *, struct peer *);
+};
+
+/* ARM loves to add padding to structs; be paranoid! */
+#define SESSION_PROOF_BASE_SIZE (64 + 33)
+
+#define ENCKEY_SEED 0
+#define HMACKEY_SEED 1
+#define IV_SEED 2
+
+struct enckey {
+ struct sha256 k;
+};
+
+struct hmackey {
+ struct sha256 k;
+};
+
+struct iv {
+ unsigned char iv[AES_BLOCK_SIZE];
+};
+
+static void sha_with_seed(const unsigned char secret[32],
+ const unsigned char serial_pubkey[33],
+ unsigned char seed,
+ struct sha256 *res)
+{
+ struct sha256_ctx ctx;
+
+ sha256_init(&ctx);
+ sha256_update(&ctx, memcheck(secret, 32), 32);
+ sha256_update(&ctx, memcheck(serial_pubkey, 33), 33);
+ sha256_u8(&ctx, seed);
+ sha256_done(&ctx, res);
+}
+
+static struct enckey enckey_from_secret(const unsigned char secret[32],
+ const unsigned char serial_pubkey[33])
+{
+ struct enckey enckey;
+ sha_with_seed(secret, serial_pubkey, ENCKEY_SEED, &enckey.k);
+ return enckey;
+}
+
+static struct hmackey hmackey_from_secret(const unsigned char secret[32],
+ const unsigned char serial_pubkey[33])
+{
+ struct hmackey hmackey;
+ sha_with_seed(secret, serial_pubkey, HMACKEY_SEED, &hmackey.k);
+ return hmackey;
+}
+
+static struct iv iv_from_secret(const unsigned char secret[32],
+ const unsigned char serial_pubkey[33])
+{
+ struct sha256 sha;
+ struct iv iv;
+
+ sha_with_seed(secret, serial_pubkey, IV_SEED, &sha);
+ memcpy(iv.iv, sha.u.u8, sizeof(iv.iv));
+ return iv;
+}
+
+struct dir_state {
+ u64 totlen;
+ struct hmackey hmackey;
+ EVP_CIPHER_CTX evpctx;
+
+ /* Current packet. */
+ struct crypto_pkt *cpkt;
+};
+
+static bool setup_crypto(struct dir_state *dir,
+ u8 shared_secret[32], u8 serial_pubkey[33])
+{
+ struct iv iv;
+ struct enckey enckey;
+
+ dir->totlen = 0;
+ dir->hmackey = hmackey_from_secret(shared_secret, serial_pubkey);
+ dir->cpkt = NULL;
+
+ iv = iv_from_secret(shared_secret, serial_pubkey);
+ enckey = enckey_from_secret(shared_secret, serial_pubkey);
+
+ return EVP_EncryptInit(&dir->evpctx, EVP_aes_128_ctr(),
+ memcheck(enckey.k.u.u8, sizeof(enckey.k)),
+ memcheck(iv.iv, sizeof(iv.iv))) == 1;
+}
+
+struct io_data {
+ /* Stuff we need to keep around to talk to peer. */
+ struct dir_state in, out;
+
+ /* Header we're currently reading. */
+ size_t len_in;
+ struct crypto_pkt hdr_in;
+
+ /* For negotiation phase. */
+ struct key_negotiate *neg;
+};
+
+static void *proto_tal_alloc(void *allocator_data, size_t size)
+{
+ return tal_arr(allocator_data, char, size);
+}
+
+static void proto_tal_free(void *allocator_data, void *pointer)
+{
+ tal_free(pointer);
+}
+
+static Pkt *decrypt_pkt(struct peer *peer, struct crypto_pkt *cpkt,
+ size_t data_len)
+{
+ size_t full_len;
+ struct sha256 hmac;
+ int outlen;
+ struct io_data *iod = peer->io_data;
+ struct ProtobufCAllocator prototal;
+ Pkt *ret;
+
+ full_len = ROUNDUP(data_len, AES_BLOCK_SIZE);
+
+ HMAC(EVP_sha256(), iod->in.hmackey.k.u.u8, sizeof(iod->in.hmackey),
+ (unsigned char *)&cpkt->totlen, sizeof(cpkt->totlen) + full_len,
+ hmac.u.u8, NULL);
+
+ if (CRYPTO_memcmp(&hmac, &cpkt->hmac, sizeof(hmac)) != 0) {
+ log_unusual(peer->log, "Packet has bad HMAC");
+ return NULL;
+ }
+
+ /* FIXME: Assumes we can decrypt in place! */
+ EVP_DecryptUpdate(&iod->in.evpctx, cpkt->data, &outlen,
+ memcheck(cpkt->data, full_len), full_len);
+ assert(outlen == full_len);
+
+ /* De-protobuf it. */
+ prototal.alloc = proto_tal_alloc;
+ prototal.free = proto_tal_free;
+ prototal.allocator_data = tal(iod, char);
+
+ ret = pkt__unpack(&prototal, data_len, cpkt->data);
+ if (!ret)
+ tal_free(prototal.allocator_data);
+ else
+ /* Make sure packet owns contents */
+ tal_steal(ret, prototal.allocator_data);
+ return ret;
+}
+
+static struct crypto_pkt *encrypt_pkt(struct peer *peer,
+ const Pkt *pkt,
+ size_t *total_len)
+{
+ static unsigned char zeroes[AES_BLOCK_SIZE-1];
+ struct crypto_pkt *cpkt;
+ unsigned char *dout;
+ size_t len, full_len;
+ int outlen;
+ struct io_data *iod = peer->io_data;
+
+ len = pkt__get_packed_size(pkt);
+ full_len = ROUNDUP(len, AES_BLOCK_SIZE);
+ *total_len = sizeof(*cpkt) + full_len;
+
+ cpkt = (struct crypto_pkt *)tal_arr(peer, char, *total_len);
+ iod->out.totlen += len;
+ cpkt->totlen = cpu_to_le64(iod->out.totlen);
+
+ dout = cpkt->data;
+ /* FIXME: Assumes we can encrypt in place! */
+ pkt__pack(pkt, dout);
+ EVP_EncryptUpdate(&iod->out.evpctx, dout, &outlen,
+ memcheck(dout, len), len);
+ dout += outlen;
+
+ /* Now encrypt tail, padding with zeroes if necessary. */
+ EVP_EncryptUpdate(&iod->out.evpctx, dout, &outlen, zeroes,
+ full_len - len);
+ assert(dout + outlen == cpkt->data + full_len);
+
+ HMAC(EVP_sha256(), iod->out.hmackey.k.u.u8, sizeof(iod->out.hmackey),
+ (unsigned char *)&cpkt->totlen, sizeof(cpkt->totlen) + full_len,
+ cpkt->hmac.u.u8, NULL);
+
+ return cpkt;
+}
+
+static int do_read_packet(int fd, struct io_plan_arg *arg)
+{
+ struct peer *peer = arg->u1.vp;
+ struct io_data *iod = peer->io_data;
+ u64 max;
+ size_t data_off, data_len;
+ int ret;
+
+ /* Still reading header? */
+ if (iod->len_in < sizeof(iod->hdr_in)) {
+ ret = read(fd, (char *)&iod->hdr_in + iod->len_in,
+ sizeof(iod->hdr_in) - iod->len_in);
+ if (ret <= 0)
+ return -1;
+ iod->len_in += ret;
+ /* We don't ever send empty packets, so don't check for
+ * that here. */
+ return 0;
+ }
+
+ max = ROUNDUP(le64_to_cpu(iod->hdr_in.totlen) - iod->in.totlen,
+ AES_BLOCK_SIZE);
+
+ if (iod->len_in == sizeof(iod->hdr_in)) {
+ /* FIXME: Handle re-xmit. */
+ if (le64_to_cpu(iod->hdr_in.totlen) < iod->in.totlen) {
+ log_unusual(peer->log,
+ "Packet went backwards: %"PRIu64
+ " -> %"PRIu64,
+ iod->in.totlen,
+ le64_to_cpu(iod->hdr_in.totlen));
+ return -1;
+ }
+ if (le64_to_cpu(iod->hdr_in.totlen)
+ > iod->in.totlen + MAX_PKT_LEN) {
+ log_unusual(peer->log,
+ "Packet overlength: %"PRIu64" -> %"PRIu64,
+ iod->in.totlen,
+ le64_to_cpu(iod->hdr_in.totlen));
+ return -1;
+ }
+ iod->in.cpkt = (struct crypto_pkt *)
+ tal_arr(iod, u8, sizeof(struct crypto_pkt) + max);
+ memcpy(iod->in.cpkt, &iod->hdr_in, sizeof(iod->hdr_in));
+ }
+
+ data_off = iod->len_in - sizeof(struct crypto_pkt);
+ ret = read(fd, iod->in.cpkt->data + data_off, max - data_off);
+ if (ret <= 0)
+ return -1;
+
+ iod->len_in += ret;
+ if (iod->len_in <= max)
+ return 0;
+
+ /* Can't overflow len arg: packet can't be more than MAX_PKT_LEN */
+ data_len = le64_to_cpu(iod->hdr_in.totlen) - iod->in.totlen;
+ peer->inpkt = decrypt_pkt(peer, iod->in.cpkt, data_len);
+ iod->in.cpkt = tal_free(iod->in.cpkt);
+
+ if (!peer->inpkt)
+ return -1;
+ iod->in.totlen += data_len;
+ return 1;
+}
+
+struct io_plan *peer_read_packet(struct io_conn *conn,
+ struct peer *peer,
+ struct io_plan *(*cb)(struct io_conn *,
+ struct peer *))
+{
+ struct io_plan_arg *arg = io_plan_arg(conn, IO_IN);
+
+ peer->io_data->len_in = 0;
+ arg->u1.vp = peer;
+ return io_set_plan(conn, IO_IN, do_read_packet,
+ (struct io_plan *(*)(struct io_conn *, void *))cb,
+ peer);
+}
+
+/* Caller must free data! */
+struct io_plan *peer_write_packet(struct io_conn *conn,
+ struct peer *peer,
+ const Pkt *pkt,
+ struct io_plan *(*next)(struct io_conn *,
+ struct peer *))
+{
+ struct io_data *iod = peer->io_data;
+ size_t totlen;
+
+ /* We free previous packet here, rather than doing indirection
+ * via io_write */
+ tal_free(iod->out.cpkt);
+ iod->out.cpkt = encrypt_pkt(peer, pkt, &totlen);
+ return io_write(conn, iod->out.cpkt, totlen, next, peer);
+}
+
+static void *pkt_unwrap(struct peer *peer, Pkt__PktCase which)
+{
+ size_t i;
+ const ProtobufCMessage *base;
+
+ if (peer->inpkt->pkt_case != which) {
+ log_unusual(peer->log, "Expected %u, got %u",
+ which, peer->inpkt->pkt_case);
+ return NULL;
+ }
+
+ /* It's a union, and each member starts with base. Pick one */
+ base = &peer->inpkt->error->base;
+
+ /* Look for unknown fields. Remember, "It's OK to be odd!" */
+ for (i = 0; i < base->n_unknown_fields; i++) {
+ log_debug(peer->log, "Unknown field in %u: %u",
+ which, base->unknown_fields[i].tag);
+ /* Odd is OK */
+ if (base->unknown_fields[i].tag & 1)
+ continue;
+ log_unusual(peer->log, "Unknown field %u in %u",
+ base->unknown_fields[i].tag, which);
+ return NULL;
+ }
+ return peer->inpkt->error;
+}
+
+static struct io_plan *check_proof(struct io_conn *conn, struct peer *peer)
+{
+ struct key_negotiate *neg = peer->io_data->neg;
+ struct sha256_double sha;
+ struct signature sig;
+ struct io_plan *(*cb)(struct io_conn *, struct peer *);
+ struct pubkey id;
+ Authenticate *auth;
+
+ auth = pkt_unwrap(peer, PKT__PKT_AUTH);
+ if (!auth)
+ return io_close(conn);
+
+ if (!proto_to_signature(auth->session_sig, &sig)) {
+ log_unusual(peer->log, "Invalid auth signature");
+ return io_close(conn);
+ }
+
+ if (!proto_to_pubkey(auth->node_id, &id)) {
+ log_unusual(peer->log, "Invalid auth id");
+ return io_close(conn);
+ }
+
+ /* Signature covers *our* session key. */
+ sha256_double(&sha,
+ neg->our_sessionpubkey, sizeof(neg->our_sessionpubkey));
+
+ if (!check_signed_hash(&sha, &sig, &id)) {
+ log_unusual(peer->log, "Bad auth signature");
+ return io_close(conn);
+ }
+
+ tal_free(auth);
+
+ /* All complete, return to caller. */
+ cb = neg->cb;
+ peer->io_data->neg = tal_free(neg);
+ return cb(conn, peer);
+}
+
+static struct io_plan *receive_proof(struct io_conn *conn, struct peer *peer)
+{
+ return peer_read_packet(conn, peer, check_proof);
+}
+
+/* Steals w onto the returned Pkt */
+static Pkt *pkt_wrap(const tal_t *ctx, void *w, Pkt__PktCase pkt_case)
+{
+ Pkt *pkt = tal(ctx, Pkt);
+ pkt__init(pkt);
+ pkt->pkt_case = pkt_case;
+ /* Union, so any will do */
+ pkt->error = tal_steal(pkt, w);
+ return pkt;
+}
+
+static Pkt *authenticate_pkt(const tal_t *ctx,
+ const struct pubkey *node_id,
+ const struct signature *sig)
+{
+ Authenticate *auth = tal(ctx, Authenticate);
+ authenticate__init(auth);
+ auth->node_id = pubkey_to_proto(auth, node_id);
+ auth->session_sig = signature_to_proto(auth, sig);
+ return pkt_wrap(ctx, auth, PKT__PKT_AUTH);
+}
+
+static struct io_plan *keys_exchanged(struct io_conn *conn, struct peer *peer)
+{
+ u8 shared_secret[32];
+ struct pubkey sessionkey;
+ struct signature sig;
+ struct key_negotiate *neg = peer->io_data->neg;
+ Pkt *auth;
+
+ if (!pubkey_from_der(neg->their_sessionpubkey,
+ sizeof(neg->their_sessionpubkey),
+ &sessionkey)) {
+ /* FIXME: Dump key in this case. */
+ log_unusual(peer->log, "Bad sessionkey");
+ return io_close(conn);
+ }
+
+ /* Derive shared secret. */
+ if (!secp256k1_ecdh(peer->state->secpctx, shared_secret,
+ &sessionkey.pubkey, neg->seckey)) {
+ log_unusual(peer->log, "Bad ECDH");
+ return io_close(conn);
+ }
+
+ /* Each side combines with their OWN session key to SENDING crypto. */
+ if (!setup_crypto(&peer->io_data->in, shared_secret,
+ neg->their_sessionpubkey)
+ || !setup_crypto(&peer->io_data->out, shared_secret,
+ neg->our_sessionpubkey)) {
+ log_unusual(peer->log, "Failed setup_crypto()");
+ return io_close(conn);
+ }
+
+ /* Now sign their session key to prove who we are. */
+ privkey_sign(peer, neg->their_sessionpubkey,
+ sizeof(neg->their_sessionpubkey), &sig);
+
+ /* FIXME: Free auth afterwards. */
+ auth = authenticate_pkt(peer, &peer->state->id, &sig);
+ return peer_write_packet(conn, peer, auth, receive_proof);
+}
+
+static struct io_plan *session_key_receive(struct io_conn *conn,
+ struct peer *peer)
+{
+ struct key_negotiate *neg = peer->io_data->neg;
+ /* Now read their key. */
+ return io_read(conn, neg->their_sessionpubkey,
+ sizeof(neg->their_sessionpubkey), keys_exchanged, peer);
+}
+
+static void gen_sessionkey(secp256k1_context *ctx,
+ u8 seckey[32],
+ secp256k1_pubkey *pubkey)
+{
+ do {
+ if (RAND_bytes(seckey, 32) != 1)
+ fatal("Could not get random bytes for sessionkey");
+ } while (!secp256k1_ec_pubkey_create(ctx, pubkey, seckey));
+}
+
+struct io_plan *peer_crypto_setup(struct io_conn *conn, struct peer *peer,
+ struct io_plan *(*cb)(struct io_conn *,
+ struct peer *))
+{
+ size_t outputlen;
+ secp256k1_pubkey sessionkey;
+ struct key_negotiate *neg;
+
+ peer->io_data = tal(peer, struct io_data);
+
+ /* We store negotiation state here. */
+ neg = peer->io_data->neg = tal(peer->io_data, struct key_negotiate);
+ neg->cb = cb;
+
+ gen_sessionkey(peer->state->secpctx, neg->seckey, &sessionkey);
+
+ secp256k1_ec_pubkey_serialize(peer->state->secpctx,
+ neg->our_sessionpubkey, &outputlen,
+ &sessionkey,
+ SECP256K1_EC_COMPRESSED);
+ assert(outputlen == sizeof(neg->our_sessionpubkey));
+ return io_write(conn, neg->our_sessionpubkey, outputlen,
+ session_key_receive, peer);
+}
diff --git a/daemon/cryptopkt.h b/daemon/cryptopkt.h
new file mode 100644
index 0000000..06c6167
--- /dev/null
+++ b/daemon/cryptopkt.h
@@ -0,0 +1,26 @@
+#ifndef LIGHTNING_DAEMON_CRYPTOPKT_H
+#define LIGHTNING_DAEMON_CRYPTOPKT_H
+#include "config.h"
+#include "lightning.pb-c.h"
+#include <ccan/io/io.h>
+
+struct peer;
+
+struct io_plan *peer_crypto_setup(struct io_conn *conn,
+ struct peer *peer,
+ struct io_plan *(*cb)(struct io_conn *,
+ struct peer *));
+
+/* Reads packet into peer->inpkt/peer->inpkt_len */
+struct io_plan *peer_read_packet(struct io_conn *conn,
+ struct peer *peer,
+ struct io_plan *(*cb)(struct io_conn *,
+ struct peer *));
+
+struct io_plan *peer_write_packet(struct io_conn *conn,
+ struct peer *peer,
+ const Pkt *pkt,
+ struct io_plan *(*next)(struct io_conn *,
+ struct peer *));
+
+#endif /* LIGHTNING_DAEMON_CRYPTOPKT_H */
diff --git a/daemon/peer.c b/daemon/peer.c
index 5f578ee..ed4fcda 100644
--- a/daemon/peer.c
+++ b/daemon/peer.c
@@ -1,3 +1,4 @@
+#include "cryptopkt.h"
#include "dns.h"
#include "jsonrpc.h"
#include "lightningd.h"
@@ -14,6 +15,33 @@
#include <sys/socket.h>
#include <sys/types.h>
+/* Send and receive (encrypted) hello message. */
+static struct io_plan *peer_test_check(struct io_conn *conn, struct peer *peer)
+{
+ if (peer->inpkt->pkt_case != PKT__PKT_ERROR)
+ fatal("Bad packet type %u", peer->inpkt->pkt_case);
+ if (!peer->inpkt->error->problem
+ || strcmp(peer->inpkt->error->problem, "hello") != 0)
+ fatal("Bad packet '%.6s'", peer->inpkt->error->problem);
+ log_info(peer->log, "Successful hello!");
+ return io_close(conn);
+}
+
+static struct io_plan *peer_test_read(struct io_conn *conn, struct peer *peer)
+{
+ return peer_read_packet(conn, peer, peer_test_check);
+}
+
+static struct io_plan *peer_test(struct io_conn *conn, struct peer *peer)
+{
+ Error err = ERROR__INIT;
+ Pkt pkt = PKT__INIT;
+ pkt.pkt_case = PKT__PKT_ERROR;
+ pkt.error = &err;
+ err.problem = "hello";
+ return peer_write_packet(conn, peer, &pkt, peer_test_read);
+}
+
static void destroy_peer(struct peer *peer)
{
list_del_from(&peer->state->peers, &peer->list);
@@ -32,6 +60,7 @@ static struct peer *new_peer(struct lightningd_state *state,
peer->state = state;
peer->addr.type = addr_type;
peer->addr.protocol = addr_protocol;
+ peer->io_data = NULL;
/* FIXME: Attach IO logging for this peer. */
tal_add_destructor(peer, destroy_peer);
@@ -63,7 +92,7 @@ struct io_plan *peer_connected_out(struct io_conn *conn,
return io_close(conn);
}
log_info(peer->log, "Connected out to %s:%s", name, port);
- return io_write(conn, "Hello!", 6, io_close_cb, NULL);
+ return peer_crypto_setup(conn, peer, peer_test);
}
static struct io_plan *peer_connected_in(struct io_conn *conn,
@@ -73,8 +102,9 @@ static struct io_plan *peer_connected_in(struct io_conn *conn,
"in");
if (!peer)
return io_close(conn);
-
- return io_write(conn, "Hello!", 6, io_close_cb, NULL);
+
+ log_info(peer->log, "Peer connected in");
+ return peer_crypto_setup(conn, peer, peer_test);
}
static int make_listen_fd(struct lightningd_state *state,
diff --git a/daemon/peer.h b/daemon/peer.h
index 50cea69..c39f943 100644
--- a/daemon/peer.h
+++ b/daemon/peer.h
@@ -1,6 +1,7 @@
#ifndef LIGHTNING_DAEMON_PEER_H
#define LIGHTNING_DAEMON_PEER_H
#include "config.h"
+#include "lightning.pb-c.h"
#include "netaddr.h"
#include <ccan/list/list.h>
@@ -14,6 +15,12 @@ struct peer {
/* The other end's address. */
struct netaddr addr;
+ /* Current received packet. */
+ Pkt *inpkt;
+
+ /* Current ongoing packetflow */
+ struct io_data *io_data;
+
/* What happened. */
struct log *log;
};
diff --git a/lightning.pb-c.c b/lightning.pb-c.c
index 5dde94f..62777aa 100644
--- a/lightning.pb-c.c
+++ b/lightning.pb-c.c
@@ -222,6 +222,49 @@ void funding__free_unpacked
assert(message->base.descriptor == &funding__descriptor);
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
}
+void authenticate__init
+ (Authenticate *message)
+{
+ static Authenticate init_value = AUTHENTICATE__INIT;
+ *message = init_value;
+}
+size_t authenticate__get_packed_size
+ (const Authenticate *message)
+{
+ assert(message->base.descriptor == &authenticate__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t authenticate__pack
+ (const Authenticate *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &authenticate__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t authenticate__pack_to_buffer
+ (const Authenticate *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &authenticate__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+Authenticate *
+ authenticate__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (Authenticate *)
+ protobuf_c_message_unpack (&authenticate__descriptor,
+ allocator, len, data);
+}
+void authenticate__free_unpacked
+ (Authenticate *message,
+ ProtobufCAllocator *allocator)
+{
+ assert(message->base.descriptor == &authenticate__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
void open_channel__init
(OpenChannel *message)
{
@@ -1344,6 +1387,57 @@ const ProtobufCMessageDescriptor funding__descriptor =
(ProtobufCMessageInit) funding__init,
NULL,NULL,NULL /* reserved[123] */
};
+static const ProtobufCFieldDescriptor authenticate__field_descriptors[2] =
+{
+ {
+ "node_id",
+ 1,
+ PROTOBUF_C_LABEL_REQUIRED,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(Authenticate, node_id),
+ &bitcoin_pubkey__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "session_sig",
+ 2,
+ PROTOBUF_C_LABEL_REQUIRED,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(Authenticate, session_sig),
+ &signature__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned authenticate__field_indices_by_name[] = {
+ 0, /* field[0] = node_id */
+ 1, /* field[1] = session_sig */
+};
+static const ProtobufCIntRange authenticate__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 2 }
+};
+const ProtobufCMessageDescriptor authenticate__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "authenticate",
+ "Authenticate",
+ "Authenticate",
+ "",
+ sizeof(Authenticate),
+ 2,
+ authenticate__field_descriptors,
+ authenticate__field_indices_by_name,
+ 1, authenticate__number_ranges,
+ (ProtobufCMessageInit) authenticate__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
static const ProtobufCEnumValue open_channel__anchor_offer__enum_values_by_number[2] =
{
{ "WILL_CREATE_ANCHOR", "OPEN_CHANNEL__ANCHOR_OFFER__WILL_CREATE_ANCHOR", 1 },
@@ -2259,7 +2353,7 @@ const ProtobufCMessageDescriptor error__descriptor =
(ProtobufCMessageInit) error__init,
NULL,NULL,NULL /* reserved[123] */
};
-static const ProtobufCFieldDescriptor pkt__field_descriptors[17] =
+static const ProtobufCFieldDescriptor pkt__field_descriptors[18] =
{
{
"update",
@@ -2465,8 +2559,21 @@ static const ProtobufCFieldDescriptor pkt__field_descriptors[17] =
0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
+ {
+ "auth",
+ 50,
+ PROTOBUF_C_LABEL_OPTIONAL,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(Pkt, pkt_case),
+ offsetof(Pkt, auth),
+ &authenticate__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
};
static const unsigned pkt__field_indices_by_name[] = {
+ 17, /* field[17] = auth */
13, /* field[13] = close */
15, /* field[15] = close_ack */
14, /* field[14] = close_complete */
@@ -2485,13 +2592,14 @@ static const unsigned pkt__field_indices_by_name[] = {
3, /* field[3] = update_signature */
7, /* field[7] = update_timedout_htlc */
};
-static const ProtobufCIntRange pkt__number_ranges[4 + 1] =
+static const ProtobufCIntRange pkt__number_ranges[5 + 1] =
{
{ 1, 0 },
{ 20, 9 },
{ 30, 13 },
{ 40, 16 },
- { 0, 17 }
+ { 50, 17 },
+ { 0, 18 }
};
const ProtobufCMessageDescriptor pkt__descriptor =
{
@@ -2501,10 +2609,10 @@ const ProtobufCMessageDescriptor pkt__descriptor =
"Pkt",
"",
sizeof(Pkt),
- 17,
+ 18,
pkt__field_descriptors,
pkt__field_indices_by_name,
- 4, pkt__number_ranges,
+ 5, pkt__number_ranges,
(ProtobufCMessageInit) pkt__init,
NULL,NULL,NULL /* reserved[123] */
};
diff --git a/lightning.pb-c.h b/lightning.pb-c.h
index 26219cc..4178a1e 100644
--- a/lightning.pb-c.h
+++ b/lightning.pb-c.h
@@ -20,6 +20,7 @@ typedef struct _Signature Signature;
typedef struct _Locktime Locktime;
typedef struct _BitcoinPubkey BitcoinPubkey;
typedef struct _Funding Funding;
+typedef struct _Authenticate Authenticate;
typedef struct _OpenChannel OpenChannel;
typedef struct _OpenAnchor OpenAnchor;
typedef struct _OpenCommitSig OpenCommitSig;
@@ -150,6 +151,26 @@ struct _Funding
/*
* Set channel params.
*/
+struct _Authenticate
+{
+ ProtobufCMessage base;
+ /*
+ * Which node this is.
+ */
+ BitcoinPubkey *node_id;
+ /*
+ * Signature of your session key. *
+ */
+ Signature *session_sig;
+};
+#define AUTHENTICATE__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&authenticate__descriptor) \
+ , NULL, NULL }
+
+
+/*
+ * Set channel params.
+ */
struct _OpenChannel
{
ProtobufCMessage base;
@@ -500,6 +521,7 @@ struct _Error
typedef enum {
PKT__PKT__NOT_SET = 0,
+ PKT__PKT_AUTH = 50,
PKT__PKT_OPEN = 20,
PKT__PKT_OPEN_ANCHOR = 21,
PKT__PKT_OPEN_COMMIT_SIG = 22,
@@ -528,6 +550,10 @@ struct _Pkt
Pkt__PktCase pkt_case;
union {
/*
+ * Start of connection
+ */
+ Authenticate *auth;
+ /*
* Opening
*/
OpenChannel *open;
@@ -658,6 +684,25 @@ Funding *
void funding__free_unpacked
(Funding *message,
ProtobufCAllocator *allocator);
+/* Authenticate methods */
+void authenticate__init
+ (Authenticate *message);
+size_t authenticate__get_packed_size
+ (const Authenticate *message);
+size_t authenticate__pack
+ (const Authenticate *message,
+ uint8_t *out);
+size_t authenticate__pack_to_buffer
+ (const Authenticate *message,
+ ProtobufCBuffer *buffer);
+Authenticate *
+ authenticate__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data);
+void authenticate__free_unpacked
+ (Authenticate *message,
+ ProtobufCAllocator *allocator);
/* OpenChannel methods */
void open_channel__init
(OpenChannel *message);
@@ -1017,6 +1062,9 @@ typedef void (*BitcoinPubkey_Closure)
typedef void (*Funding_Closure)
(const Funding *message,
void *closure_data);
+typedef void (*Authenticate_Closure)
+ (const Authenticate *message,
+ void *closure_data);
typedef void (*OpenChannel_Closure)
(const OpenChannel *message,
void *closure_data);
@@ -1082,6 +1130,7 @@ extern const ProtobufCMessageDescriptor signature__descriptor;
extern const ProtobufCMessageDescriptor locktime__descriptor;
extern const ProtobufCMessageDescriptor bitcoin_pubkey__descriptor;
extern const ProtobufCMessageDescriptor funding__descriptor;
+extern const ProtobufCMessageDescriptor authenticate__descriptor;
extern const ProtobufCMessageDescriptor open_channel__descriptor;
extern const ProtobufCEnumDescriptor open_channel__anchor_offer__descriptor;
extern const ProtobufCMessageDescriptor open_anchor__descriptor;
diff --git a/lightning.proto b/lightning.proto
index 5f0eb03..0c1fcdd 100644
--- a/lightning.proto
+++ b/lightning.proto
@@ -52,6 +52,14 @@ message funding {
//
// Set channel params.
+message authenticate {
+ // Which node this is.
+ required bitcoin_pubkey node_id = 1;
+ // Signature of your session key. */
+ required signature session_sig = 2;
+};
+
+// Set channel params.
message open_channel {
// Relative locktime for outputs going to us.
required locktime delay = 1;
@@ -205,6 +213,8 @@ message error {
// This is the union which defines all of them
message pkt {
oneof pkt {
+ // Start of connection
+ authenticate auth = 50;
// Opening
open_channel open = 20;
open_anchor open_anchor = 21;
📝 Original message:
OK, once I started trying to send data, I refined this a little so
we always send protobufs after the initial session key.
Feedback gratefully accepted!
Thanks,
Rusty.
--
After useful feedback from Anthony Towns and Mats Jerratsch (of
thunder.network fame), this is the third version of inter-node crypto.
1) First, each side sends a 33-byte session pubkey. This is a
bitcoin-style compressed EC key, unique for each session.
2) ECDH is used to derive a shared secret. From this we generate
the following transmission encoding parameters for each side:
Session AES-128 key: SHA256(shared-secret || my-sessionpubkey || 0)
Session HMAC key: SHA256(shared-secret || my-sessionpubkey || 1)
IV for AES: SHA256(shared-secret || my-sessionpubkey || 2)
3) All packets from then on are encrypted of form:
/* HMAC, covering totlen and data */
struct sha256 hmac;
/* Total data transmitted (including this). */
le64 totlen;
/* Encrypted contents, rounded up to 16 byte boundary. */
u8 data[];
4) The first packet is an Authenticate protobuf, containing this node's
pubkey, and a bitcoin-style EC signature of the other side's session
pubkey.
5) Unknown protobuf fields are handled in the protocol as follows
(including in the initial Authenticate packet):
1) Odd numbered fields are optional, and backwards compatible.
2) Even numbered fields are required; abort if you get one.
Currently both sides just send an error packet "hello" after the
handshake, and make sure they receive the same.
Signed-off-by: Rusty Russell <rusty at rustcorp.com.au>
diff --git a/daemon/Makefile b/daemon/Makefile
index 699cdc9..dfdcc0f 100644
--- a/daemon/Makefile
+++ b/daemon/Makefile
@@ -14,6 +14,7 @@ DAEMON_LIB_SRC := \
DAEMON_LIB_OBJS := $(DAEMON_LIB_SRC:.c=.o)
DAEMON_SRC := \
+ daemon/cryptopkt.c \
daemon/dns.c \
daemon/jsonrpc.c \
daemon/lightningd.c \
@@ -31,6 +32,7 @@ DAEMON_JSMN_HEADERS := daemon/jsmn/jsmn.h
DAEMON_HEADERS := \
daemon/configdir.h \
+ daemon/cryptopkt.h \
daemon/dns.h \
daemon/json.h \
daemon/jsonrpc.h \
diff --git a/daemon/cryptopkt.c b/daemon/cryptopkt.c
new file mode 100644
index 0000000..0667d6d
--- /dev/null
+++ b/daemon/cryptopkt.c
@@ -0,0 +1,511 @@
+#include "bitcoin/shadouble.h"
+#include "bitcoin/signature.h"
+#include "cryptopkt.h"
+#include "lightning.pb-c.h"
+#include "lightningd.h"
+#include "log.h"
+#include "peer.h"
+#include "protobuf_convert.h"
+#include "secrets.h"
+#include <ccan/build_assert/build_assert.h>
+#include <ccan/crypto/sha256/sha256.h>
+#include <ccan/endian/endian.h>
+#include <ccan/io/io_plan.h>
+#include <ccan/mem/mem.h>
+#include <ccan/short_types/short_types.h>
+#include <inttypes.h>
+#include <openssl/aes.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+#include <secp256k1.h>
+#include <secp256k1_ecdh.h>
+
+#define MAX_PKT_LEN (1024 * 1024)
+
+#define ROUNDUP(x,a) (((x) + ((a)-1)) & ~((a)-1))
+
+struct crypto_pkt {
+ /* HMAC */
+ struct sha256 hmac;
+ /* Total length transmitted. */
+ le64 totlen;
+ /* ... contents... */
+ u8 data[];
+};
+
+/* Temporary structure for negotiation (peer->io_data->neg) */
+struct key_negotiate {
+ /* Our session secret key. */
+ u8 seckey[32];
+
+ /* Our pubkey, their pubkey. */
+ u8 our_sessionpubkey[33], their_sessionpubkey[33];
+
+ /* Callback once it's all done. */
+ struct io_plan *(*cb)(struct io_conn *, struct peer *);
+};
+
+/* ARM loves to add padding to structs; be paranoid! */
+#define SESSION_PROOF_BASE_SIZE (64 + 33)
+
+#define ENCKEY_SEED 0
+#define HMACKEY_SEED 1
+#define IV_SEED 2
+
+struct enckey {
+ struct sha256 k;
+};
+
+struct hmackey {
+ struct sha256 k;
+};
+
+struct iv {
+ unsigned char iv[AES_BLOCK_SIZE];
+};
+
+static void sha_with_seed(const unsigned char secret[32],
+ const unsigned char serial_pubkey[33],
+ unsigned char seed,
+ struct sha256 *res)
+{
+ struct sha256_ctx ctx;
+
+ sha256_init(&ctx);
+ sha256_update(&ctx, memcheck(secret, 32), 32);
+ sha256_update(&ctx, memcheck(serial_pubkey, 33), 33);
+ sha256_u8(&ctx, seed);
+ sha256_done(&ctx, res);
+}
+
+static struct enckey enckey_from_secret(const unsigned char secret[32],
+ const unsigned char serial_pubkey[33])
+{
+ struct enckey enckey;
+ sha_with_seed(secret, serial_pubkey, ENCKEY_SEED, &enckey.k);
+ return enckey;
+}
+
+static struct hmackey hmackey_from_secret(const unsigned char secret[32],
+ const unsigned char serial_pubkey[33])
+{
+ struct hmackey hmackey;
+ sha_with_seed(secret, serial_pubkey, HMACKEY_SEED, &hmackey.k);
+ return hmackey;
+}
+
+static struct iv iv_from_secret(const unsigned char secret[32],
+ const unsigned char serial_pubkey[33])
+{
+ struct sha256 sha;
+ struct iv iv;
+
+ sha_with_seed(secret, serial_pubkey, IV_SEED, &sha);
+ memcpy(iv.iv, sha.u.u8, sizeof(iv.iv));
+ return iv;
+}
+
+struct dir_state {
+ u64 totlen;
+ struct hmackey hmackey;
+ EVP_CIPHER_CTX evpctx;
+
+ /* Current packet. */
+ struct crypto_pkt *cpkt;
+};
+
+static bool setup_crypto(struct dir_state *dir,
+ u8 shared_secret[32], u8 serial_pubkey[33])
+{
+ struct iv iv;
+ struct enckey enckey;
+
+ dir->totlen = 0;
+ dir->hmackey = hmackey_from_secret(shared_secret, serial_pubkey);
+ dir->cpkt = NULL;
+
+ iv = iv_from_secret(shared_secret, serial_pubkey);
+ enckey = enckey_from_secret(shared_secret, serial_pubkey);
+
+ return EVP_EncryptInit(&dir->evpctx, EVP_aes_128_ctr(),
+ memcheck(enckey.k.u.u8, sizeof(enckey.k)),
+ memcheck(iv.iv, sizeof(iv.iv))) == 1;
+}
+
+struct io_data {
+ /* Stuff we need to keep around to talk to peer. */
+ struct dir_state in, out;
+
+ /* Header we're currently reading. */
+ size_t len_in;
+ struct crypto_pkt hdr_in;
+
+ /* For negotiation phase. */
+ struct key_negotiate *neg;
+};
+
+static void *proto_tal_alloc(void *allocator_data, size_t size)
+{
+ return tal_arr(allocator_data, char, size);
+}
+
+static void proto_tal_free(void *allocator_data, void *pointer)
+{
+ tal_free(pointer);
+}
+
+static Pkt *decrypt_pkt(struct peer *peer, struct crypto_pkt *cpkt,
+ size_t data_len)
+{
+ size_t full_len;
+ struct sha256 hmac;
+ int outlen;
+ struct io_data *iod = peer->io_data;
+ struct ProtobufCAllocator prototal;
+ Pkt *ret;
+
+ full_len = ROUNDUP(data_len, AES_BLOCK_SIZE);
+
+ HMAC(EVP_sha256(), iod->in.hmackey.k.u.u8, sizeof(iod->in.hmackey),
+ (unsigned char *)&cpkt->totlen, sizeof(cpkt->totlen) + full_len,
+ hmac.u.u8, NULL);
+
+ if (CRYPTO_memcmp(&hmac, &cpkt->hmac, sizeof(hmac)) != 0) {
+ log_unusual(peer->log, "Packet has bad HMAC");
+ return NULL;
+ }
+
+ /* FIXME: Assumes we can decrypt in place! */
+ EVP_DecryptUpdate(&iod->in.evpctx, cpkt->data, &outlen,
+ memcheck(cpkt->data, full_len), full_len);
+ assert(outlen == full_len);
+
+ /* De-protobuf it. */
+ prototal.alloc = proto_tal_alloc;
+ prototal.free = proto_tal_free;
+ prototal.allocator_data = tal(iod, char);
+
+ ret = pkt__unpack(&prototal, data_len, cpkt->data);
+ if (!ret)
+ tal_free(prototal.allocator_data);
+ else
+ /* Make sure packet owns contents */
+ tal_steal(ret, prototal.allocator_data);
+ return ret;
+}
+
+static struct crypto_pkt *encrypt_pkt(struct peer *peer,
+ const Pkt *pkt,
+ size_t *total_len)
+{
+ static unsigned char zeroes[AES_BLOCK_SIZE-1];
+ struct crypto_pkt *cpkt;
+ unsigned char *dout;
+ size_t len, full_len;
+ int outlen;
+ struct io_data *iod = peer->io_data;
+
+ len = pkt__get_packed_size(pkt);
+ full_len = ROUNDUP(len, AES_BLOCK_SIZE);
+ *total_len = sizeof(*cpkt) + full_len;
+
+ cpkt = (struct crypto_pkt *)tal_arr(peer, char, *total_len);
+ iod->out.totlen += len;
+ cpkt->totlen = cpu_to_le64(iod->out.totlen);
+
+ dout = cpkt->data;
+ /* FIXME: Assumes we can encrypt in place! */
+ pkt__pack(pkt, dout);
+ EVP_EncryptUpdate(&iod->out.evpctx, dout, &outlen,
+ memcheck(dout, len), len);
+ dout += outlen;
+
+ /* Now encrypt tail, padding with zeroes if necessary. */
+ EVP_EncryptUpdate(&iod->out.evpctx, dout, &outlen, zeroes,
+ full_len - len);
+ assert(dout + outlen == cpkt->data + full_len);
+
+ HMAC(EVP_sha256(), iod->out.hmackey.k.u.u8, sizeof(iod->out.hmackey),
+ (unsigned char *)&cpkt->totlen, sizeof(cpkt->totlen) + full_len,
+ cpkt->hmac.u.u8, NULL);
+
+ return cpkt;
+}
+
+static int do_read_packet(int fd, struct io_plan_arg *arg)
+{
+ struct peer *peer = arg->u1.vp;
+ struct io_data *iod = peer->io_data;
+ u64 max;
+ size_t data_off, data_len;
+ int ret;
+
+ /* Still reading header? */
+ if (iod->len_in < sizeof(iod->hdr_in)) {
+ ret = read(fd, (char *)&iod->hdr_in + iod->len_in,
+ sizeof(iod->hdr_in) - iod->len_in);
+ if (ret <= 0)
+ return -1;
+ iod->len_in += ret;
+ /* We don't ever send empty packets, so don't check for
+ * that here. */
+ return 0;
+ }
+
+ max = ROUNDUP(le64_to_cpu(iod->hdr_in.totlen) - iod->in.totlen,
+ AES_BLOCK_SIZE);
+
+ if (iod->len_in == sizeof(iod->hdr_in)) {
+ /* FIXME: Handle re-xmit. */
+ if (le64_to_cpu(iod->hdr_in.totlen) < iod->in.totlen) {
+ log_unusual(peer->log,
+ "Packet went backwards: %"PRIu64
+ " -> %"PRIu64,
+ iod->in.totlen,
+ le64_to_cpu(iod->hdr_in.totlen));
+ return -1;
+ }
+ if (le64_to_cpu(iod->hdr_in.totlen)
+ > iod->in.totlen + MAX_PKT_LEN) {
+ log_unusual(peer->log,
+ "Packet overlength: %"PRIu64" -> %"PRIu64,
+ iod->in.totlen,
+ le64_to_cpu(iod->hdr_in.totlen));
+ return -1;
+ }
+ iod->in.cpkt = (struct crypto_pkt *)
+ tal_arr(iod, u8, sizeof(struct crypto_pkt) + max);
+ memcpy(iod->in.cpkt, &iod->hdr_in, sizeof(iod->hdr_in));
+ }
+
+ data_off = iod->len_in - sizeof(struct crypto_pkt);
+ ret = read(fd, iod->in.cpkt->data + data_off, max - data_off);
+ if (ret <= 0)
+ return -1;
+
+ iod->len_in += ret;
+ if (iod->len_in <= max)
+ return 0;
+
+ /* Can't overflow len arg: packet can't be more than MAX_PKT_LEN */
+ data_len = le64_to_cpu(iod->hdr_in.totlen) - iod->in.totlen;
+ peer->inpkt = decrypt_pkt(peer, iod->in.cpkt, data_len);
+ iod->in.cpkt = tal_free(iod->in.cpkt);
+
+ if (!peer->inpkt)
+ return -1;
+ iod->in.totlen += data_len;
+ return 1;
+}
+
+struct io_plan *peer_read_packet(struct io_conn *conn,
+ struct peer *peer,
+ struct io_plan *(*cb)(struct io_conn *,
+ struct peer *))
+{
+ struct io_plan_arg *arg = io_plan_arg(conn, IO_IN);
+
+ peer->io_data->len_in = 0;
+ arg->u1.vp = peer;
+ return io_set_plan(conn, IO_IN, do_read_packet,
+ (struct io_plan *(*)(struct io_conn *, void *))cb,
+ peer);
+}
+
+/* Caller must free data! */
+struct io_plan *peer_write_packet(struct io_conn *conn,
+ struct peer *peer,
+ const Pkt *pkt,
+ struct io_plan *(*next)(struct io_conn *,
+ struct peer *))
+{
+ struct io_data *iod = peer->io_data;
+ size_t totlen;
+
+ /* We free previous packet here, rather than doing indirection
+ * via io_write */
+ tal_free(iod->out.cpkt);
+ iod->out.cpkt = encrypt_pkt(peer, pkt, &totlen);
+ return io_write(conn, iod->out.cpkt, totlen, next, peer);
+}
+
+static void *pkt_unwrap(struct peer *peer, Pkt__PktCase which)
+{
+ size_t i;
+ const ProtobufCMessage *base;
+
+ if (peer->inpkt->pkt_case != which) {
+ log_unusual(peer->log, "Expected %u, got %u",
+ which, peer->inpkt->pkt_case);
+ return NULL;
+ }
+
+ /* It's a union, and each member starts with base. Pick one */
+ base = &peer->inpkt->error->base;
+
+ /* Look for unknown fields. Remember, "It's OK to be odd!" */
+ for (i = 0; i < base->n_unknown_fields; i++) {
+ log_debug(peer->log, "Unknown field in %u: %u",
+ which, base->unknown_fields[i].tag);
+ /* Odd is OK */
+ if (base->unknown_fields[i].tag & 1)
+ continue;
+ log_unusual(peer->log, "Unknown field %u in %u",
+ base->unknown_fields[i].tag, which);
+ return NULL;
+ }
+ return peer->inpkt->error;
+}
+
+static struct io_plan *check_proof(struct io_conn *conn, struct peer *peer)
+{
+ struct key_negotiate *neg = peer->io_data->neg;
+ struct sha256_double sha;
+ struct signature sig;
+ struct io_plan *(*cb)(struct io_conn *, struct peer *);
+ struct pubkey id;
+ Authenticate *auth;
+
+ auth = pkt_unwrap(peer, PKT__PKT_AUTH);
+ if (!auth)
+ return io_close(conn);
+
+ if (!proto_to_signature(auth->session_sig, &sig)) {
+ log_unusual(peer->log, "Invalid auth signature");
+ return io_close(conn);
+ }
+
+ if (!proto_to_pubkey(auth->node_id, &id)) {
+ log_unusual(peer->log, "Invalid auth id");
+ return io_close(conn);
+ }
+
+ /* Signature covers *our* session key. */
+ sha256_double(&sha,
+ neg->our_sessionpubkey, sizeof(neg->our_sessionpubkey));
+
+ if (!check_signed_hash(&sha, &sig, &id)) {
+ log_unusual(peer->log, "Bad auth signature");
+ return io_close(conn);
+ }
+
+ tal_free(auth);
+
+ /* All complete, return to caller. */
+ cb = neg->cb;
+ peer->io_data->neg = tal_free(neg);
+ return cb(conn, peer);
+}
+
+static struct io_plan *receive_proof(struct io_conn *conn, struct peer *peer)
+{
+ return peer_read_packet(conn, peer, check_proof);
+}
+
+/* Steals w onto the returned Pkt */
+static Pkt *pkt_wrap(const tal_t *ctx, void *w, Pkt__PktCase pkt_case)
+{
+ Pkt *pkt = tal(ctx, Pkt);
+ pkt__init(pkt);
+ pkt->pkt_case = pkt_case;
+ /* Union, so any will do */
+ pkt->error = tal_steal(pkt, w);
+ return pkt;
+}
+
+static Pkt *authenticate_pkt(const tal_t *ctx,
+ const struct pubkey *node_id,
+ const struct signature *sig)
+{
+ Authenticate *auth = tal(ctx, Authenticate);
+ authenticate__init(auth);
+ auth->node_id = pubkey_to_proto(auth, node_id);
+ auth->session_sig = signature_to_proto(auth, sig);
+ return pkt_wrap(ctx, auth, PKT__PKT_AUTH);
+}
+
+static struct io_plan *keys_exchanged(struct io_conn *conn, struct peer *peer)
+{
+ u8 shared_secret[32];
+ struct pubkey sessionkey;
+ struct signature sig;
+ struct key_negotiate *neg = peer->io_data->neg;
+ Pkt *auth;
+
+ if (!pubkey_from_der(neg->their_sessionpubkey,
+ sizeof(neg->their_sessionpubkey),
+ &sessionkey)) {
+ /* FIXME: Dump key in this case. */
+ log_unusual(peer->log, "Bad sessionkey");
+ return io_close(conn);
+ }
+
+ /* Derive shared secret. */
+ if (!secp256k1_ecdh(peer->state->secpctx, shared_secret,
+ &sessionkey.pubkey, neg->seckey)) {
+ log_unusual(peer->log, "Bad ECDH");
+ return io_close(conn);
+ }
+
+ /* Each side combines with their OWN session key to SENDING crypto. */
+ if (!setup_crypto(&peer->io_data->in, shared_secret,
+ neg->their_sessionpubkey)
+ || !setup_crypto(&peer->io_data->out, shared_secret,
+ neg->our_sessionpubkey)) {
+ log_unusual(peer->log, "Failed setup_crypto()");
+ return io_close(conn);
+ }
+
+ /* Now sign their session key to prove who we are. */
+ privkey_sign(peer, neg->their_sessionpubkey,
+ sizeof(neg->their_sessionpubkey), &sig);
+
+ /* FIXME: Free auth afterwards. */
+ auth = authenticate_pkt(peer, &peer->state->id, &sig);
+ return peer_write_packet(conn, peer, auth, receive_proof);
+}
+
+static struct io_plan *session_key_receive(struct io_conn *conn,
+ struct peer *peer)
+{
+ struct key_negotiate *neg = peer->io_data->neg;
+ /* Now read their key. */
+ return io_read(conn, neg->their_sessionpubkey,
+ sizeof(neg->their_sessionpubkey), keys_exchanged, peer);
+}
+
+static void gen_sessionkey(secp256k1_context *ctx,
+ u8 seckey[32],
+ secp256k1_pubkey *pubkey)
+{
+ do {
+ if (RAND_bytes(seckey, 32) != 1)
+ fatal("Could not get random bytes for sessionkey");
+ } while (!secp256k1_ec_pubkey_create(ctx, pubkey, seckey));
+}
+
+struct io_plan *peer_crypto_setup(struct io_conn *conn, struct peer *peer,
+ struct io_plan *(*cb)(struct io_conn *,
+ struct peer *))
+{
+ size_t outputlen;
+ secp256k1_pubkey sessionkey;
+ struct key_negotiate *neg;
+
+ peer->io_data = tal(peer, struct io_data);
+
+ /* We store negotiation state here. */
+ neg = peer->io_data->neg = tal(peer->io_data, struct key_negotiate);
+ neg->cb = cb;
+
+ gen_sessionkey(peer->state->secpctx, neg->seckey, &sessionkey);
+
+ secp256k1_ec_pubkey_serialize(peer->state->secpctx,
+ neg->our_sessionpubkey, &outputlen,
+ &sessionkey,
+ SECP256K1_EC_COMPRESSED);
+ assert(outputlen == sizeof(neg->our_sessionpubkey));
+ return io_write(conn, neg->our_sessionpubkey, outputlen,
+ session_key_receive, peer);
+}
diff --git a/daemon/cryptopkt.h b/daemon/cryptopkt.h
new file mode 100644
index 0000000..06c6167
--- /dev/null
+++ b/daemon/cryptopkt.h
@@ -0,0 +1,26 @@
+#ifndef LIGHTNING_DAEMON_CRYPTOPKT_H
+#define LIGHTNING_DAEMON_CRYPTOPKT_H
+#include "config.h"
+#include "lightning.pb-c.h"
+#include <ccan/io/io.h>
+
+struct peer;
+
+struct io_plan *peer_crypto_setup(struct io_conn *conn,
+ struct peer *peer,
+ struct io_plan *(*cb)(struct io_conn *,
+ struct peer *));
+
+/* Reads packet into peer->inpkt/peer->inpkt_len */
+struct io_plan *peer_read_packet(struct io_conn *conn,
+ struct peer *peer,
+ struct io_plan *(*cb)(struct io_conn *,
+ struct peer *));
+
+struct io_plan *peer_write_packet(struct io_conn *conn,
+ struct peer *peer,
+ const Pkt *pkt,
+ struct io_plan *(*next)(struct io_conn *,
+ struct peer *));
+
+#endif /* LIGHTNING_DAEMON_CRYPTOPKT_H */
diff --git a/daemon/peer.c b/daemon/peer.c
index 5f578ee..ed4fcda 100644
--- a/daemon/peer.c
+++ b/daemon/peer.c
@@ -1,3 +1,4 @@
+#include "cryptopkt.h"
#include "dns.h"
#include "jsonrpc.h"
#include "lightningd.h"
@@ -14,6 +15,33 @@
#include <sys/socket.h>
#include <sys/types.h>
+/* Send and receive (encrypted) hello message. */
+static struct io_plan *peer_test_check(struct io_conn *conn, struct peer *peer)
+{
+ if (peer->inpkt->pkt_case != PKT__PKT_ERROR)
+ fatal("Bad packet type %u", peer->inpkt->pkt_case);
+ if (!peer->inpkt->error->problem
+ || strcmp(peer->inpkt->error->problem, "hello") != 0)
+ fatal("Bad packet '%.6s'", peer->inpkt->error->problem);
+ log_info(peer->log, "Successful hello!");
+ return io_close(conn);
+}
+
+static struct io_plan *peer_test_read(struct io_conn *conn, struct peer *peer)
+{
+ return peer_read_packet(conn, peer, peer_test_check);
+}
+
+static struct io_plan *peer_test(struct io_conn *conn, struct peer *peer)
+{
+ Error err = ERROR__INIT;
+ Pkt pkt = PKT__INIT;
+ pkt.pkt_case = PKT__PKT_ERROR;
+ pkt.error = &err;
+ err.problem = "hello";
+ return peer_write_packet(conn, peer, &pkt, peer_test_read);
+}
+
static void destroy_peer(struct peer *peer)
{
list_del_from(&peer->state->peers, &peer->list);
@@ -32,6 +60,7 @@ static struct peer *new_peer(struct lightningd_state *state,
peer->state = state;
peer->addr.type = addr_type;
peer->addr.protocol = addr_protocol;
+ peer->io_data = NULL;
/* FIXME: Attach IO logging for this peer. */
tal_add_destructor(peer, destroy_peer);
@@ -63,7 +92,7 @@ struct io_plan *peer_connected_out(struct io_conn *conn,
return io_close(conn);
}
log_info(peer->log, "Connected out to %s:%s", name, port);
- return io_write(conn, "Hello!", 6, io_close_cb, NULL);
+ return peer_crypto_setup(conn, peer, peer_test);
}
static struct io_plan *peer_connected_in(struct io_conn *conn,
@@ -73,8 +102,9 @@ static struct io_plan *peer_connected_in(struct io_conn *conn,
"in");
if (!peer)
return io_close(conn);
-
- return io_write(conn, "Hello!", 6, io_close_cb, NULL);
+
+ log_info(peer->log, "Peer connected in");
+ return peer_crypto_setup(conn, peer, peer_test);
}
static int make_listen_fd(struct lightningd_state *state,
diff --git a/daemon/peer.h b/daemon/peer.h
index 50cea69..c39f943 100644
--- a/daemon/peer.h
+++ b/daemon/peer.h
@@ -1,6 +1,7 @@
#ifndef LIGHTNING_DAEMON_PEER_H
#define LIGHTNING_DAEMON_PEER_H
#include "config.h"
+#include "lightning.pb-c.h"
#include "netaddr.h"
#include <ccan/list/list.h>
@@ -14,6 +15,12 @@ struct peer {
/* The other end's address. */
struct netaddr addr;
+ /* Current received packet. */
+ Pkt *inpkt;
+
+ /* Current ongoing packetflow */
+ struct io_data *io_data;
+
/* What happened. */
struct log *log;
};
diff --git a/lightning.pb-c.c b/lightning.pb-c.c
index 5dde94f..62777aa 100644
--- a/lightning.pb-c.c
+++ b/lightning.pb-c.c
@@ -222,6 +222,49 @@ void funding__free_unpacked
assert(message->base.descriptor == &funding__descriptor);
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
}
+void authenticate__init
+ (Authenticate *message)
+{
+ static Authenticate init_value = AUTHENTICATE__INIT;
+ *message = init_value;
+}
+size_t authenticate__get_packed_size
+ (const Authenticate *message)
+{
+ assert(message->base.descriptor == &authenticate__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t authenticate__pack
+ (const Authenticate *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &authenticate__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t authenticate__pack_to_buffer
+ (const Authenticate *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &authenticate__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+Authenticate *
+ authenticate__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (Authenticate *)
+ protobuf_c_message_unpack (&authenticate__descriptor,
+ allocator, len, data);
+}
+void authenticate__free_unpacked
+ (Authenticate *message,
+ ProtobufCAllocator *allocator)
+{
+ assert(message->base.descriptor == &authenticate__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
void open_channel__init
(OpenChannel *message)
{
@@ -1344,6 +1387,57 @@ const ProtobufCMessageDescriptor funding__descriptor =
(ProtobufCMessageInit) funding__init,
NULL,NULL,NULL /* reserved[123] */
};
+static const ProtobufCFieldDescriptor authenticate__field_descriptors[2] =
+{
+ {
+ "node_id",
+ 1,
+ PROTOBUF_C_LABEL_REQUIRED,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(Authenticate, node_id),
+ &bitcoin_pubkey__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "session_sig",
+ 2,
+ PROTOBUF_C_LABEL_REQUIRED,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(Authenticate, session_sig),
+ &signature__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned authenticate__field_indices_by_name[] = {
+ 0, /* field[0] = node_id */
+ 1, /* field[1] = session_sig */
+};
+static const ProtobufCIntRange authenticate__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 2 }
+};
+const ProtobufCMessageDescriptor authenticate__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "authenticate",
+ "Authenticate",
+ "Authenticate",
+ "",
+ sizeof(Authenticate),
+ 2,
+ authenticate__field_descriptors,
+ authenticate__field_indices_by_name,
+ 1, authenticate__number_ranges,
+ (ProtobufCMessageInit) authenticate__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
static const ProtobufCEnumValue open_channel__anchor_offer__enum_values_by_number[2] =
{
{ "WILL_CREATE_ANCHOR", "OPEN_CHANNEL__ANCHOR_OFFER__WILL_CREATE_ANCHOR", 1 },
@@ -2259,7 +2353,7 @@ const ProtobufCMessageDescriptor error__descriptor =
(ProtobufCMessageInit) error__init,
NULL,NULL,NULL /* reserved[123] */
};
-static const ProtobufCFieldDescriptor pkt__field_descriptors[17] =
+static const ProtobufCFieldDescriptor pkt__field_descriptors[18] =
{
{
"update",
@@ -2465,8 +2559,21 @@ static const ProtobufCFieldDescriptor pkt__field_descriptors[17] =
0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
+ {
+ "auth",
+ 50,
+ PROTOBUF_C_LABEL_OPTIONAL,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(Pkt, pkt_case),
+ offsetof(Pkt, auth),
+ &authenticate__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
};
static const unsigned pkt__field_indices_by_name[] = {
+ 17, /* field[17] = auth */
13, /* field[13] = close */
15, /* field[15] = close_ack */
14, /* field[14] = close_complete */
@@ -2485,13 +2592,14 @@ static const unsigned pkt__field_indices_by_name[] = {
3, /* field[3] = update_signature */
7, /* field[7] = update_timedout_htlc */
};
-static const ProtobufCIntRange pkt__number_ranges[4 + 1] =
+static const ProtobufCIntRange pkt__number_ranges[5 + 1] =
{
{ 1, 0 },
{ 20, 9 },
{ 30, 13 },
{ 40, 16 },
- { 0, 17 }
+ { 50, 17 },
+ { 0, 18 }
};
const ProtobufCMessageDescriptor pkt__descriptor =
{
@@ -2501,10 +2609,10 @@ const ProtobufCMessageDescriptor pkt__descriptor =
"Pkt",
"",
sizeof(Pkt),
- 17,
+ 18,
pkt__field_descriptors,
pkt__field_indices_by_name,
- 4, pkt__number_ranges,
+ 5, pkt__number_ranges,
(ProtobufCMessageInit) pkt__init,
NULL,NULL,NULL /* reserved[123] */
};
diff --git a/lightning.pb-c.h b/lightning.pb-c.h
index 26219cc..4178a1e 100644
--- a/lightning.pb-c.h
+++ b/lightning.pb-c.h
@@ -20,6 +20,7 @@ typedef struct _Signature Signature;
typedef struct _Locktime Locktime;
typedef struct _BitcoinPubkey BitcoinPubkey;
typedef struct _Funding Funding;
+typedef struct _Authenticate Authenticate;
typedef struct _OpenChannel OpenChannel;
typedef struct _OpenAnchor OpenAnchor;
typedef struct _OpenCommitSig OpenCommitSig;
@@ -150,6 +151,26 @@ struct _Funding
/*
* Set channel params.
*/
+struct _Authenticate
+{
+ ProtobufCMessage base;
+ /*
+ * Which node this is.
+ */
+ BitcoinPubkey *node_id;
+ /*
+ * Signature of your session key. *
+ */
+ Signature *session_sig;
+};
+#define AUTHENTICATE__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&authenticate__descriptor) \
+ , NULL, NULL }
+
+
+/*
+ * Set channel params.
+ */
struct _OpenChannel
{
ProtobufCMessage base;
@@ -500,6 +521,7 @@ struct _Error
typedef enum {
PKT__PKT__NOT_SET = 0,
+ PKT__PKT_AUTH = 50,
PKT__PKT_OPEN = 20,
PKT__PKT_OPEN_ANCHOR = 21,
PKT__PKT_OPEN_COMMIT_SIG = 22,
@@ -528,6 +550,10 @@ struct _Pkt
Pkt__PktCase pkt_case;
union {
/*
+ * Start of connection
+ */
+ Authenticate *auth;
+ /*
* Opening
*/
OpenChannel *open;
@@ -658,6 +684,25 @@ Funding *
void funding__free_unpacked
(Funding *message,
ProtobufCAllocator *allocator);
+/* Authenticate methods */
+void authenticate__init
+ (Authenticate *message);
+size_t authenticate__get_packed_size
+ (const Authenticate *message);
+size_t authenticate__pack
+ (const Authenticate *message,
+ uint8_t *out);
+size_t authenticate__pack_to_buffer
+ (const Authenticate *message,
+ ProtobufCBuffer *buffer);
+Authenticate *
+ authenticate__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data);
+void authenticate__free_unpacked
+ (Authenticate *message,
+ ProtobufCAllocator *allocator);
/* OpenChannel methods */
void open_channel__init
(OpenChannel *message);
@@ -1017,6 +1062,9 @@ typedef void (*BitcoinPubkey_Closure)
typedef void (*Funding_Closure)
(const Funding *message,
void *closure_data);
+typedef void (*Authenticate_Closure)
+ (const Authenticate *message,
+ void *closure_data);
typedef void (*OpenChannel_Closure)
(const OpenChannel *message,
void *closure_data);
@@ -1082,6 +1130,7 @@ extern const ProtobufCMessageDescriptor signature__descriptor;
extern const ProtobufCMessageDescriptor locktime__descriptor;
extern const ProtobufCMessageDescriptor bitcoin_pubkey__descriptor;
extern const ProtobufCMessageDescriptor funding__descriptor;
+extern const ProtobufCMessageDescriptor authenticate__descriptor;
extern const ProtobufCMessageDescriptor open_channel__descriptor;
extern const ProtobufCEnumDescriptor open_channel__anchor_offer__descriptor;
extern const ProtobufCMessageDescriptor open_anchor__descriptor;
diff --git a/lightning.proto b/lightning.proto
index 5f0eb03..0c1fcdd 100644
--- a/lightning.proto
+++ b/lightning.proto
@@ -52,6 +52,14 @@ message funding {
//
// Set channel params.
+message authenticate {
+ // Which node this is.
+ required bitcoin_pubkey node_id = 1;
+ // Signature of your session key. */
+ required signature session_sig = 2;
+};
+
+// Set channel params.
message open_channel {
// Relative locktime for outputs going to us.
required locktime delay = 1;
@@ -205,6 +213,8 @@ message error {
// This is the union which defines all of them
message pkt {
oneof pkt {
+ // Start of connection
+ authenticate auth = 50;
// Opening
open_channel open = 20;
open_anchor open_anchor = 21;