Skip to content

Attributable errors #8291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions channeld/channeld.c
Original file line number Diff line number Diff line change
Expand Up @@ -2577,10 +2577,11 @@ static void handle_peer_fail_htlc(struct peer *peer, const u8 *msg)
u8 *reason;
struct htlc *htlc;
struct failed_htlc *f;
struct tlv_update_fail_htlc_tlvs *tlvs_attr_data;

/* reason is not an onionreply because spec doesn't know about that */
if (!fromwire_update_fail_htlc(msg, msg,
&channel_id, &id, &reason)) {
&channel_id, &id, &reason, &tlvs_attr_data)) {
peer_failed_warn(peer->pps, &peer->channel_id,
"Bad update_fail_htlc %s", tal_hex(msg, msg));
}
Expand All @@ -2591,7 +2592,12 @@ static void handle_peer_fail_htlc(struct peer *peer, const u8 *msg)
htlc->failed = f = tal(htlc, struct failed_htlc);
f->id = id;
f->sha256_of_onion = NULL;
f->onion = new_onionreply(f, take(reason));
struct attribution_data *attr = tal(f, struct attribution_data);
attr->htlc_hold_time = tal_dup_arr(f, u8, tlvs_attr_data->attribution_data->htlc_hold_times, 80, 0);
attr->truncated_hmac = tal_dup_arr(f, u8, tlvs_attr_data->attribution_data->truncated_hmacs, 840, 0);
f->onion = new_onionreply(f, take(reason),
attr);

