From 159fc7d1a22bd02eeca286ed8abccbd1db04cb09 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 17 Oct 2022 11:14:39 +1030 Subject: [PATCH] common/onion_message_parse: generic routine for parsing onion messages. Instead of open coding in connectd/onion_message, we move it to common with a nice API. This lets us process the BOLT test vectors. Signed-off-by: Rusty Russell --- common/Makefile | 1 + common/onion_message_parse.c | 187 +++++++++++++++++++++++++++++++++++ common/onion_message_parse.h | 37 +++++++ connectd/Makefile | 1 + connectd/onion_message.c | 168 ++++--------------------------- 5 files changed, 244 insertions(+), 150 deletions(-) create mode 100644 common/onion_message_parse.c create mode 100644 common/onion_message_parse.h diff --git a/common/Makefile b/common/Makefile index 2e078dc39..53dc835c5 100644 --- a/common/Makefile +++ b/common/Makefile @@ -59,6 +59,7 @@ COMMON_SRC_NOGEN := \ common/node_id.c \ common/onion.c \ common/onionreply.c \ + common/onion_message_parse.c \ common/peer_billboard.c \ common/peer_failed.c \ common/peer_io.c \ diff --git a/common/onion_message_parse.c b/common/onion_message_parse.c new file mode 100644 index 000000000..311c33ef5 --- /dev/null +++ b/common/onion_message_parse.c @@ -0,0 +1,187 @@ +/* Caller does fromwire_onion_message(), this does the rest. */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool decrypt_final_onionmsg(const tal_t *ctx, + const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv, + const struct pubkey *my_id, + struct pubkey *alias, + struct secret **path_id) +{ + struct tlv_encrypted_data_tlv *encmsg; + + if (!blindedpath_get_alias(ss, my_id, alias)) + return false; + + encmsg = decrypt_encrypted_data(tmpctx, blinding, ss, enctlv); + if (!encmsg) + return false; + + if (tal_bytelen(encmsg->path_id) == sizeof(**path_id)) { + *path_id = tal(ctx, struct secret); + memcpy(*path_id, encmsg->path_id, sizeof(**path_id)); + } else + *path_id = NULL; + + return true; +} + +static bool decrypt_forwarding_onionmsg(const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv, + struct pubkey *next_node, + struct pubkey *next_blinding) +{ + struct tlv_encrypted_data_tlv *encmsg; + + encmsg = decrypt_encrypted_data(tmpctx, blinding, ss, enctlv); + if (!encmsg) + return false; + + /* BOLT-onion-message #4: + * + * The reader: + * - if it is not the final node according to the onion encryption: + *... + * - if the `enctlv` ... does not contain + * `next_node_id`: + * - MUST drop the message. + */ + if (!encmsg->next_node_id) + return false; + + /* BOLT-onion-message #4: + * The reader: + * - if it is not the final node according to the onion encryption: + *... + * - if the `enctlv` contains `path_id`: + * - MUST drop the message. + */ + if (encmsg->path_id) + return false; + + *next_node = *encmsg->next_node_id; + blindedpath_next_blinding(encmsg, blinding, ss, next_blinding); + return true; +} + +/* Returns false on failure */ +bool onion_message_parse(const tal_t *ctx, + const u8 *onion_message_packet, + const struct pubkey *blinding, + const struct node_id *peer, + const struct pubkey *me, + u8 **next_onion_msg, + struct pubkey *next_node_id, + struct tlv_onionmsg_tlv **final_om, + struct pubkey *final_alias, + struct secret **final_path_id) +{ + enum onion_wire badreason; + struct onionpacket *op; + struct pubkey ephemeral; + struct route_step *rs; + struct tlv_onionmsg_tlv *om; + struct secret ss, onion_ss; + const u8 *cursor; + size_t max, maxlen; + + /* We unwrap the onion now. */ + op = parse_onionpacket(tmpctx, + onion_message_packet, + tal_bytelen(onion_message_packet), + &badreason); + if (!op) { + status_peer_debug(peer, "onion_message_parse: can't parse onionpacket: %s", + onion_wire_name(badreason)); + return false; + } + + ephemeral = op->ephemeralkey; + if (!unblind_onion(blinding, ecdh, &ephemeral, &ss)) { + status_peer_debug(peer, "onion_message_parse: can't unblind onionpacket"); + return false; + } + + /* Now get onion shared secret and parse it. */ + ecdh(&ephemeral, &onion_ss); + rs = process_onionpacket(tmpctx, op, &onion_ss, NULL, 0, false); + if (!rs) { + status_peer_debug(peer, + "onion_message_parse: can't process onionpacket ss=%s", + type_to_string(tmpctx, struct secret, &onion_ss)); + return false; + } + + /* The raw payload is prepended with length in the modern onion. */ + cursor = rs->raw_payload; + max = tal_bytelen(rs->raw_payload); + maxlen = fromwire_bigsize(&cursor, &max); + if (!cursor) { + status_peer_debug(peer, "onion_message_parse: Invalid hop payload %s", + tal_hex(tmpctx, rs->raw_payload)); + return false; + } + if (maxlen > max) { + status_peer_debug(peer, "onion_message_parse: overlong hop payload %s", + tal_hex(tmpctx, rs->raw_payload)); + return false; + } + + om = fromwire_tlv_onionmsg_tlv(tmpctx, &cursor, &maxlen); + if (!om) { + status_peer_debug(peer, "onion_message_parse: invalid onionmsg_tlv %s", + tal_hex(tmpctx, rs->raw_payload)); + return false; + } + + if (rs->nextcase == ONION_END) { + *next_onion_msg = NULL; + *final_om = tal_steal(ctx, om); + /* Final enctlv is actually optional */ + if (!om->encrypted_recipient_data) { + *final_alias = *me; + *final_path_id = NULL; + } else if (!decrypt_final_onionmsg(ctx, blinding, &ss, + om->encrypted_recipient_data, me, + final_alias, + final_path_id)) { + status_peer_debug(peer, + "onion_message_parse: failed to decrypt encrypted_recipient_data" + " %s", tal_hex(tmpctx, om->encrypted_recipient_data)); + return false; + } + } else { + struct pubkey next_blinding; + + *final_om = NULL; + + /* This fails as expected if no enctlv. */ + if (!decrypt_forwarding_onionmsg(blinding, &ss, om->encrypted_recipient_data, next_node_id, + &next_blinding)) { + status_peer_debug(peer, + "onion_message_parse: invalid encrypted_recipient_data %s", + tal_hex(tmpctx, om->encrypted_recipient_data)); + return false; + } + + *next_onion_msg = towire_onion_message(ctx, + &next_blinding, + serialize_onionpacket(tmpctx, rs->next)); + } + + /* Exactly one is set */ + assert(!*next_onion_msg + !*final_om == 1); + return true; +} diff --git a/common/onion_message_parse.h b/common/onion_message_parse.h new file mode 100644 index 000000000..364eae40c --- /dev/null +++ b/common/onion_message_parse.h @@ -0,0 +1,37 @@ +#ifndef LIGHTNING_COMMON_ONION_MESSAGE_PARSE_H +#define LIGHTNING_COMMON_ONION_MESSAGE_PARSE_H +#include "config.h" +#include +#include + +struct tlv_onionmsg_tlv; +struct node_id; +struct pubkey; + +/** + * onion_message_parse: core routine to check onion_message + * @ctx: context to allocate @next_onion_msg or @final_om/@path_id off + * @onion_message_packet: Sphinx-encrypted onion + * @blinding: Blinding we were given for @onion_message_packet + * @peer: node_id of peer (for status_peer_debug msgs) + * @me: my pubkey + * @next_onion_msg (out): set if we should forward, otherwise NULL. + * @next_node_id (out): set to node id to fwd to, iff *@next_onion_msg. + * @final_om (out): set if we're the final hop, otherwise NULL. + * @final_alias (out): our alias (if *@final_om), or our own ID + * @final_path_id (out): secret enclosed, if any (iff *@final_om). + * + * Returns false if it wasn't valid. + */ +bool onion_message_parse(const tal_t *ctx, + const u8 *onion_message_packet, + const struct pubkey *blinding, + const struct node_id *peer, + const struct pubkey *me, + u8 **next_onion_msg, + struct pubkey *next_node_id, + struct tlv_onionmsg_tlv **final_om, + struct pubkey *final_alias, + struct secret **final_path_id); + +#endif /* LIGHTNING_COMMON_ONION_MESSAGE_PARSE_H */ diff --git a/connectd/Makefile b/connectd/Makefile index 684349c41..ad8853ae3 100644 --- a/connectd/Makefile +++ b/connectd/Makefile @@ -62,6 +62,7 @@ CONNECTD_COMMON_OBJS := \ common/node_id.o \ common/onion.o \ common/onionreply.o \ + common/onion_message_parse.o \ common/ping.o \ common/per_peer_state.o \ common/psbt_open.o \ diff --git a/connectd/onion_message.c b/connectd/onion_message.c index bf22caa37..a12671aa0 100644 --- a/connectd/onion_message.c +++ b/connectd/onion_message.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -35,84 +36,17 @@ void onionmsg_req(struct daemon *daemon, const u8 *msg) } } -static bool decrypt_final_onionmsg(const tal_t *ctx, - const struct pubkey *blinding, - const struct secret *ss, - const u8 *enctlv, - const struct pubkey *my_id, - struct pubkey *alias, - struct secret **path_id) -{ - struct tlv_encrypted_data_tlv *encmsg; - - if (!blindedpath_get_alias(ss, my_id, alias)) - return false; - - encmsg = decrypt_encrypted_data(tmpctx, blinding, ss, enctlv); - if (!encmsg) - return false; - - if (tal_bytelen(encmsg->path_id) == sizeof(**path_id)) { - *path_id = tal(ctx, struct secret); - memcpy(*path_id, encmsg->path_id, sizeof(**path_id)); - } else - *path_id = NULL; - - return true; -} - -static bool decrypt_forwarding_onionmsg(const struct pubkey *blinding, - const struct secret *ss, - const u8 *enctlv, - struct pubkey *next_node, - struct pubkey *next_blinding) -{ - struct tlv_encrypted_data_tlv *encmsg; - - encmsg = decrypt_encrypted_data(tmpctx, blinding, ss, enctlv); - if (!encmsg) - return false; - - /* BOLT-onion-message #4: - * - * The reader: - * - if it is not the final node according to the onion encryption: - *... - * - if the `enctlv` ... does not contain - * `next_node_id`: - * - MUST drop the message. - */ - if (!encmsg->next_node_id) - return false; - - /* BOLT-onion-message #4: - * The reader: - * - if it is not the final node according to the onion encryption: - *... - * - if the `enctlv` contains `path_id`: - * - MUST drop the message. - */ - if (encmsg->path_id) - return false; - - *next_node = *encmsg->next_node_id; - blindedpath_next_blinding(encmsg, blinding, ss, next_blinding); - return true; -} - /* Peer sends an onion msg. */ void handle_onion_message(struct daemon *daemon, struct peer *peer, const u8 *msg) { - enum onion_wire badreason; - struct onionpacket *op; - struct pubkey blinding, ephemeral; - struct route_step *rs; + struct pubkey blinding; u8 *onion; - struct tlv_onionmsg_tlv *om; - struct secret ss, onion_ss; - const u8 *cursor; - size_t max, maxlen; + u8 *next_onion_msg; + struct pubkey next_node; + struct tlv_onionmsg_tlv *final_om; + struct pubkey final_alias; + struct secret *final_path_id; /* Ignore unless explicitly turned on. */ if (!feature_offered(daemon->our_features->bits[NODE_ANNOUNCE_FEATURE], @@ -127,91 +61,28 @@ void handle_onion_message(struct daemon *daemon, return; } - /* We unwrap the onion now. */ - op = parse_onionpacket(tmpctx, onion, tal_bytelen(onion), &badreason); - if (!op) { - status_peer_debug(&peer->id, "onion msg: can't parse onionpacket: %s", - onion_wire_name(badreason)); + if (!onion_message_parse(tmpctx, onion, &blinding, &peer->id, + &daemon->mykey, + &next_onion_msg, &next_node, + &final_om, &final_alias, &final_path_id)) return; - } - ephemeral = op->ephemeralkey; - if (!unblind_onion(&blinding, ecdh, &ephemeral, &ss)) { - status_peer_debug(&peer->id, "onion msg: can't unblind onionpacket"); - return; - } - - /* Now get onion shared secret and parse it. */ - ecdh(&ephemeral, &onion_ss); - rs = process_onionpacket(tmpctx, op, &onion_ss, NULL, 0, false); - if (!rs) { - status_peer_debug(&peer->id, - "onion msg: can't process onionpacket ss=%s", - type_to_string(tmpctx, struct secret, &onion_ss)); - return; - } - - /* The raw payload is prepended with length in the modern onion. */ - cursor = rs->raw_payload; - max = tal_bytelen(rs->raw_payload); - maxlen = fromwire_bigsize(&cursor, &max); - if (!cursor) { - status_peer_debug(&peer->id, "onion msg: Invalid hop payload %s", - tal_hex(tmpctx, rs->raw_payload)); - return; - } - if (maxlen > max) { - status_peer_debug(&peer->id, "onion msg: overlong hop payload %s", - tal_hex(tmpctx, rs->raw_payload)); - return; - } - - om = fromwire_tlv_onionmsg_tlv(msg, &cursor, &maxlen); - if (!om) { - status_peer_debug(&peer->id, "onion msg: invalid onionmsg_tlv %s", - tal_hex(tmpctx, rs->raw_payload)); - return; - } - - if (rs->nextcase == ONION_END) { - struct pubkey alias; - struct secret *self_id; + if (final_om) { u8 *omsg; - /* Final enctlv is actually optional */ - if (!om->encrypted_recipient_data) { - alias = daemon->mykey; - self_id = NULL; - } else if (!decrypt_final_onionmsg(tmpctx, &blinding, &ss, - om->encrypted_recipient_data, &daemon->mykey, &alias, - &self_id)) { - status_peer_debug(&peer->id, - "onion msg: failed to decrypt encrypted_recipient_data" - " %s", tal_hex(tmpctx, om->encrypted_recipient_data)); - return; - } - /* We re-marshall here by policy, before handing to lightningd */ omsg = tal_arr(tmpctx, u8, 0); - towire_tlvstream_raw(&omsg, om->fields); + towire_tlvstream_raw(&omsg, final_om->fields); daemon_conn_send(daemon->master, take(towire_connectd_got_onionmsg_to_us(NULL, - &alias, self_id, - om->reply_path, + &final_alias, final_path_id, + final_om->reply_path, omsg))); } else { - struct pubkey next_node, next_blinding; - struct peer *next_peer; struct node_id next_node_id; + struct peer *next_peer; - /* This fails as expected if no enctlv. */ - if (!decrypt_forwarding_onionmsg(&blinding, &ss, om->encrypted_recipient_data, &next_node, - &next_blinding)) { - status_peer_debug(&peer->id, - "onion msg: invalid encrypted_recipient_data %s", - tal_hex(tmpctx, om->encrypted_recipient_data)); - return; - } + assert(next_onion_msg); /* FIXME: Handle short_channel_id! */ node_id_from_pubkey(&next_node_id, &next_node); @@ -224,10 +95,7 @@ void handle_onion_message(struct daemon *daemon, &next_node)); return; } - inject_peer_msg(next_peer, - take(towire_onion_message(NULL, - &next_blinding, - serialize_onionpacket(tmpctx, rs->next)))); + inject_peer_msg(next_peer, take(next_onion_msg)); } }