mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 05:12:45 +01:00
lightningd/cryptomsg: routines to encrypt on the wire.
After the handshake, it's a simple matter of AEAD and key rotation. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
faebb87d01
commit
a7f682c66e
@ -8,7 +8,7 @@ lightningd-all: lightningd/lightningd
|
|||||||
|
|
||||||
default: lightningd-all
|
default: lightningd-all
|
||||||
|
|
||||||
LIGHTNINGD_LIB_SRC := \
|
LIGHTNINGD_OLD_SRC := \
|
||||||
daemon/configdir.c \
|
daemon/configdir.c \
|
||||||
daemon/json.c \
|
daemon/json.c \
|
||||||
daemon/log.c \
|
daemon/log.c \
|
||||||
@ -17,8 +17,14 @@ LIGHTNINGD_LIB_SRC := \
|
|||||||
daemon/pseudorand.c \
|
daemon/pseudorand.c \
|
||||||
daemon/routing.c \
|
daemon/routing.c \
|
||||||
daemon/watch.c
|
daemon/watch.c
|
||||||
|
LIGHTNINGD_OLD_OBJS := $(LIGHTNINGD_OLD_SRC:.c=.o)
|
||||||
|
LIGHTNINGD_OLD_HEADERS := $(LIGHTNINGD_OLD_SRC:.c=.h)
|
||||||
|
|
||||||
|
LIGHTNINGD_LIB_SRC := \
|
||||||
|
lightningd/cryptomsg.c
|
||||||
|
|
||||||
LIGHTNINGD_LIB_OBJS := $(LIGHTNINGD_LIB_SRC:.c=.o)
|
LIGHTNINGD_LIB_OBJS := $(LIGHTNINGD_LIB_SRC:.c=.o)
|
||||||
|
LIGHTNINGD_LIB_HEADERS := $(LIGHTNINGD_LIB_SRC:.c=.h)
|
||||||
|
|
||||||
LIGHTNINGD_SRC := \
|
LIGHTNINGD_SRC := \
|
||||||
lightningd/lightningd.c \
|
lightningd/lightningd.c \
|
||||||
@ -32,7 +38,7 @@ LIGHTNINGD_JSMN_HEADERS := daemon/jsmn/jsmn.h
|
|||||||
LIGHTNINGD_HEADERS := lightningd/lightningd.h \
|
LIGHTNINGD_HEADERS := lightningd/lightningd.h \
|
||||||
lightningd/subdaemon.h
|
lightningd/subdaemon.h
|
||||||
|
|
||||||
$(LIGHTNINGD_OBJS): $(LIGHTNINGD_HEADERS) $(LIGHTNINGD_JSMN_HEADERS) $(BITCOIN_HEADERS) $(CORE_HEADERS) $(GEN_HEADERS) $(CCAN_HEADERS) $(DAEMON_HEADERS) $(LIBBASE58_HEADERS)
|
$(LIGHTNINGD_OBJS) $(LIGHTNINGD_LIB_OBJS): $(LIGHTNINGD_HEADERS) $(LIGHTNINGD_JSMN_HEADERS) $(BITCOIN_HEADERS) $(CORE_HEADERS) $(GEN_HEADERS) $(CCAN_HEADERS) $(DAEMON_HEADERS) $(LIBBASE58_HEADERS)
|
||||||
|
|
||||||
check-source: $(LIGHTNINGD_SRC:%=check-src-include-order/%)
|
check-source: $(LIGHTNINGD_SRC:%=check-src-include-order/%)
|
||||||
check-source: $(LIGHTNINGD_LIB_SRC:%=check-src-include-order/%)
|
check-source: $(LIGHTNINGD_LIB_SRC:%=check-src-include-order/%)
|
||||||
@ -40,12 +46,12 @@ check-source: $(LIGHTNINGD_CLI_SRC:%=check-src-include-order/%)
|
|||||||
check-source: $(LIGHTNINGD_HEADERS:%=check-hdr-include-order/%)
|
check-source: $(LIGHTNINGD_HEADERS:%=check-hdr-include-order/%)
|
||||||
check-source-bolt: $(LIGHTNINGD_SRC:%=bolt-check/%) $(LIGHTNINGD_HEADERS:%=bolt-check/%)
|
check-source-bolt: $(LIGHTNINGD_SRC:%=bolt-check/%) $(LIGHTNINGD_HEADERS:%=bolt-check/%)
|
||||||
|
|
||||||
check-whitespace: $(LIGHTNINGD_SRC:%=check-whitespace/%) $(LIGHTNINGD_HEADERS:%=check-whitespace/%)
|
check-whitespace: $(LIGHTNINGD_SRC:%=check-whitespace/%) $(LIGHTNINGD_HEADERS:%=check-whitespace/%) $(LIGHTNINGD_LIB_SRC:%=check-whitespace/%) $(LIGHTNINGD_LIB_HEADERS:%=check-whitespace/%)
|
||||||
|
|
||||||
check-lightningd-makefile:
|
check-lightningd-makefile:
|
||||||
@if [ "`ls lightningd/*.h | grep -v lightningd/gen | tr '\012' ' '`" != "`echo $(LIGHTNINGD_HEADERS) ''`" ]; then echo LIGHTNINGD_HEADERS incorrect; exit 1; fi
|
@if [ "`ls lightningd/*.h | grep -v lightningd/gen | tr '\012' ' '`" != "`echo $(LIGHTNINGD_HEADERS) ''`" ]; then echo LIGHTNINGD_HEADERS incorrect; exit 1; fi
|
||||||
|
|
||||||
lightningd/lightningd: $(LIGHTNINGD_OBJS) $(LIGHTNINGD_LIB_OBJS) $(LIGHTNINGD_JSMN_OBJS) $(CORE_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(CCAN_OBJS) $(LIBBASE58_OBJS) libsecp256k1.a
|
lightningd/lightningd: $(LIGHTNINGD_OBJS) $(LIGHTNINGD_OLD_OBJS) $(LIGHTNINGD_LIB_OBJS) $(LIGHTNINGD_JSMN_OBJS) $(CORE_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(CCAN_OBJS) $(LIBBASE58_OBJS) libsecp256k1.a
|
||||||
|
|
||||||
clean: lightningd-clean
|
clean: lightningd-clean
|
||||||
|
|
||||||
|
308
lightningd/cryptomsg.c
Normal file
308
lightningd/cryptomsg.c
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <ccan/build_assert/build_assert.h>
|
||||||
|
#include <ccan/crypto/hkdf_sha256/hkdf_sha256.h>
|
||||||
|
#include <ccan/crypto/sha256/sha256.h>
|
||||||
|
#include <ccan/endian/endian.h>
|
||||||
|
#include <ccan/mem/mem.h>
|
||||||
|
#include <ccan/short_types/short_types.h>
|
||||||
|
#include <lightningd/cryptomsg.h>
|
||||||
|
#include <sodium/crypto_aead_chacha20poly1305.h>
|
||||||
|
#include <status.h>
|
||||||
|
#include <utils.h>
|
||||||
|
#include <wire/wire_io.h>
|
||||||
|
|
||||||
|
struct crypto_state {
|
||||||
|
/* Received and sent nonces. */
|
||||||
|
u64 rn, sn;
|
||||||
|
/* Sending and receiving keys. */
|
||||||
|
struct sha256 sk, rk;
|
||||||
|
/* Chaining key for re-keying */
|
||||||
|
struct sha256 s_ck, r_ck;
|
||||||
|
|
||||||
|
/* Peer who owns us: peer->crypto_state == this */
|
||||||
|
struct peer *peer;
|
||||||
|
|
||||||
|
/* Output and input buffers. */
|
||||||
|
u8 *out, *in;
|
||||||
|
struct io_plan *(*next_in)(struct io_conn *, struct peer *, u8 *);
|
||||||
|
struct io_plan *(*next_out)(struct io_conn *, struct peer *);
|
||||||
|
};
|
||||||
|
|
||||||
|
static void hkdf_two_keys(struct sha256 *out1, struct sha256 *out2,
|
||||||
|
const struct sha256 *in1,
|
||||||
|
const struct sha256 *in2)
|
||||||
|
{
|
||||||
|
/* 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 sha256 okm[2];
|
||||||
|
|
||||||
|
BUILD_ASSERT(sizeof(okm) == 64);
|
||||||
|
hkdf_sha256(okm, sizeof(okm), in1, sizeof(*in1), in2, sizeof(*in2),
|
||||||
|
NULL, 0);
|
||||||
|
*out1 = okm[0];
|
||||||
|
*out2 = okm[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void maybe_rotate_key(u64 *n, struct sha256 *k, struct sha256 *ck)
|
||||||
|
{
|
||||||
|
struct sha256 new_k, new_ck;
|
||||||
|
|
||||||
|
/* BOLT #8:
|
||||||
|
*
|
||||||
|
* A key is to be rotated after a party sends of decrypts
|
||||||
|
* `1000` messages with it. This can be properly accounted
|
||||||
|
* for by rotating the key once the nonce dedicated to it
|
||||||
|
* exceeds `1000`.
|
||||||
|
*/
|
||||||
|
if (*n != 1000)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* BOLT #8:
|
||||||
|
*
|
||||||
|
* Key rotation for a key `k` is performed according to the following:
|
||||||
|
*
|
||||||
|
* * Let `ck` be the chaining key obtained at the end of `Act Three`.
|
||||||
|
* * `ck', k' = HKDF(ck, k)`
|
||||||
|
* * Reset the nonce for the key to `n = 0`.
|
||||||
|
* * `k = k'`
|
||||||
|
* * `ck = ck'`
|
||||||
|
*/
|
||||||
|
hkdf_two_keys(&new_ck, &new_k, ck, k);
|
||||||
|
status_trace("# 0x%s, 0x%s = HKDF(0x%s, 0x%s)",
|
||||||
|
tal_hexstr(trc, &new_ck, sizeof(new_ck)),
|
||||||
|
tal_hexstr(trc, &new_k, sizeof(new_k)),
|
||||||
|
tal_hexstr(trc, ck, sizeof(*ck)),
|
||||||
|
tal_hexstr(trc, k, sizeof(*k)));
|
||||||
|
*ck = new_ck;
|
||||||
|
*k = new_k;
|
||||||
|
*n = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct io_plan *peer_decrypt_body(struct io_conn *conn,
|
||||||
|
struct crypto_state *cs)
|
||||||
|
{
|
||||||
|
unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
|
||||||
|
unsigned long long mlen;
|
||||||
|
u8 *decrypted = tal_arr(cs->in, u8, tal_count(cs->in) - 16), *in;
|
||||||
|
struct io_plan *plan;
|
||||||
|
|
||||||
|
le64_nonce(npub, cs->rn++);
|
||||||
|
|
||||||
|
/* BOLT #8:
|
||||||
|
*
|
||||||
|
* * Decrypt `c` using `ChaCha20-Poly1305`, `rn`, and `rk` to
|
||||||
|
* obtain decrypted plaintext packet `p`.
|
||||||
|
*
|
||||||
|
* * The nonce `rn` MUST be incremented after this step.
|
||||||
|
*/
|
||||||
|
if (crypto_aead_chacha20poly1305_ietf_decrypt(decrypted,
|
||||||
|
&mlen, NULL,
|
||||||
|
memcheck(cs->in,
|
||||||
|
tal_count(cs->in)),
|
||||||
|
tal_count(cs->in),
|
||||||
|
NULL, 0,
|
||||||
|
npub, cs->rk.u.u8) != 0) {
|
||||||
|
/* FIXME: Report error! */
|
||||||
|
return io_close(conn);
|
||||||
|
}
|
||||||
|
assert(mlen == tal_count(decrypted));
|
||||||
|
|
||||||
|
maybe_rotate_key(&cs->rn, &cs->rk, &cs->r_ck);
|
||||||
|
|
||||||
|
/* Steal cs->in: we free it after, and decrypted too unless
|
||||||
|
* they steal but be careful not to touch anything after
|
||||||
|
* next_in (could free itself) */
|
||||||
|
in = tal_steal(NULL, cs->in);
|
||||||
|
cs->in = NULL;
|
||||||
|
|
||||||
|
plan = cs->next_in(conn, cs->peer, decrypted);
|
||||||
|
tal_free(in);
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct io_plan *peer_decrypt_header(struct io_conn *conn,
|
||||||
|
struct crypto_state *cs)
|
||||||
|
{
|
||||||
|
unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
|
||||||
|
unsigned long long mlen;
|
||||||
|
be16 len;
|
||||||
|
|
||||||
|
le64_nonce(npub, cs->rn++);
|
||||||
|
|
||||||
|
/* BOLT #8:
|
||||||
|
*
|
||||||
|
* * Let the encrypted length prefix be known as `lc`
|
||||||
|
*
|
||||||
|
* * Decrypt `lc` using `ChaCha20-Poy1305`, `rn`, and `rk` to
|
||||||
|
* obtain size of the encrypted packet `l`.
|
||||||
|
* * A zero-length byte slice is to be passed as the AD
|
||||||
|
* (associated data).
|
||||||
|
* * The nonce `rn` MUST be incremented after this step.
|
||||||
|
*/
|
||||||
|
if (crypto_aead_chacha20poly1305_ietf_decrypt((unsigned char *)&len,
|
||||||
|
&mlen, NULL,
|
||||||
|
memcheck(cs->in,
|
||||||
|
tal_count(cs->in)),
|
||||||
|
tal_count(cs->in),
|
||||||
|
NULL, 0,
|
||||||
|
npub, cs->rk.u.u8) != 0) {
|
||||||
|
/* FIXME: Report error! */
|
||||||
|
return io_close(conn);
|
||||||
|
}
|
||||||
|
assert(mlen == sizeof(len));
|
||||||
|
tal_free(cs->in);
|
||||||
|
|
||||||
|
/* BOLT #8:
|
||||||
|
*
|
||||||
|
* * Read _exactly_ `l+16` bytes from the network buffer, let
|
||||||
|
* the bytes be known as `c`.
|
||||||
|
*/
|
||||||
|
cs->in = tal_arr(cs, u8, (u32)be16_to_cpu(len) + 16);
|
||||||
|
return io_read(conn, cs->in, tal_count(cs->in), peer_decrypt_body, cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct io_plan *peer_read_message(struct io_conn *conn,
|
||||||
|
struct crypto_state *cs,
|
||||||
|
struct io_plan *(*next)(struct io_conn *,
|
||||||
|
struct peer *,
|
||||||
|
u8 *msg))
|
||||||
|
{
|
||||||
|
assert(!cs->in);
|
||||||
|
/* BOLT #8:
|
||||||
|
*
|
||||||
|
* ### Decrypting Messages
|
||||||
|
*
|
||||||
|
* In order to decrypt the _next_ message in the network
|
||||||
|
* stream, the following is done:
|
||||||
|
*
|
||||||
|
* * Read _exactly_ `18-bytes` from the network buffer.
|
||||||
|
*/
|
||||||
|
cs->in = tal_arr(cs, u8, 18);
|
||||||
|
cs->next_in = next;
|
||||||
|
return io_read(conn, cs->in, 18, peer_decrypt_header, cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct io_plan *peer_write_done(struct io_conn *conn,
|
||||||
|
struct crypto_state *cs)
|
||||||
|
{
|
||||||
|
cs->out = tal_free(cs->out);
|
||||||
|
return cs->next_out(conn, cs->peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct io_plan *peer_write_message(struct io_conn *conn,
|
||||||
|
struct crypto_state *cs,
|
||||||
|
const u8 *msg,
|
||||||
|
struct io_plan *(*next)(struct io_conn *,
|
||||||
|
struct peer *))
|
||||||
|
{
|
||||||
|
unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
|
||||||
|
unsigned long long clen, mlen = tal_count(msg);
|
||||||
|
be16 l;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
assert(!cs->out);
|
||||||
|
|
||||||
|
cs->out = tal_arr(cs, u8, sizeof(l) + 16 + mlen + 16);
|
||||||
|
cs->next_out = next;
|
||||||
|
|
||||||
|
/* BOLT #8:
|
||||||
|
*
|
||||||
|
* In order to encrypt a lightning message (`m`), given a
|
||||||
|
* sending key (`sk`), and a nonce (`sn`), the following is done:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* * let `l = len(m)`,
|
||||||
|
* where `len` obtains the length in bytes of the lightning message.
|
||||||
|
*
|
||||||
|
* * Serialize `l` into `2-bytes` encoded as a big-endian integer.
|
||||||
|
*/
|
||||||
|
l = cpu_to_be16(mlen);
|
||||||
|
|
||||||
|
/* BOLT #8:
|
||||||
|
*
|
||||||
|
* * Encrypt `l` using `ChaChaPoly-1305`, `sn`, and `sk` to obtain `lc`
|
||||||
|
* (`18-bytes`)
|
||||||
|
* * The nonce `sn` is encoded as a 96-bit big-endian number.
|
||||||
|
* * The nonce `sn` MUST be incremented after this step.
|
||||||
|
* * A zero-length byte slice is to be passed as the AD
|
||||||
|
*/
|
||||||
|
le64_nonce(npub, cs->sn++);
|
||||||
|
ret = crypto_aead_chacha20poly1305_ietf_encrypt(cs->out, &clen,
|
||||||
|
(unsigned char *)
|
||||||
|
memcheck(&l, sizeof(l)),
|
||||||
|
sizeof(l),
|
||||||
|
NULL, 0,
|
||||||
|
NULL, npub,
|
||||||
|
cs->sk.u.u8);
|
||||||
|
assert(ret == 0);
|
||||||
|
assert(clen == sizeof(l) + 16);
|
||||||
|
|
||||||
|
/* BOLT #8:
|
||||||
|
*
|
||||||
|
* * Finally encrypt the message itself (`m`) using the same
|
||||||
|
* procedure used to encrypt the length prefix. Let
|
||||||
|
* encrypted ciphertext be known as `c`.
|
||||||
|
*
|
||||||
|
* * The nonce `sn` MUST be incremented after this step.
|
||||||
|
*/
|
||||||
|
le64_nonce(npub, cs->sn++);
|
||||||
|
ret = crypto_aead_chacha20poly1305_ietf_encrypt(cs->out + clen, &clen,
|
||||||
|
memcheck(msg, mlen),
|
||||||
|
mlen,
|
||||||
|
NULL, 0,
|
||||||
|
NULL, npub,
|
||||||
|
cs->sk.u.u8);
|
||||||
|
assert(ret == 0);
|
||||||
|
assert(clen == mlen + 16);
|
||||||
|
|
||||||
|
maybe_rotate_key(&cs->sn, &cs->sk, &cs->s_ck);
|
||||||
|
|
||||||
|
/* BOLT #8:
|
||||||
|
* * Send `lc || c` over the network buffer.
|
||||||
|
*/
|
||||||
|
return io_write(conn, cs->out, tal_count(cs->out), peer_write_done, cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_state *crypto_state(struct peer *peer,
|
||||||
|
const struct sha256 *sk,
|
||||||
|
const struct sha256 *rk,
|
||||||
|
const struct sha256 *rck,
|
||||||
|
const struct sha256 *sck,
|
||||||
|
u64 rn, u64 sn)
|
||||||
|
{
|
||||||
|
struct crypto_state *cs = tal(peer, struct crypto_state);
|
||||||
|
|
||||||
|
cs->rn = rn;
|
||||||
|
cs->sn = sn;
|
||||||
|
cs->sk = *sk;
|
||||||
|
cs->rk = *rk;
|
||||||
|
cs->s_ck = *sck;
|
||||||
|
cs->r_ck = *rck;
|
||||||
|
cs->peer = peer;
|
||||||
|
cs->out = cs->in = NULL;
|
||||||
|
|
||||||
|
return cs;
|
||||||
|
}
|
32
lightningd/cryptomsg.h
Normal file
32
lightningd/cryptomsg.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef LIGHTNING_LIGHTNINGD_CRYPTOMSG_H
|
||||||
|
#define LIGHTNING_LIGHTNINGD_CRYPTOMSG_H
|
||||||
|
#include "config.h"
|
||||||
|
#include <ccan/short_types/short_types.h>
|
||||||
|
|
||||||
|
struct io_conn;
|
||||||
|
struct peer;
|
||||||
|
struct sha256;
|
||||||
|
|
||||||
|
/* Initializes peer->crypto_state */
|
||||||
|
struct crypto_state *crypto_state(struct peer *peer,
|
||||||
|
const struct sha256 *sk,
|
||||||
|
const struct sha256 *rk,
|
||||||
|
const struct sha256 *rck,
|
||||||
|
const struct sha256 *sck,
|
||||||
|
u64 rn, u64 sn);
|
||||||
|
|
||||||
|
/* Get decrypted message */
|
||||||
|
struct io_plan *peer_read_message(struct io_conn *conn,
|
||||||
|
struct crypto_state *cs,
|
||||||
|
struct io_plan *(*next)(struct io_conn *,
|
||||||
|
struct peer *,
|
||||||
|
u8 *msg));
|
||||||
|
|
||||||
|
/* Sends and frees message */
|
||||||
|
struct io_plan *peer_write_message(struct io_conn *conn,
|
||||||
|
struct crypto_state *cs,
|
||||||
|
const u8 *msg,
|
||||||
|
struct io_plan *(*next)(struct io_conn *,
|
||||||
|
struct peer *));
|
||||||
|
|
||||||
|
#endif /* LIGHTNING_LIGHTNINGD_CRYPTOMSG_H */
|
Loading…
Reference in New Issue
Block a user