start_commit_timer(peer);
return;
}
Expand Down Expand Up @@ -4964,8 +4970,12 @@ static void send_fail_or_fulfill(struct peer *peer, const struct htlc *h)
f->sha256_of_onion,
f->badonion);
} else {
struct tlv_update_fail_htlc_tlvs *attr_data_tlv = tlv_update_fail_htlc_tlvs_new(peer);
attr_data_tlv->attribution_data = tal(peer, struct tlv_update_fail_htlc_tlvs_attribution_data);
memcpy(attr_data_tlv->attribution_data->htlc_hold_times, f->onion->attr_data->htlc_hold_time, 80);
memcpy(attr_data_tlv->attribution_data->truncated_hmacs, f->onion->attr_data->truncated_hmac, 840);
msg = towire_update_fail_htlc(peer, &peer->channel_id, h->id,
f->onion->contents);
f->onion->contents, attr_data_tlv);
}
} else if (h->r) {
msg = towire_update_fulfill_htlc(NULL, &peer->channel_id, h->id,
Expand Down
28 changes: 27 additions & 1 deletion common/onionreply.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ void towire_onionreply(u8 **cursor, const struct onionreply *r)
{
towire_u16(cursor, tal_count(r->contents));
towire_u8_array(cursor, r->contents, tal_count(r->contents));
if (r->attr_data) {
towire_u8_array(cursor, r->attr_data->htlc_hold_time, 80);
towire_u8_array(cursor, r->attr_data->truncated_hmac, 840);
}
}

struct onionreply *fromwire_onionreply(const tal_t *ctx,
Expand All @@ -16,6 +20,14 @@ struct onionreply *fromwire_onionreply(const tal_t *ctx,
struct onionreply *r = tal(ctx, struct onionreply);
r->contents = fromwire_tal_arrn(r, cursor, max,
fromwire_u16(cursor, max));
if (*max >= 80 + 840) {
r->attr_data = tal(ctx, struct attribution_data);
r->attr_data->htlc_hold_time = fromwire_tal_arrn(r, cursor, max, 80);
r->attr_data->truncated_hmac = fromwire_tal_arrn(r, cursor, max, 840);
} else {
r->attr_data = NULL;
}

if (!*cursor)
return tal_free(r);
return r;
Expand All @@ -31,12 +43,26 @@ struct onionreply *dup_onionreply(const tal_t *ctx,

n = tal(ctx, struct onionreply);
n->contents = tal_dup_talarr(n, u8, r->contents);
if (r->attr_data) {
n->attr_data = tal(ctx, struct attribution_data);
n->attr_data->htlc_hold_time = tal_dup_talarr(r, u8, r->attr_data->htlc_hold_time);
n->attr_data->truncated_hmac = tal_dup_talarr(r, u8, r->attr_data->truncated_hmac);
} else {
n->attr_data = NULL;
}
return n;
}

struct onionreply *new_onionreply(const tal_t *ctx, const u8 *contents TAKES)
struct onionreply *new_onionreply(const tal_t *ctx, const u8 *contents TAKES, const struct attribution_data *attr_data TAKES)
{
struct onionreply *r = tal(ctx, struct onionreply);
r->contents = tal_dup_talarr(r, u8, contents);
if (attr_data) {
r->attr_data = tal(ctx, struct attribution_data);
r->attr_data->htlc_hold_time = tal_dup_talarr(r, u8, attr_data->htlc_hold_time);
r->attr_data->truncated_hmac = tal_dup_talarr(r, u8, attr_data->truncated_hmac);
} else {
r->attr_data = NULL;
}
return r;
}
9 changes: 8 additions & 1 deletion common/onionreply.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@
#include <ccan/short_types/short_types.h>
#include <ccan/tal/tal.h>

/* A separate type for attribution data. */
struct attribution_data {
u8 *htlc_hold_time;
u8 *truncated_hmac;
};

/* A separate type for an onion reply, to differentiate from a wire msg. */
struct onionreply {
u8 *contents;
struct attribution_data *attr_data;
};

/**
Expand All @@ -20,5 +27,5 @@ struct onionreply *fromwire_onionreply(const tal_t *ctx,
struct onionreply *dup_onionreply(const tal_t *ctx,
const struct onionreply *r TAKES);

struct onionreply *new_onionreply(const tal_t *ctx, const u8 *contents TAKES);
struct onionreply *new_onionreply(const tal_t *ctx, const u8 *contents TAKES, const struct attribution_data *attr_data TAKES);
#endif /* LIGHTNING_COMMON_ONIONREPLY_H */
137 changes: 134 additions & 3 deletions common/sphinx.c
Original file line number Diff line number Diff line change
Expand Up @@ -818,14 +818,15 @@ struct onionreply *create_onionreply(const tal_t *ctx,
towire(&reply->contents, payload, tal_count(payload));
tal_free(payload);

reply->attr_data = NULL;
return reply;
}

struct onionreply *wrap_onionreply(const tal_t *ctx,
const struct secret *shared_secret,
const struct onionreply *reply)
{
struct secret key;
struct secret key, attr_key;
struct onionreply *result = tal(ctx, struct onionreply);

/* BOLT #4:
Expand All @@ -839,9 +840,109 @@ struct onionreply *wrap_onionreply(const tal_t *ctx,
subkey_from_hmac("ammag", shared_secret, &key);
result->contents = tal_dup_talarr(result, u8, reply->contents);
xor_cipher_stream(result->contents, &key, tal_bytelen(result->contents));

if (reply->attr_data) {
result->attr_data = tal(ctx, struct attribution_data);
subkey_from_hmac("ammagext", shared_secret, &attr_key);
result->attr_data->htlc_hold_time = tal_dup_talarr(result, u8, reply->attr_data->htlc_hold_time);
xor_cipher_stream_off(&attr_key, 0, result->attr_data->htlc_hold_time, tal_bytelen(result->attr_data->htlc_hold_time));
result->attr_data->truncated_hmac = tal_dup_talarr(result, u8, reply->attr_data->truncated_hmac);
xor_cipher_stream_off(&attr_key,
tal_bytelen(result->attr_data->htlc_hold_time),
result->attr_data->truncated_hmac,
tal_bytelen(result->attr_data->truncated_hmac));
} else {
result->attr_data = NULL;
}

return result;
}

static void write_downstream_hmacs(struct crypto_auth_hmacsha256_state *state, int pos, u8 *truncated_hmac) {
int hmac_idx = MAX_HOPS + MAX_HOPS - pos - 1;
for (int i = 0; i < pos; i++) {
hmac_update(state, truncated_hmac + (hmac_idx * TRUNC_HMAC_LEN), TRUNC_HMAC_LEN);
int block_size = MAX_HOPS - i - 1;
hmac_idx += block_size;
}
}

static void add_hmacs_to_attribution_data(struct onionreply *failonion, struct secret *shared_secret) {
struct secret um_key;
subkey_from_hmac("um", shared_secret, &um_key);
for (int i = 0; i < MAX_HOPS; i++) {
struct crypto_auth_hmacsha256_state state;
struct hmac hmac;
int pos = MAX_HOPS - i - 1;
hmac_start(&state, um_key.data, sizeof(um_key.data));
hmac_update(&state, failonion->contents, tal_bytelen(failonion->contents));
hmac_update(&state, failonion->attr_data->htlc_hold_time, (pos + 1) * HOLD_TIME_LEN);
write_downstream_hmacs(&state, pos, failonion->attr_data->truncated_hmac);
hmac_done(&state, &hmac);
memcpy(failonion->attr_data->truncated_hmac + (i * TRUNC_HMAC_LEN), hmac.bytes, TRUNC_HMAC_LEN);
}

}

void update_attributable_data(struct onionreply *failonion, u32 hold_times, struct secret *shared_secret) {
if (!failonion->attr_data) {
failonion->attr_data = tal(failonion, struct attribution_data);
failonion->attr_data->htlc_hold_time = tal_arrz(failonion, u8, 80);
failonion->attr_data->truncated_hmac = tal_arrz(failonion, u8, 840);
} else {
/* Right shift */
memmove(&failonion->attr_data->htlc_hold_time[HOLD_TIME_LEN], failonion->attr_data->htlc_hold_time, HOLD_TIME_LEN * (MAX_HOPS - 1));
int src_index = HMAC_COUNT - 2;
int dest_index = HMAC_COUNT - 1;
int copy_len = 1;

for (int i = 0; i < MAX_HOPS - 1; i++) {
memmove(&failonion->attr_data->truncated_hmac[dest_index * TRUNC_HMAC_LEN],
&failonion->attr_data->truncated_hmac[src_index * TRUNC_HMAC_LEN],
copy_len * TRUNC_HMAC_LEN);
if (i == MAX_HOPS - 2)
break;

copy_len += 1;
src_index -= copy_len + 1;
dest_index -= copy_len;
}
}
u8 *hold_times_be = tal_arr(failonion, u8, 0);
towire_u32(&hold_times_be, hold_times);
memcpy(failonion->attr_data->htlc_hold_time, hold_times_be, HOLD_TIME_LEN);
add_hmacs_to_attribution_data(failonion, shared_secret);
}

static u8 *verify_attr_data(struct onionreply *reply,
int pos,
const struct secret *shared_secret)
{
struct secret um_key;
struct hmac hmac;
struct crypto_auth_hmacsha256_state state;
u8 expected_hmac[4], actual_hmac[4];

subkey_from_hmac("um", shared_secret, &um_key);

hmac_start(&state, um_key.data, sizeof(um_key.data));
hmac_update(&state, reply->contents, tal_bytelen(reply->contents));
hmac_update(&state, reply->attr_data->htlc_hold_time, (pos + 1) * HOLD_TIME_LEN);
write_downstream_hmacs(&state, pos, reply->attr_data->truncated_hmac);
hmac_done(&state, &hmac);
memcpy(expected_hmac, hmac.bytes, TRUNC_HMAC_LEN);

/* Compare with actual index. */
int hmac_idx = MAX_HOPS - pos - 1;
memcpy(actual_hmac, reply->attr_data->truncated_hmac + hmac_idx * TRUNC_HMAC_LEN, TRUNC_HMAC_LEN);

if (memcmp(actual_hmac, expected_hmac, 4) != 0) {
return NULL;
}

return tal_dup_arr(reply, u8, reply->attr_data->htlc_hold_time, 4, 0);
}

u8 *unwrap_onionreply(const tal_t *ctx,
const struct secret *shared_secrets,
const int numhops,
Expand All @@ -852,9 +953,9 @@ u8 *unwrap_onionreply(const tal_t *ctx,
const u8 *cursor;
size_t max;
u16 msglen;

r = new_onionreply(tmpctx, reply->contents);
r = new_onionreply(tmpctx, reply->contents, reply->attr_data);
*origin_index = -1;
int attr_hop_count = numhops > 20 ? 20 : numhops;

for (int i = 0; i < numhops; i++) {
struct secret key;
Expand All @@ -871,6 +972,36 @@ u8 *unwrap_onionreply(const tal_t *ctx,
cursor = r->contents;
max = tal_count(r->contents);

/* Check attribution error HMACs, If present. */
if (r->attr_data) {
if (i < attr_hop_count) {
int pos = attr_hop_count - i - 1;
u8 *hop_time = verify_attr_data(r, pos, &shared_secrets[i]);
if (hop_time) {
/* Shift Left */
memmove(r->attr_data->htlc_hold_time, &r->attr_data->htlc_hold_time[HOLD_TIME_LEN], HOLD_TIME_LEN * (MAX_HOPS - 1));

int src_index = MAX_HOPS;
int dest_index = 1;
int copy_len = MAX_HOPS - 1;

for (int i = 0; i < MAX_HOPS - 1; i++) {
memmove(&r->attr_data->truncated_hmac[dest_index * TRUNC_HMAC_LEN],
&r->attr_data->truncated_hmac[src_index * TRUNC_HMAC_LEN],
copy_len * TRUNC_HMAC_LEN);

src_index += copy_len;
dest_index += copy_len + 1;
copy_len -= 1;
}
// printf("Hop time at position %d: %s \n", i, tal_hexstr(tmpctx, hop_time, 4));
} else {
/* FIXME: Add logging */
// printf("Invalid HMAC at pos: %d", i);
}
}
}

fromwire_hmac(&cursor, &max, &hmac);
/* Too short. */
if (!cursor)
Expand Down
6 changes: 6 additions & 0 deletions common/sphinx.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ struct node_id;
#define ROUTING_INFO_SIZE 1300
#define TOTAL_PACKET_SIZE(payload) (VERSION_SIZE + PUBKEY_SIZE + (payload) + HMAC_SIZE)

#define HOLD_TIME_LEN 4
#define MAX_HOPS 20
#define HMAC_COUNT 210
#define TRUNC_HMAC_LEN 4

struct onionpacket {
/* Cleartext information */
u8 version;
Expand Down Expand Up @@ -241,6 +246,7 @@ struct onionpacket *sphinx_decompress(const tal_t *ctx,
const struct sphinx_compressed_onion *src,
const struct secret *shared_secret);

void update_attributable_data(struct onionreply *failonion, u32 hold_times, struct secret *shared_secret);
/**
* Use ECDH to generate a shared secret from a privkey and a pubkey.
*
Expand Down
2 changes: 1 addition & 1 deletion common/test/run-blindedpath_onion.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ void fromwire_sciddir_or_pubkey(const u8 **cursor UNNEEDED, size_t *max UNNEEDED
struct sciddir_or_pubkey *sciddpk UNNEEDED)
{ fprintf(stderr, "fromwire_sciddir_or_pubkey called!\n"); abort(); }
/* Generated stub for new_onionreply */
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED)
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED, const struct attribution_data *attr_data TAKES UNNEEDED)
{ fprintf(stderr, "new_onionreply called!\n"); abort(); }
/* Generated stub for pubkey_from_node_id */
bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED)
Expand Down
2 changes: 1 addition & 1 deletion common/test/run-onion-message-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED,
void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED)
{ fprintf(stderr, "fromwire_node_id called!\n"); abort(); }
/* Generated stub for new_onionreply */
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED)
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED, const struct attribution_data *attr_data TAKES UNNEEDED)
{ fprintf(stderr, "new_onionreply called!\n"); abort(); }
/* Generated stub for pubkey_from_node_id */
bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED)
Expand Down
2 changes: 1 addition & 1 deletion common/test/run-onion-test-vector.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ bool fromwire_tlv(const u8 **cursor UNNEEDED, size_t *max UNNEEDED,
const char *mvt_tag_str(enum mvt_tag tag UNNEEDED)
{ fprintf(stderr, "mvt_tag_str called!\n"); abort(); }
/* Generated stub for new_onionreply */
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED)
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED, const struct attribution_data *attr_data TAKES UNNEEDED)
{ fprintf(stderr, "new_onionreply called!\n"); abort(); }
/* Generated stub for node_id_from_hexstr */
bool node_id_from_hexstr(const char *str UNNEEDED, size_t slen UNNEEDED, struct node_id *id UNNEEDED)
Expand Down
2 changes: 1 addition & 1 deletion common/test/run-route_blinding_onion_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ void fromwire_sciddir_or_pubkey(const u8 **cursor UNNEEDED, size_t *max UNNEEDED
const char *mvt_tag_str(enum mvt_tag tag UNNEEDED)
{ fprintf(stderr, "mvt_tag_str called!\n"); abort(); }
/* Generated stub for new_onionreply */
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED)
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED, const struct attribution_data *attr_data TAKES UNNEEDED)
{ fprintf(stderr, "new_onionreply called!\n"); abort(); }
/* Generated stub for node_id_from_hexstr */
bool node_id_from_hexstr(const char *str UNNEEDED, size_t slen UNNEEDED, struct node_id *id UNNEEDED)
Expand Down
2 changes: 1 addition & 1 deletion common/test/run-sphinx-xor_cipher_stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ void hmac_update(crypto_auth_hmacsha256_state *state UNNEEDED,
const void *src UNNEEDED, size_t slen UNNEEDED)
{ fprintf(stderr, "hmac_update called!\n"); abort(); }
/* Generated stub for new_onionreply */
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED)
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED, const struct attribution_data *attr_data TAKES UNNEEDED)
{ fprintf(stderr, "new_onionreply called!\n"); abort(); }
/* Generated stub for pubkey_from_node_id */
bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED)
Expand Down
Loading
Loading