From 32e1b5bb0675fd4c8cde17b7505ce6f6a2ed7205 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 10 Jan 2017 15:38:33 +1030 Subject: [PATCH] lightningd/handshake: simple daemon to do BOLT 8 crypto handshake. Signed-off-by: Rusty Russell --- lightningd/Makefile | 3 +- lightningd/handshake/Makefile | 57 + lightningd/handshake/handshake.c | 1025 +++++++++++++++++ .../handshake/handshake_control_wire_csv | 12 + .../handshake/handshake_status_wire_csv | 31 + lightningd/lightningd.c | 3 +- 6 files changed, 1129 insertions(+), 2 deletions(-) create mode 100644 lightningd/handshake/Makefile create mode 100644 lightningd/handshake/handshake.c create mode 100644 lightningd/handshake/handshake_control_wire_csv create mode 100644 lightningd/handshake/handshake_status_wire_csv diff --git a/lightningd/Makefile b/lightningd/Makefile index 89b87de05..1b2eb9dbb 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -4,7 +4,7 @@ lightningd-wrongdir: $(MAKE) -C .. lightningd-all -lightningd-all: lightningd/lightningd lightningd/lightningd_hsm +lightningd-all: lightningd/lightningd lightningd/lightningd_hsm lightningd/lightningd_handshake default: lightningd-all @@ -46,6 +46,7 @@ LIGHTNINGD_HEADERS := \ $(LIGHTNINGD_OBJS) $(LIGHTNINGD_LIB_OBJS): $(LIGHTNINGD_HEADERS) $(LIGHTNINGD_JSMN_HEADERS) $(BITCOIN_HEADERS) $(CORE_HEADERS) $(GEN_HEADERS) $(CCAN_HEADERS) $(DAEMON_HEADERS) $(LIGHTNINGD_HSM_CONTROL_HEADERS) $(LIBBASE58_HEADERS) include lightningd/hsm/Makefile +include lightningd/handshake/Makefile check-source: $(LIGHTNINGD_SRC:%=check-src-include-order/%) check-source: $(LIGHTNINGD_LIB_SRC:%=check-src-include-order/%) diff --git a/lightningd/handshake/Makefile b/lightningd/handshake/Makefile new file mode 100644 index 000000000..d74a3f429 --- /dev/null +++ b/lightningd/handshake/Makefile @@ -0,0 +1,57 @@ +#! /usr/bin/make + +# Designed to be run one level up +lightningd/handshake-wrongdir: + $(MAKE) -C .. lightningd/handshake-all + +default: lightningd/handshake-all + +lightningd/handshake-all: lightningd/lightningd_handshake + +# lightningd/handshake needs these: +LIGHTNINGD_HANDSHAKE_HEADERS := \ + lightningd/handshake/gen_handshake_control_wire.h \ + lightningd/handshake/gen_handshake_status_wire.h +LIGHTNINGD_HANDSHAKE_SRC := lightningd/handshake/handshake.c \ + $(LIGHTNINGD_HANDSHAKE_HEADERS:.h=.c) +LIGHTNINGD_HANDSHAKE_OBJS := $(LIGHTNINGD_HANDSHAKE_SRC:.c=.o) + +# Control daemon uses this: +LIGHTNINGD_HANDSHAKE_CONTROL_HEADERS := $(LIGHTNINGD_HANDSHAKE_HEADERS) +LIGHTNINGD_HANDSHAKE_CONTROL_SRC := $(LIGHTNINGD_HANDSHAKE_HEADERS:.h=.c) +LIGHTNINGD_HANDSHAKE_CONTROL_OBJS := $(LIGHTNINGD_HANDSHAKE_CONTROL_SRC:.c=.o) + +LIGHTNINGD_HANDSHAKE_GEN_SRC := $(filter lightningd/handshake/gen_%, $(LIGHTNINGD_HANDSHAKE_SRC) $(LIGHTNINGD_HANDSHAKE_CONTROL_SRC)) + +LIGHTNINGD_HANDSHAKE_SRC_NOGEN := $(filter-out lightningd/handshake/gen_%, $(LIGHTNINGD_HANDSHAKE_SRC)) + +$(LIGHTNINGD_HANDSHAKE_OBJS): $(CCAN_HEADERS) $(CORE_HEADERS) $(GEN_HEADERS) $(WIRE_HEADERS) $(BITCOIN_HEADERS) $(LIGHTNINGD_HANDSHAKE_HEADERS) $(LIGHTNINGD_HSM_CLIENT_HEADERS) $(LIBBASE58_HEADERS) + +lightningd/handshake/gen_handshake_control_wire.h: $(WIRE_GEN) lightningd/handshake/handshake_control_wire_csv + $(WIRE_GEN) --header $@ handshake_control_wire_type < lightningd/handshake/handshake_control_wire_csv > $@ + +lightningd/handshake/gen_handshake_control_wire.c: $(WIRE_GEN) lightningd/handshake/handshake_control_wire_csv + $(WIRE_GEN) ${@:.c=.h} handshake_control_wire_type < lightningd/handshake/handshake_control_wire_csv > $@ + +lightningd/handshake/gen_handshake_status_wire.h: $(WIRE_GEN) lightningd/handshake/handshake_status_wire_csv + $(WIRE_GEN) --header $@ handshake_status_wire_type < lightningd/handshake/handshake_status_wire_csv > $@ + +lightningd/handshake/gen_handshake_status_wire.c: $(WIRE_GEN) lightningd/handshake/handshake_status_wire_csv + $(WIRE_GEN) ${@:.c=.h} handshake_status_wire_type < lightningd/handshake/handshake_status_wire_csv > $@ + +LIGHTNINGD_HANDSHAKE_OBJS := $(LIGHTNINGD_HANDSHAKE_SRC:.c=.o) $(LIGHTNINGD_HANDSHAKE_GEN_SRC:.c=.o) + +lightningd/lightningd_handshake: $(LIGHTNINGD_LIB_OBJS) $(LIGHTNINGD_HANDSHAKE_OBJS) $(CORE_OBJS) $(WIRE_OBJS) $(BITCOIN_OBJS) $(CCAN_OBJS) $(LIGHTNINGD_HSM_CLIENT_OBJS) $(LIBBASE58_OBJS) libsecp256k1.a + $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) + +check-source: $(LIGHTNINGD_HANDSHAKE_SRC_NOGEN:%=check-src-include-order/%) +check-source-bolt: $(LIGHTNINGD_HANDSHAKE_SRC:%=bolt-check/%) $(LIGHTNINGD_HANDSHAKE_HEADERS:%=bolt-check/%) + +check-whitespace: $(LIGHTNINGD_HANDSHAKE_SRC_NOGEN:%=check-whitespace/%) $(LIGHTNINGD_HANDSHAKE_HEADERS_NOGEN:%=check-whitespace/%) + +clean: lightningd/handshake-clean + +lightningd/handshake-clean: + $(RM) $(LIGHTNINGD_HANDSHAKE_OBJS) gen_* + +-include lightningd/handshake/test/Makefile diff --git a/lightningd/handshake/handshake.c b/lightningd/handshake/handshake.c new file mode 100644 index 000000000..cd62e6950 --- /dev/null +++ b/lightningd/handshake/handshake.c @@ -0,0 +1,1025 @@ +#include "status.h" +#include "type_to_string.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Stdout == status, stdin == requests, 3 == client */ +#define STATUS_FD STDOUT_FILENO +#define REQ_FD STDIN_FILENO + +/* Representing chacha keys and ecdh results we derive them from; + * even though it's not really an SHA */ +struct secret { + struct sha256 s; +}; + +/* BOLT #8: + * + * * `generateKey()` + * * where generateKey generates and returns a fresh `secp256k1` keypair + * * the object returned by `generateKey` has two attributes: + * * `.pub`: which returns an abstract object representing the + * public key + * * `.priv`: which represents the private key used to generate the + * public key + */ +struct keypair { + struct pubkey pub; + struct privkey priv; +}; + +static struct keypair generate_key(void) +{ + struct keypair k; + + do { + randombytes_buf(k.priv.secret, sizeof(k.priv.secret)); + } while (!secp256k1_ec_pubkey_create(secp256k1_ctx, + &k.pub.pubkey, k.priv.secret)); + return k; +} + +/* BOLT #8: + * + * Throughout the handshake process, each side maintains these variables: + * + * * `ck`: The **chaining key**. This value is the accumulated hash of all + * previous ECDH outputs. At the end of the handshake, `ck` is used to + * derive the encryption keys for lightning messages. + * + * * `h`: The **handshake hash**. This value is the accumulated hash of _all_ + * handshake data that has been sent and received so far during the + * handshake process. + * + * * `temp_k1`, `temp_k2`, `temp_k3`: **intermediate keys** used to + * encrypt/decrypt the zero-length AEAD payloads at the end of each + * handshake message. + * + * * `e`: A party's **ephemeral keypair**. For each session a node MUST + * generate a new ephemeral key with strong cryptographic randomness. + * + * * `s`: A party's **static public key** (`ls` for local, `rs` for remote) + */ +struct handshake { + struct secret ck; + struct secret temp_k; + struct sha256 h; + struct keypair e; + struct secret ss; +}; + +/* h = SHA-256(h || data) */ +static void sha_mix_in(struct sha256 *h, const void *data, size_t len) +{ + struct sha256_ctx shactx; + + sha256_init(&shactx); + sha256_update(&shactx, h, sizeof(*h)); + sha256_update(&shactx, data, len); + sha256_done(&shactx, h); +} + +/* h = SHA-256(h || pub.serializeCompressed()) */ +static void sha_mix_in_key(struct sha256 *h, const struct pubkey *key) +{ + u8 der[PUBKEY_DER_LEN]; + size_t len = sizeof(der); + + secp256k1_ec_pubkey_serialize(secp256k1_ctx, der, &len, &key->pubkey, + SECP256K1_EC_COMPRESSED); + assert(len == sizeof(der)); + sha_mix_in(h, der, sizeof(der)); +} + +/* out1, out2 = HKDF(in1, in2)` */ +static void hkdf_two_keys(struct secret *out1, struct secret *out2, + const struct secret *in1, + const void *in2, size_t in2_size) +{ + /* BOLT #8: + * + * * `HKDF(salt,ikm)`: a function is defined in [5](#reference-5), + * evaluated with a zero-length `info` field. + * * All invocations of the `HKDF` implicitly return `64-bytes` + * of cryptographic randomness using the extract-and-expand + * component of the `HKDF`. + */ + struct secret okm[2]; + + status_trace("# HKDF(0x%s,%s%s)", + tal_hexstr(trc, in1, sizeof(*in1)), + in2_size ? "0x" : "zero", + tal_hexstr(trc, in2, in2_size)); + BUILD_ASSERT(sizeof(okm) == 64); + hkdf_sha256(okm, sizeof(okm), in1, sizeof(*in1), in2, in2_size, + NULL, 0); + *out1 = okm[0]; + *out2 = okm[1]; +} + +static void le64_nonce(unsigned char *npub, u64 nonce) +{ + /* BOLT #8: + * + * ...with nonce `n` encoded as 32 zero bits followed by a + * *little-endian* 64-bit value (this follows the Noise Protocol + * convention, rather than our normal endian). + */ + le64 le_nonce = cpu_to_le64(nonce); + const size_t zerolen = crypto_aead_chacha20poly1305_ietf_NPUBBYTES - sizeof(le_nonce); + + BUILD_ASSERT(crypto_aead_chacha20poly1305_ietf_NPUBBYTES >= sizeof(le_nonce)); + /* First part is 0, followed by nonce. */ + memset(npub, 0, zerolen); + memcpy(npub + zerolen, &le_nonce, sizeof(le_nonce)); +} + +/* BOLT #8: + * * `encryptWithAD(k, n, ad, plaintext)`: outputs `encrypt(k, n, ad, + * plaintext)` + * * where `encrypt` is an evaluation of `ChaCha20-Poly1305` (IETF + * variant) with the passed arguments, with nonce `n` + */ +static void encrypt_ad(const struct secret *k, u64 nonce, + const void *additional_data, size_t additional_data_len, + const void *plaintext, size_t plaintext_len, + void *output, size_t outputlen) +{ + unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES]; + unsigned long long clen; + int ret; + + assert(outputlen == plaintext_len + crypto_aead_chacha20poly1305_ietf_ABYTES); + le64_nonce(npub, nonce); + BUILD_ASSERT(sizeof(*k) == crypto_aead_chacha20poly1305_ietf_KEYBYTES); + status_trace("# encryptWithAD(0x%s, 0x%s, 0x%s, %s%s)", + tal_hexstr(trc, k, sizeof(*k)), + tal_hexstr(trc, npub, sizeof(npub)), + tal_hexstr(trc, additional_data, additional_data_len), + plaintext_len ? "0x" : "", + tal_hexstr(trc, plaintext, plaintext_len)); + + ret = crypto_aead_chacha20poly1305_ietf_encrypt(output, &clen, + memcheck(plaintext, plaintext_len), + plaintext_len, + additional_data, additional_data_len, + NULL, npub, k->s.u.u8); + assert(ret == 0); + assert(clen == plaintext_len + crypto_aead_chacha20poly1305_ietf_ABYTES); +} + +/* BOLT #8: + * * `decryptWithAD(k, n, ad, ciphertext)`: outputs `decrypt(k, n, ad, + * ciphertext)` + * * where `decrypt` is an evaluation of `ChaCha20-Poly1305` (IETF + * variant) with the passed arguments, with nonce `n` + */ +static bool decrypt(const struct secret *k, u64 nonce, + const void *additional_data, size_t additional_data_len, + const void *ciphertext, size_t ciphertext_len, + void *output, size_t outputlen) +{ + unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES]; + unsigned long long mlen; + + assert(outputlen == ciphertext_len - crypto_aead_chacha20poly1305_ietf_ABYTES); + + le64_nonce(npub, nonce); + BUILD_ASSERT(sizeof(*k) == crypto_aead_chacha20poly1305_ietf_KEYBYTES); + status_trace("# decryptWithAD(0x%s, 0x%s, 0x%s, 0x%s)", + tal_hexstr(trc, k, sizeof(*k)), + tal_hexstr(trc, npub, sizeof(npub)), + tal_hexstr(trc, additional_data, additional_data_len), + tal_hexstr(trc, ciphertext, ciphertext_len)); + if (crypto_aead_chacha20poly1305_ietf_decrypt(output, &mlen, NULL, + memcheck(ciphertext, ciphertext_len), + ciphertext_len, + additional_data, additional_data_len, + npub, k->s.u.u8) != 0) + return false; + + assert(mlen == ciphertext_len - crypto_aead_chacha20poly1305_ietf_ABYTES); + return true; +} + +static struct handshake *new_handshake(const tal_t *ctx, + const struct pubkey *id) +{ + struct handshake *handshake = tal(ctx, struct handshake); + + /* BOLT #8: + * + * Before the start of the first act, both sides initialize their + * per-sessions state as follows: + * + * 1. `h = SHA-256(protocolName)` + * * where `protocolName = "Noise_XK_secp256k1_ChaChaPoly_SHA256"` + * encoded as an ascii string. + */ + sha256(&handshake->h, "Noise_XK_secp256k1_ChaChaPoly_SHA256", + strlen("Noise_XK_secp256k1_ChaChaPoly_SHA256")); + + /* BOLT #8: + * + * 2. `ck = h` + */ + BUILD_ASSERT(sizeof(handshake->h) == sizeof(handshake->ck)); + memcpy(&handshake->ck, &handshake->h, sizeof(handshake->ck)); + status_trace("# ck=%s", + tal_hexstr(trc, &handshake->ck, sizeof(handshake->ck))); + + /* BOLT #8: + * + * 3. `h = SHA-256(h || prologue)` + * * where `prologue` is the ascii string: `lightning`. + */ + sha_mix_in(&handshake->h, "lightning", strlen("lightning")); + + /* BOLT #8: + * + * As a concluding step, both sides mix the responder's public key + * into the handshake digest: + * + * * The initiating node mixes in the responding node's static public + * key serialized in Bitcoin's DER compressed format: + * * `h = SHA-256(h || rs.pub.serializeCompressed())` + * + * * The responding node mixes in their local static public key + * serialized in Bitcoin's DER compressed format: + * * `h = SHA-256(h || ls.pub.serializeCompressed())` + */ + sha_mix_in_key(&handshake->h, id); + status_trace("# h=%s", + tal_hexstr(trc, &handshake->h, sizeof(handshake->h))); + + return handshake; +} + +/* BOLT #8: + * + * Act One is sent from initiator to responder. During `Act One`, the + * initiator attempts to satisfy an implicit challenge by the responder. To + * complete this challenge, the initiator _must_ know the static public key of + * the responder. + */ +struct act_one { + u8 v; + u8 pubkey[PUBKEY_DER_LEN]; + u8 tag[crypto_aead_chacha20poly1305_ietf_ABYTES]; +}; + +/* BOLT #8: The handshake message is _exactly_ `50 bytes` */ +#define ACT_ONE_SIZE 50 /* ARM's stupid ABI adds padding. */ + +static inline void check_act_one(const struct act_one *act1) +{ + /* BOLT #8: + * + * : `1 byte` for the handshake version, `33 bytes` for the compressed + * ephemeral public key of the initiator, and `16 bytes` for the + * `poly1305` tag. + */ + BUILD_ASSERT(sizeof(act1->v) == 1); + BUILD_ASSERT(sizeof(act1->pubkey) == 33); + BUILD_ASSERT(sizeof(act1->tag) == 16); +} + +static void act_one_initiator(struct handshake *h, int fd, + const struct pubkey *their_id) +{ + struct act_one act1; + size_t len; + + status_send(towire_initr_act_one(h)); + + /* BOLT #8: + * + * **Sender Actions:** + * + * * `e = generateKey()` + */ + h->e = generate_key(); + status_trace("e.priv: 0x%s", + tal_hexstr(trc, &h->e.priv, sizeof(h->e.priv))); + status_trace("e.pub: 0x%s", + type_to_string(trc, struct pubkey, &h->e.pub)); + + /* BOLT #8: + * + * * `h = SHA-256(h || e.pub.serializeCompressed())` + * * The newly generated ephemeral key is accumulated into our + * running handshake digest. + */ + sha_mix_in_key(&h->h, &h->e.pub); + status_trace("# h=0x%s", tal_hexstr(trc, &h->h, sizeof(h->h))); + + /* BOLT #8: + * + * * `ss = ECDH(rs, e.priv)` + * * The initiator performs a `ECDH` between its newly generated + * ephemeral key with the remote node's static public key. + */ + if (!secp256k1_ecdh(secp256k1_ctx, h->ss.s.u.u8, + &their_id->pubkey, h->e.priv.secret)) + status_failed(WIRE_INITR_ACT1_BAD_ECDH_FOR_SS, "%s", ""); + status_trace("# ss=0x%s", tal_hexstr(trc, &h->ss.s, sizeof(h->ss.s))); + + /* BOLT #8: + * + * * `ck, temp_k1 = HKDF(ck, ss)` + * * This phase generates a new temporary encryption key + * which is used to generate the authenticating MAC. + */ + hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss)); + status_trace("# ck,temp_k1=0x%s,0x%s", + tal_hexstr(trc, &h->ck, sizeof(h->ck)), + tal_hexstr(trc, &h->temp_k, sizeof(h->temp_k))); + + /* BOLT #8: + * + * * `c = encryptWithAD(temp_k1, 0, h, zero)` + * * where `zero` is a zero-length plaintext + */ + encrypt_ad(&h->temp_k, 0, &h->h, sizeof(h->h), NULL, 0, + act1.tag, sizeof(act1.tag)); + status_trace("# c=%s", tal_hexstr(trc, act1.tag, sizeof(act1.tag))); + + /* BOLT #8: + * + * * `h = SHA-256(h || c)` + * * Finally, the generated ciphertext is accumulated into the + * authenticating handshake digest. + */ + sha_mix_in(&h->h, act1.tag, sizeof(act1.tag)); + status_trace("# h=0x%s", tal_hexstr(trc, &h->h, sizeof(h->h))); + + /* BOLT #8: + * + * * Send `m = 0 || e.pub.serializeCompressed() || c` to the responder over the network buffer. + */ + act1.v = 0; + len = sizeof(act1.pubkey); + secp256k1_ec_pubkey_serialize(secp256k1_ctx, act1.pubkey, &len, + &h->e.pub.pubkey, + SECP256K1_EC_COMPRESSED); + status_trace("output: 0x%s", tal_hexstr(trc, &act1, ACT_ONE_SIZE)); + if (!write_all(fd, &act1, ACT_ONE_SIZE)) + status_failed(WIRE_INITR_ACT1_WRITE_FAILED, + "%s", strerror(errno)); +} + +static void act_one_responder(struct handshake *h, int fd, struct pubkey *re) +{ + struct act_one act1; + + status_send(towire_respr_act_one(h)); + + /* BOLT #8: + * + * * Read _exactly_ `50-bytes` from the network buffer. + * + * * Parse out the read message (`m`) into `v = m[0]`, `re = + * m[1:34]` and `c = m[43:]` + * * where `m[0]` is the _first_ byte of `m`, `m[1:33]` are the + * next `33` bytes of `m` and `m[34:]` is the last 16 bytes of + * `m` + */ + if (!read_all(fd, &act1, ACT_ONE_SIZE)) + status_failed(WIRE_RESPR_ACT1_READ_FAILED, + "%s", strerror(errno)); + status_trace("input: 0x%s", tal_hexstr(trc, &act1, ACT_ONE_SIZE)); + + /* BOLT #8: + * + * * If `v` is an unrecognized handshake version, then the responder + * MUST abort the connection attempt. + */ + if (act1.v != 0) + status_failed(WIRE_RESPR_ACT1_BAD_VERSION, "%u", act1.v); + + /* BOLT #8: + * * The raw bytes of the remote party's ephemeral public key + * (`e`) are to be deserialized into a point on the curve using + * affine coordinates as encoded by the key's serialized + * composed format. + */ + if (secp256k1_ec_pubkey_parse(secp256k1_ctx, &re->pubkey, + act1.pubkey, sizeof(act1.pubkey)) != 1) + status_failed(WIRE_RESPR_ACT1_BAD_PUBKEY, "%s", + tal_hexstr(trc, &act1.pubkey, + sizeof(act1.pubkey))); + status_trace("# re=0x%s", type_to_string(trc, struct pubkey, re)); + + /* BOLT #8: + * + * * `h = SHA-256(h || re.serializeCompressed())` + * * Accumulate the initiator's ephemeral key into the + * authenticating handshake digest. + */ + sha_mix_in_key(&h->h, re); + status_trace("# h=0x%s", tal_hexstr(trc, &h->h, sizeof(h->h))); + + /* BOLT #8: + * * `ss = ECDH(re, s.priv)` + * * The responder performs an `ECDH` between its static public + * key and the initiator's ephemeral public key. + */ + if (!hsm_do_ecdh(&h->ss.s, re)) + status_failed(WIRE_RESPR_ACT1_BAD_HSM_ECDH, + "re=%s", + type_to_string(trc, struct pubkey, re)); + status_trace("# ss=0x%s", tal_hexstr(trc, &h->ss, sizeof(h->ss))); + + /* BOLT #8: + * + * * `ck, temp_k1 = HKDF(ck, ss)` + * * This phase generates a new temporary encryption key + * which will be used to shortly check the + * authenticating MAC. + */ + hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss)); + status_trace("# ck,temp_k1=0x%s,0x%s", + tal_hexstr(trc, &h->ck, sizeof(h->ck)), + tal_hexstr(trc, &h->temp_k, sizeof(h->temp_k))); + + /* BOLT #8: + * + * * `p = decryptWithAD(temp_k1, 0, h, c)` + * * If the MAC check in this operation fails, then the initiator + * does _not_ know our static public key. If so, then the + * responder MUST terminate the connection without any further + * messages. + */ + if (!decrypt(&h->temp_k, 0, &h->h, sizeof(h->h), + act1.tag, sizeof(act1.tag), NULL, 0)) + status_failed(WIRE_RESPR_ACT1_BAD_TAG, "re=%s ss=%s tag=%s", + type_to_string(trc, struct pubkey, re), + tal_hexstr(trc, &h->ss, sizeof(h->ss)), + tal_hexstr(trc, act1.tag, sizeof(act1.tag))); + + /* BOLT #8: + * + * * `h = SHA-256(h || c)` + * * Mix the received ciphertext into the handshake digest. This + * step serves to ensure the payload wasn't modified by a MiTM. + */ + sha_mix_in(&h->h, act1.tag, sizeof(act1.tag)); + status_trace("# h=0x%s", tal_hexstr(trc, &h->h, sizeof(h->h))); +} + +/* BOLT #8: + * + * `Act Two` is sent from the responder to the initiator. `Act Two` will + * _only_ take place if `Act One` was successful. `Act One` was successful if + * the responder was able to properly decrypt and check the `MAC` of the tag + * sent at the end of `Act One`. + */ +struct act_two { + u8 v; + u8 pubkey[PUBKEY_DER_LEN]; + u8 tag[crypto_aead_chacha20poly1305_ietf_ABYTES]; +}; + +/* BOLT #8: The handshake is _exactly_ `50 bytes:` */ +#define ACT_TWO_SIZE 50 /* ARM's stupid ABI adds padding. */ + +static inline void check_act_two(const struct act_two *act2) +{ + /* BOLT #8: + * `1 byte` for the handshake version, + * `33 bytes` for the compressed ephemeral public key of the initiator, and + * `16 bytes` for the `poly1305` tag. + */ + BUILD_ASSERT(sizeof(act2->v) == 1); + BUILD_ASSERT(sizeof(act2->pubkey) == 33); + BUILD_ASSERT(sizeof(act2->tag) == 16); +} + +static void act_two_responder(struct handshake *h, int fd, + const struct pubkey *re) +{ + struct act_two act2; + size_t len; + + status_send(towire_respr_act_two(h)); + + /* BOLT #8: + * + * **Sender Actions:** + * + * * `e = generateKey()` + */ + h->e = generate_key(); + status_trace("# e.pub=0x%s e.priv=0x%s", + type_to_string(trc, struct pubkey, &h->e.pub), + tal_hexstr(trc, &h->e.priv, sizeof(h->e.priv))); + + /* BOLT #8: + * + * * `h = SHA-256(h || e.pub.serializeCompressed())` + * * The newly generated ephemeral key is accumulated into our + * running handshake digest. + */ + sha_mix_in_key(&h->h, &h->e.pub); + status_trace("# h=0x%s", tal_hexstr(trc, &h->h, sizeof(h->h))); + + /* BOLT #8: + * + * * `ss = ECDH(re, e.priv)` + * * where `re` is the ephemeral key of the initiator which was + * received during `ActOne`. + */ + if (!secp256k1_ecdh(secp256k1_ctx, h->ss.s.u.u8, &re->pubkey, + h->e.priv.secret)) + status_failed(WIRE_RESPR_ACT2_BAD_ECDH_FOR_SS, "re=%s e.priv=%s", + type_to_string(trc, struct pubkey, re), + tal_hexstr(trc, &h->e.priv, sizeof(h->e.priv))); + status_trace("# ss=0x%s", tal_hexstr(trc, &h->ss.s, sizeof(h->ss.s))); + + /* BOLT #8: + * + * * `ck, temp_k2 = HKDF(ck, ss)` + * * This phase generates a new temporary encryption key + * which is used to generate the authenticating MAC. + */ + hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss)); + status_trace("# ck,temp_k2=0x%s,0x%s", + tal_hexstr(trc, &h->ck, sizeof(h->ck)), + tal_hexstr(trc, &h->temp_k, sizeof(h->temp_k))); + + /* BOLT #8: + * + * * `c = encryptWithAD(temp_k2, 0, h, zero)` + * * where `zero` is a zero-length plaintext + */ + encrypt_ad(&h->temp_k, 0, &h->h, sizeof(h->h), NULL, 0, + act2.tag, sizeof(act2.tag)); + status_trace("# c=0x%s", tal_hexstr(trc, act2.tag, sizeof(act2.tag))); + + /* BOLT #8: + * + * * `h = SHA-256(h || c)` + * * Finally, the generated ciphertext is accumulated into the + * authenticating handshake digest. + */ + sha_mix_in(&h->h, act2.tag, sizeof(act2.tag)); + status_trace("# h=0x%s", tal_hexstr(trc, &h->h, sizeof(h->h))); + + /* BOLT #8: + * + * * Send `m = 0 || e.pub.serializeCompressed() || c` to the initiator over the network buffer. + */ + act2.v = 0; + len = sizeof(act2.pubkey); + secp256k1_ec_pubkey_serialize(secp256k1_ctx, act2.pubkey, &len, + &h->e.pub.pubkey, + SECP256K1_EC_COMPRESSED); + status_trace("output: 0x%s", tal_hexstr(trc, &act2, ACT_TWO_SIZE)); + if (!write_all(fd, &act2, ACT_TWO_SIZE)) + status_failed(WIRE_RESPR_ACT2_WRITE_FAILED, + "%s", strerror(errno)); +} + +static void act_two_initiator(struct handshake *h, int fd, struct pubkey *re) +{ + struct act_two act2; + + status_send(towire_initr_act_two(h)); + + /* BOLT #8: + * + * * Read _exactly_ `50-bytes` from the network buffer. + * + * * Parse out the read message (`m`) into `v = m[0]`, `re = m[1:34]` + * and `c = m[43:]` + * * where `m[0]` is the _first_ byte of `m`, `m[1:33]` are the + * next `33` bytes of `m` and `m[34:]` is the last 16 bytes of + * `m` + */ + if (!read_all(fd, &act2, ACT_TWO_SIZE)) + status_failed(WIRE_INITR_ACT2_READ_FAILED, + "%s", strerror(errno)); + status_trace("input: 0x%s", tal_hexstr(trc, &act2, ACT_TWO_SIZE)); + + /* BOLT #8: + * + * * If `v` is an unrecognized handshake version, then the responder + * MUST abort the connection attempt. + */ + if (act2.v != 0) + status_failed(WIRE_INITR_ACT2_BAD_VERSION, "%u", act2.v); + + /* BOLT #8: + * + * * The raw bytes of the remote party's ephemeral public key + * (`re`) are to be deserialized into a point on the curve using + * affine coordinates as encoded by the key's serialized + * composed format. + */ + if (secp256k1_ec_pubkey_parse(secp256k1_ctx, &re->pubkey, + act2.pubkey, sizeof(act2.pubkey)) != 1) + status_failed(WIRE_INITR_ACT2_BAD_PUBKEY, "%s", + tal_hexstr(trc, &act2.pubkey, + sizeof(act2.pubkey))); + status_trace("# re=0x%s", type_to_string(trc, struct pubkey, re)); + + /* BOLT #8: + * + * * `h = SHA-256(h || re.serializeCompressed())` + */ + sha_mix_in_key(&h->h, re); + status_trace("# h=0x%s", tal_hexstr(trc, &h->h, sizeof(h->h))); + + /* BOLT #8: + * + * * `ss = ECDH(re, e.priv)` + */ + if (!secp256k1_ecdh(secp256k1_ctx, h->ss.s.u.u8, &re->pubkey, + h->e.priv.secret)) + status_failed(WIRE_INITR_ACT2_BAD_ECDH_FOR_SS, "re=%s e.priv=%s", + type_to_string(trc, struct pubkey, re), + tal_hexstr(trc, &h->e.priv, sizeof(h->e.priv))); + status_trace("# ss=0x%s", tal_hexstr(trc, &h->ss, sizeof(h->ss))); + + /* BOLT #8: + * + * * `ck, temp_k2 = HKDF(ck, ss)` + * * This phase generates a new temporary encryption key + * which is used to generate the authenticating MAC. + */ + hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss)); + status_trace("# ck,temp_k2=0x%s,0x%s", + tal_hexstr(trc, &h->ck, sizeof(h->ck)), + tal_hexstr(trc, &h->temp_k, sizeof(h->temp_k))); + + /* BOLT #8: + * + * * `p = decryptWithAD(temp_k2, 0, h, c)` + * * If the MAC check in this operation fails, then the initiator + * MUST terminate the connection without any further messages. + */ + if (!decrypt(&h->temp_k, 0, &h->h, sizeof(h->h), + act2.tag, sizeof(act2.tag), NULL, 0)) + status_failed(WIRE_INITR_ACT2_BAD_TAG, "c=%s", + tal_hexstr(trc, act2.tag, sizeof(act2.tag))); + + /* BOLT #8: + * + * * `h = SHA-256(h || c)` + * * Mix the received ciphertext into the handshake digest. This + * step serves to ensure the payload wasn't modified by a MiTM. + */ + sha_mix_in(&h->h, act2.tag, sizeof(act2.tag)); + status_trace("# h=0x%s", tal_hexstr(trc, &h->h, sizeof(h->h))); +} + +/* BOLT #8: + * + * `Act Three` is the final phase in the authenticated key agreement described + * in this section. This act is sent from the initiator to the responder as a + * final concluding step. `Act Three` is only executed `iff` `Act Two` was + * successful. During `Act Three`, the initiator transports its static public + * key to the responder encrypted with _strong_ forward secrecy using the + * accumulated `HKDF` derived secret key at this point of the handshake. + */ +struct act_three { + u8 v; + u8 ciphertext[PUBKEY_DER_LEN + crypto_aead_chacha20poly1305_ietf_ABYTES]; + u8 tag[crypto_aead_chacha20poly1305_ietf_ABYTES]; +}; + +/* BOLT #8: The handshake is _exactly_ `66 bytes` */ +#define ACT_THREE_SIZE 66 /* ARM's stupid ABI adds padding. */ + +static inline void check_act_three(const struct act_three *act3) +{ + /* BOLT #8: + * + * `1 byte` for the handshake version, `33 bytes` for the ephemeral + * public key encrypted with the `ChaCha20` stream cipher, `16 bytes` + * for the encrypted public key's tag generated via the `AEAD` + * construction, and `16 bytes` for a final authenticating tag. + */ + BUILD_ASSERT(sizeof(act3->v) == 1); + BUILD_ASSERT(sizeof(act3->ciphertext) == 33 + 16); + BUILD_ASSERT(sizeof(act3->tag) == 16); +} + +static void act_three_initiator(struct handshake *h, int fd, + const struct pubkey *re, + const struct pubkey *my_id) +{ + struct act_three act3; + u8 spub[PUBKEY_DER_LEN]; + size_t len = sizeof(spub); + + status_send(towire_initr_act_three(h)); + + /* BOLT #8: + * * `c = encryptWithAD(temp_k2, 1, h, s.pub.serializeCompressed())` + * * where `s` is the static public key of the initiator. + */ + secp256k1_ec_pubkey_serialize(secp256k1_ctx, spub, &len, + &my_id->pubkey, + SECP256K1_EC_COMPRESSED); + encrypt_ad(&h->temp_k, 1, &h->h, sizeof(h->h), spub, sizeof(spub), + act3.ciphertext, sizeof(act3.ciphertext)); + status_trace("# c=0x%s", + tal_hexstr(trc,act3.ciphertext,sizeof(act3.ciphertext))); + + /* BOLT #8: + * * `h = SHA-256(h || c)` + */ + sha_mix_in(&h->h, act3.ciphertext, sizeof(act3.ciphertext)); + status_trace("# h=0x%s", tal_hexstr(trc, &h->h, sizeof(h->h))); + + /* BOLT #8: + * + * * `ss = ECDH(re, s.priv)` + * * where `re` is the ephemeral public key of the responder. + * + */ + if (!hsm_do_ecdh(&h->ss.s, re)) + status_failed(WIRE_INITR_ACT3_BAD_HSM_ECDH, + "re=%s", + type_to_string(trc, struct pubkey, re)); + status_trace("# ss=0x%s", tal_hexstr(trc, &h->ss, sizeof(h->ss))); + + /* BOLT #8: + * + * * `ck, temp_k3 = HKDF(ck, ss)` + * * Mix the final intermediate shared secret into the running chaining key. + */ + hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss)); + status_trace("# ck,temp_k3=0x%s,0x%s", + tal_hexstr(trc, &h->ck, sizeof(h->ck)), + tal_hexstr(trc, &h->temp_k, sizeof(h->temp_k))); + + /* BOLT #8: + * + * * `t = encryptWithAD(temp_k3, 0, h, zero)` + * * where `zero` is a zero-length plaintext + * + */ + encrypt_ad(&h->temp_k, 0, &h->h, sizeof(h->h), NULL, 0, + act3.tag, sizeof(act3.tag)); + status_trace("# t=0x%s", + tal_hexstr(trc, act3.tag, sizeof(act3.tag))); + + /* BOLT #8: + * + * * Send `m = 0 || c || t` over the network buffer. + * + */ + act3.v = 0; + + status_trace("output: 0x%s", tal_hexstr(trc, &act3, ACT_THREE_SIZE)); + if (!write_all(fd, &act3, ACT_THREE_SIZE)) + status_failed(WIRE_INITR_ACT3_WRITE_FAILED, + "%s", strerror(errno)); +} + +static void act_three_responder(struct handshake *h, int fd, + struct pubkey *their_id) +{ + struct act_three act3; + u8 der[PUBKEY_DER_LEN]; + + status_send(towire_respr_act_three(h)); + + /* BOLT #8: + * + * **Receiver Actions:** + * + * * Read _exactly_ `66-bytes` from the network buffer. + */ + if (!read_all(fd, &act3, ACT_THREE_SIZE)) + status_failed(WIRE_RESPR_ACT3_READ_FAILED, + "%s", strerror(errno)); + status_trace("input: 0x%s", tal_hexstr(trc, &act3, ACT_THREE_SIZE)); + + /* BOLT #8: + * + * * Parse out the read message (`m`) into `v = m[0]`, `c = m[1:50]` and `t = m[50:]` + */ + + /* BOLT #8: + * + * * If `v` is an unrecognized handshake version, then the responder MUST + * abort the connection attempt. + */ + if (act3.v != 0) + status_failed(WIRE_RESPR_ACT3_BAD_VERSION, "%u", act3.v); + + /* BOLT #8: + * + * * `rs = decryptWithAD(temp_k2, 1, h, c)` + * * At this point, the responder has recovered the static public key of the + * initiator. + */ + if (!decrypt(&h->temp_k, 1, &h->h, sizeof(h->h), + act3.ciphertext, sizeof(act3.ciphertext), + der, sizeof(der))) + status_failed(WIRE_RESPR_ACT3_BAD_CIPHERTEXT, + "ciphertext=%s", + tal_hexstr(trc, act3.ciphertext, + sizeof(act3.ciphertext))); + status_trace("# rs=0x%s", tal_hexstr(trc, der, sizeof(der))); + + if (secp256k1_ec_pubkey_parse(secp256k1_ctx, &their_id->pubkey, + der, sizeof(der)) != 1) + status_failed(WIRE_RESPR_ACT3_BAD_PUBKEY, "%s", + tal_hexstr(trc, &der, sizeof(der))); + + /* BOLT #8: + * + * * `h = SHA-256(h || c)` + * + */ + sha_mix_in(&h->h, act3.ciphertext, sizeof(act3.ciphertext)); + status_trace("# h=0x%s", tal_hexstr(trc, &h->h, sizeof(h->h))); + + /* BOLT #8: + * + * * `ss = ECDH(rs, e.priv)` + * * where `e` is the responder's original ephemeral key + */ + if (!secp256k1_ecdh(secp256k1_ctx, h->ss.s.u.u8, &their_id->pubkey, + h->e.priv.secret)) + status_failed(WIRE_RESPR_ACT3_BAD_ECDH_FOR_SS, "rs=%s e.priv=%s", + type_to_string(trc, struct pubkey, their_id), + tal_hexstr(trc, &h->e.priv, sizeof(h->e.priv))); + status_trace("# ss=0x%s", tal_hexstr(trc, &h->ss, sizeof(h->ss))); + + /* BOLT #8: + * * `ck, temp_k3 = HKDF(ck, ss)` + */ + hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss)); + status_trace("# ck,temp_k3=0x%s,0x%s", + tal_hexstr(trc, &h->ck, sizeof(h->ck)), + tal_hexstr(trc, &h->temp_k, sizeof(h->temp_k))); + + /* BOLT #8: + * * `p = decryptWithAD(temp_k3, 0, h, t)` + * * If the MAC check in this operation fails, then the responder MUST + * terminate the connection without any further messages. + * + */ + if (!decrypt(&h->temp_k, 0, &h->h, sizeof(h->h), + act3.tag, sizeof(act3.tag), NULL, 0)) + status_failed(WIRE_RESPR_ACT3_BAD_TAG, "temp_k3=%s h=%s t=%s", + tal_hexstr(trc, &h->temp_k, sizeof(h->temp_k)), + tal_hexstr(trc, &h->h, sizeof(h->h)), + tal_hexstr(trc, act3.tag, sizeof(act3.tag))); +} + +static void initiator(int fd, const struct pubkey *my_id, + const struct pubkey *their_id, + struct secret *ck, struct secret *sk, struct secret *rk) +{ + const tal_t *tmpctx = tal_tmpctx(NULL); + struct handshake *h = new_handshake(tmpctx, their_id); + struct pubkey re; + + act_one_initiator(h, fd, their_id); + act_two_initiator(h, fd, &re); + act_three_initiator(h, fd, &re, my_id); + + /* We need this for re-keying */ + *ck = h->ck; + + /* BOLT #8: + * + * * `sk, rk = HKDF(ck, zero)` + * + * * where `zero` is a zero-length plaintext, `sk` is the key to + * be used by the initiator to encrypt messages to the + * responder, and `rk` is the key to be used by the initiator + * to decrypt messages sent by the responder. + * + * * This step generates the final encryption keys to be used for + * sending and receiving messages for the duration of the + * session. + */ + hkdf_two_keys(sk, rk, ck, NULL, 0); + status_trace("output: sk,rk=0x%s,0x%s", + tal_hexstr(trc, sk, sizeof(*sk)), + tal_hexstr(trc, rk, sizeof(*rk))); + tal_free(tmpctx); +} + +static void responder(int fd, + const struct pubkey *my_id, + struct pubkey *their_id, + struct secret *ck, struct secret *sk, struct secret *rk) +{ + const tal_t *tmpctx = tal_tmpctx(NULL); + struct handshake *h = new_handshake(tmpctx, my_id); + struct pubkey re; + + act_one_responder(h, fd, &re); + act_two_responder(h, fd, &re); + act_three_responder(h, fd, their_id); + + /* We need this for re-keying */ + *ck = h->ck; + + /* BOLT #8: + * + * * `rk, sk = HKDF(ck, zero)` + * * where `zero` is a zero-length plaintext, `rk` is the key to + * be used by the responder to decrypt the messages sent by the + * initiator, and `sk` is the key to be used by the responder + * to encrypt messages to the initiator, + * + * * This step generates the final encryption keys to be used for + * sending and receiving messages for the duration of the + * session. + */ + hkdf_two_keys(rk, sk, ck, NULL, 0); + status_trace("output: rk,sk=0x%s,0x%s", + tal_hexstr(trc, rk, sizeof(*rk)), + tal_hexstr(trc, sk, sizeof(*sk))); + tal_free(tmpctx); +} + +#ifndef TESTING +/* We expect hsmfd as fd 4, then a request then the clientfd */ +int main(int argc, char *argv[]) +{ + u8 *msg; + struct pubkey my_id, their_id; + int hsmfd = 4, clientfd; + struct secret ck, rk, sk; + struct crypto_state *cs; + + if (argc == 2 && streq(argv[1], "--version")) { + printf("%s\n", version()); + exit(0); + } + + breakpoint(); + secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY + | SECP256K1_CONTEXT_SIGN); + status_setup(STATUS_FD); + + hsm_setup(hsmfd); + + msg = wire_sync_read(NULL, REQ_FD); + if (!msg) + status_failed(WIRE_BAD_COMMAND, "%s", strerror(errno)); + clientfd = fdpass_recv(REQ_FD); + if (clientfd < 0) + status_failed(WIRE_BAD_FDPASS, "%s", strerror(errno)); + + if (fromwire_handshake_responder_req(msg, NULL, &my_id)) { + responder(clientfd, &my_id, &their_id, &ck, &sk, &rk); + cs = crypto_state(NULL, &sk.s, &rk.s, &ck.s, &ck.s, 0, 0); + wire_sync_write(REQ_FD, + towire_handshake_responder_resp(msg, + &their_id, + cs)); + } else if (fromwire_handshake_initiator_req(msg, NULL, &my_id, + &their_id)) { + initiator(clientfd, &my_id, &their_id, &ck, &sk, &rk); + cs = crypto_state(NULL, &sk.s, &rk.s, &ck.s, &ck.s, 0, 0); + wire_sync_write(REQ_FD, + towire_handshake_initiator_resp(msg, cs)); + } else + status_failed(WIRE_BAD_COMMAND, "%i", fromwire_peektype(msg)); + + /* Hand back the fd. */ + fdpass_send(REQ_FD, clientfd); + + /* Wait for exit command (avoid status close being read before reqfd) */ + msg = wire_sync_read(msg, REQ_FD); + if (!msg) + status_failed(WIRE_BAD_COMMAND, "%s", strerror(errno)); + if (!fromwire_handshake_exit_req(msg, NULL)) + status_failed(WIRE_BAD_COMMAND, "Expected exit req not %i", + fromwire_peektype(msg)); + tal_free(msg); + return 0; +} +#endif /* TESTING */ diff --git a/lightningd/handshake/handshake_control_wire_csv b/lightningd/handshake/handshake_control_wire_csv new file mode 100644 index 000000000..6092005f8 --- /dev/null +++ b/lightningd/handshake/handshake_control_wire_csv @@ -0,0 +1,12 @@ +#include +handshake_responder_req,0 +handshake_responder_req,0,my_id,33 +handshake_responder_resp,1 +handshake_responder_resp,0,initiator_id,33 +handshake_responder_resp,33,cs,144,struct crypto_state +handshake_initiator_req,2 +handshake_initiator_req,0,my_id,33 +handshake_initiator_req,33,responder_id,33 +handshake_initiator_resp,3 +handshake_initiator_resp,0,cs,144,struct crypto_state +handshake_exit_req,4 diff --git a/lightningd/handshake/handshake_status_wire_csv b/lightningd/handshake/handshake_status_wire_csv new file mode 100644 index 000000000..606d1b53b --- /dev/null +++ b/lightningd/handshake/handshake_status_wire_csv @@ -0,0 +1,31 @@ +bad_command,0x8000 +bad_fdpass,0x8001 +initr_act1_bad_ecdh_for_ss,0x8011 +initr_act1_write_failed,0x8012 +initr_act2_read_failed,0x8013 +initr_act2_bad_version,0x8014 +initr_act2_bad_pubkey,0x8015 +initr_act2_bad_ecdh_for_ss,0x8016 +initr_act2_bad_tag,0x8017 +initr_act3_bad_hsm_ecdh,0x8018 +initr_act3_write_failed,0x8019 +respr_act1_read_failed,0x801A +respr_act1_bad_version,0x801B +respr_act1_bad_pubkey,0x801C +respr_act1_bad_hsm_ecdh,0x801D +respr_act1_bad_tag,0x801E +respr_act2_bad_ecdh_for_ss,0x801F +respr_act2_write_failed,0x8020 +respr_act3_read_failed,0x8021 +respr_act3_bad_version,0x8022 +respr_act3_bad_ciphertext,0x8023 +respr_act3_bad_pubkey,0x8024 +respr_act3_bad_ecdh_for_ss,0x8025 +respr_act3_bad_tag,0x8026 +initr_act_one,1 +initr_act_two,2 +initr_act_three,3 +respr_act_one,4 +respr_act_two,5 +respr_act_three,6 +success,0 diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index c2a4acf3e..a323a05b5 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -96,7 +96,8 @@ static struct lightningd *new_lightningd(const tal_t *ctx) static const char *daemons[] = { "lightningd", - "lightningd_hsm" + "lightningd_hsm", + "lightningd_handshake" }; /* Check we can run them, and check their versions */