From d30f3f1a40ebafae7b444967f5388a883d2d9f0e Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Sun, 16 Oct 2016 16:24:28 +0200 Subject: [PATCH] sphinx: Remove obsolete onion implementation --- Makefile | 16 +- daemon/onion.c | 82 ------ daemon/onion.h | 21 -- test/onion_key.c | 102 ------- test/onion_key.h | 24 -- test/test_onion.c | 642 --------------------------------------------- test/test_onion.py | 345 ------------------------ 7 files changed, 1 insertion(+), 1231 deletions(-) delete mode 100644 daemon/onion.c delete mode 100644 daemon/onion.h delete mode 100644 test/onion_key.c delete mode 100644 test/onion_key.h delete mode 100644 test/test_onion.c delete mode 100644 test/test_onion.py diff --git a/Makefile b/Makefile index 54a360254..52455a724 100644 --- a/Makefile +++ b/Makefile @@ -23,9 +23,7 @@ BITCOIN_FEATURES := \ FEATURES := $(BITCOIN_FEATURES) TEST_PROGRAMS := \ - test/onion_key \ test/test_protocol \ - test/test_onion \ test/test_sphinx BITCOIN_SRC := \ @@ -200,18 +198,6 @@ $(CCAN_OBJS) $(CDUMP_OBJS) $(HELPER_OBJS) $(BITCOIN_OBJS) $(TEST_PROGRAMS:=.o) c # Except for CCAN, everything depends on bitcoin/ and core headers. $(HELPER_OBJS) $(CORE_OBJS) $(BITCOIN_OBJS) $(TEST_PROGRAMS:=.o): $(BITCOIN_HEADERS) $(CORE_HEADERS) $(CCAN_HEADERS) $(GEN_HEADERS) -test-onion: test/test_onion test/onion_key - set -e; TMPF=/tmp/onion.$$$$; test/test_onion --generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do test/test_onion --decode $$(test/onion_key --priv $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF - -test-onion2: test/test_onion test/onion_key - set -e; TMPF=/tmp/onion.$$$$; python test/test_onion.py generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do test/test_onion --decode $$(test/onion_key --priv $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF - -test-onion3: test/test_onion test/onion_key - set -e; TMPF=/tmp/onion.$$$$; test/test_onion --generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do python test/test_onion.py decode $$(test/onion_key --priv $$k) $$(test/onion_key --pub $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF - -test-onion4: test/test_onion test/onion_key - set -e; TMPF=/tmp/onion.$$$$; python test/test_onion.py generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do python test/test_onion.py decode $$(test/onion_key --priv $$k) $$(test/onion_key --pub $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF - test-protocol: test/test_protocol set -e; TMP=`mktemp`; [ -n "$(NO_VALGRIND)" ] || PREFIX="valgrind -q --error-exitcode=7"; for f in test/commits/*.script; do if ! $$PREFIX test/test_protocol < $$f > $$TMP; then echo "test/test_protocol < $$f FAILED" >&2; exit 1; fi; diff -u $$TMP $$f.expected; done; rm $$TMP @@ -220,7 +206,7 @@ doc/protocol-%.svg: test/test_protocol protocol-diagrams: $(patsubst %.script, doc/protocol-%.svg, $(notdir $(wildcard test/commits/*.script))) -check: test-onion test-protocol bitcoin-tests +check: test-protocol bitcoin-tests include bitcoin/Makefile diff --git a/daemon/onion.c b/daemon/onion.c deleted file mode 100644 index 5e6238134..000000000 --- a/daemon/onion.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "log.h" -#include "onion.h" -#include "peer.h" -#include "protobuf_convert.h" -#include "routing.h" -#include - -/* FIXME: http://www.cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf */ - -/* Frees r */ -static const u8 *to_onion(const tal_t *ctx, const Route *r) -{ - u8 *onion = tal_arr(ctx, u8, route__get_packed_size(r)); - route__pack(r, onion); - tal_free(r); - return onion; -} - -/* Create an onion for this path. */ -const u8 *onion_create(const tal_t *ctx, - secp256k1_context *secpctx, - const struct pubkey *ids, - const u64 *amounts, - size_t num_hops) -{ - Route *r = tal(ctx, Route); - size_t i; - - route__init(r); - r->n_steps = num_hops + 1; - r->steps = tal_arr(r, RouteStep *, r->n_steps); - - for (i = 0; i < num_hops; i++) { - r->steps[i] = tal(r, RouteStep); - route_step__init(r->steps[i]); - r->steps[i]->next_case = ROUTE_STEP__NEXT_BITCOIN; - r->steps[i]->bitcoin = pubkey_to_proto(r, secpctx, &ids[i]); - r->steps[i]->amount = amounts[i]; - } - - /* Now the stop marker. */ - r->steps[i] = tal(r, RouteStep); - route_step__init(r->steps[i]); - r->steps[i]->next_case = ROUTE_STEP__NEXT_END; - r->steps[i]->end = true; - r->steps[i]->amount = 0; - - return to_onion(ctx, r); -} - -/* Decode next step in the route, and fill out the onion to send onwards. */ -RouteStep *onion_unwrap(struct peer *peer, - const void *data, size_t len, const u8 **next) -{ - struct ProtobufCAllocator *prototal = make_prototal(peer); - Route *r; - RouteStep *step; - - r = route__unpack(prototal, len, data); - if (!r || r->n_steps == 0) { - log_unusual(peer->log, "Failed to unwrap onion"); - tal_free(prototal); - return NULL; - } - - /* Remove first step. */ - step = r->steps[0]; - /* Make sure that step owns the rest */ - steal_from_prototal(peer, prototal, step); - - /* Re-pack with remaining steps. */ - r->n_steps--; - memmove(r->steps, r->steps + 1, sizeof(*r->steps) * r->n_steps); - - if (!r->n_steps) { - *next = NULL; - tal_free(r); - } else - *next = to_onion(peer, r); - - return step; -} diff --git a/daemon/onion.h b/daemon/onion.h deleted file mode 100644 index 90405524e..000000000 --- a/daemon/onion.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef LIGHTNING_DAEMON_ONION_H -#define LIGHTNING_DAEMON_ONION_H -#include "config.h" -#include "lightning.pb-c.h" -#include -#include - -struct peer; -struct node_connection; - -/* Decode next step in the route, and fill out the onion to send onwards. */ -RouteStep *onion_unwrap(struct peer *peer, - const void *data, size_t len, const u8 **next); - -/* Create an onion for sending msatoshi down path, paying fees. */ -const u8 *onion_create(const tal_t *ctx, - secp256k1_context *secpctx, - const struct pubkey *ids, - const u64 *amounts, - size_t num_hops); -#endif /* LIGHTNING_DAEMON_ONION_H */ diff --git a/test/onion_key.c b/test/onion_key.c deleted file mode 100644 index 611ee27bc..000000000 --- a/test/onion_key.c +++ /dev/null @@ -1,102 +0,0 @@ -#define _GNU_SOURCE 1 -#include "onion_key.h" -#include "version.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Not really! */ -static void random_bytes(void *dst, size_t n) -{ - size_t i; - unsigned char *d = dst; - - for (i = 0; i < n; i++) - d[i] = random() % 256; -} - -static void random_key(secp256k1_context *ctx, - struct seckey *seckey, secp256k1_pubkey *pkey) -{ - do { - random_bytes(seckey->u.u8, sizeof(seckey->u)); - } while (!secp256k1_ec_pubkey_create(ctx, pkey, seckey->u.u8)); -} - -/* We don't want to spend a byte encoding sign, so make sure it's 0x2 */ -static void gen_keys(secp256k1_context *ctx, - struct seckey *seckey, struct compressed_pubkey *pubkey) -{ - secp256k1_pubkey pkey; - size_t len = sizeof(pubkey->u8); - - random_key(ctx, seckey, &pkey); - - secp256k1_ec_pubkey_serialize(ctx, pubkey->u8, &len, &pkey, - SECP256K1_EC_COMPRESSED); - assert(len == sizeof(pubkey->u8)); -} - -static void print_keypair(bool pub, bool priv) -{ - secp256k1_context *ctx; - struct seckey seckey; - struct compressed_pubkey pubkey; - char sechex[hex_str_size(sizeof(seckey))]; - char pubhex[hex_str_size(sizeof(pubkey))]; - - assert(pub || priv); - - ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - gen_keys(ctx, &seckey, &pubkey); - - hex_encode(&seckey, sizeof(seckey), sechex, sizeof(sechex)); - hex_encode(&pubkey, sizeof(pubkey), pubhex, sizeof(pubhex)); - - if (pub && priv) { - printf("%s:%s\n", sechex, pubhex); - } else { - printf("%s\n", (priv ? sechex : pubhex)); - } -} - -int main(int argc, char *argv[]) -{ - bool pub = true, priv = true; - - opt_register_noarg("--help|-h", opt_usage_and_exit, - "[...]\n" - "Generate (deterministic if seed) secp256k1 keys", - "Print this message."); - opt_register_noarg("--pub", - opt_set_invbool, &priv, - "Generate only the public key"); - opt_register_noarg("--priv", - opt_set_invbool, &pub, - "Generate only the private key"); - opt_register_version(); - - opt_parse(&argc, argv, opt_log_stderr_exit); - if (!priv && !pub) - opt_usage_exit_fail("Can't use --pub and --priv"); - - if (argc == 1) { - srandom(time(NULL) + getpid()); - print_keypair(pub, priv); - } else { - int i; - for (i = 1; i < argc; i++) { - srandom(atoi(argv[i])); - print_keypair(pub, priv); - } - } - - return 0; -} diff --git a/test/onion_key.h b/test/onion_key.h deleted file mode 100644 index 61cd36083..000000000 --- a/test/onion_key.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef ONION_KEY_H -#define ONION_KEY_H -#include -#include "bitcoin/privkey.h" - -struct seckey { - union { - struct privkey k; - unsigned char u8[32]; - beint64_t be64[4]; - } u; -}; - -/* First byte is 0x02 or 0x03 indicating even or odd y */ -struct compressed_pubkey { - unsigned char u8[33]; -}; - -/* Prepend 0x02 to get pubkey for libsecp256k1 */ -struct onion_pubkey { - unsigned char u8[32]; -}; - -#endif /* ONION_KEY_H */ diff --git a/test/test_onion.c b/test/test_onion.c deleted file mode 100644 index 33c4a4162..000000000 --- a/test/test_onion.c +++ /dev/null @@ -1,642 +0,0 @@ -#define _GNU_SOURCE 1 -#include "onion_key.h" -#include "version.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * The client knows the server's public key S (which has corresponding - private key s) in advance. - * The client generates an ephemeral private key r, and its corresponding - public key R. - * The client computes K = ECDH(r, S), and sends R to the server at - connection establishing time. - * The server receives R, and computes K = ECHD(R, s). - * Both client and server compute Kenc = SHA256(K || 0) and Kmac = SHA256(K - || 1), and now send HMAC-SHA256(key=Kmac, msg=AES(key=Kenc, msg=m)) instead - of m, for each message. -*/ - -struct enckey { - struct sha256 k; -}; - -struct hmackey { - struct sha256 k; -}; - -struct iv { - unsigned char iv[crypto_stream_aes128ctr_NONCEBYTES]; -}; - -static void sha_with_seed(const unsigned char secret[32], - unsigned char seed, - struct sha256 *res) -{ - struct sha256_ctx ctx; - - sha256_init(&ctx); - sha256_update(&ctx, memcheck(secret, 32), 32); - sha256_u8(&ctx, seed); - sha256_done(&ctx, res); -} - -static struct enckey enckey_from_secret(const unsigned char secret[32]) -{ - struct enckey enckey; - sha_with_seed(secret, 0, &enckey.k); - return enckey; -} - -static struct hmackey hmackey_from_secret(const unsigned char secret[32]) -{ - struct hmackey hmackey; - sha_with_seed(secret, 1, &hmackey.k); - memcheck(&hmackey, 1); - return hmackey; -} - - -static void ivs_from_secret(const unsigned char secret[32], - struct iv *iv, struct iv *pad_iv) -{ - struct sha256 sha; - sha_with_seed(secret, 2, &sha); - BUILD_ASSERT(sizeof(*iv) + sizeof(*pad_iv) == sizeof(sha)); - memcpy(iv->iv, sha.u.u8, sizeof(iv->iv)); - memcpy(pad_iv->iv, sha.u.u8 + sizeof(iv->iv), sizeof(pad_iv->iv)); -} - -/* Not really! */ -static void random_bytes(void *dst, size_t n) -{ - size_t i; - unsigned char *d = dst; - - for (i = 0; i < n; i++) - d[i] = random() % 256; -} - -/* Compressed key would start with 0x3? Subtract from group. Thanks - * Greg Maxwell. */ -static void flip_key(struct seckey *seckey) -{ - int i; - bool carry = 0; - - const int64_t group[] = { - 0xFFFFFFFFFFFFFFFFULL, - 0xFFFFFFFFFFFFFFFEULL, - 0xBAAEDCE6AF48A03BULL, - 0xBFD25E8CD0364141ULL - }; - - for (i = 3; i >= 0; i--) { - uint64_t v = be64_to_cpu(seckey->u.be64[i]); - if (carry) { - /* Beware wrap if v == 0xFFFF.... */ - carry = (group[i] <= v); - v++; - } else - carry = (group[i] < v); - - v = group[i] - v; - seckey->u.be64[i] = cpu_to_be64(v); - } -} - -#if 0 -int main(int argc, char *argv[]) -{ - struct seckey k; - - k.u.be64[0] = cpu_to_be64(0xFFFFFFFFFFFFFFFFULL); - k.u.be64[1] = cpu_to_be64(0xFFFFFFFFFFFFFFFEULL); - k.u.be64[2] = cpu_to_be64(0xBAAEDCE6AF48A03BULL); - k.u.be64[3] = cpu_to_be64(0xBFD25E8CD0364141ULL); - flip_key(&k); - assert(k.u.be64[0] == 0); - assert(k.u.be64[1] == 0); - assert(k.u.be64[2] == 0); - assert(k.u.be64[3] == 0); - flip_key(&k); - assert(k.u.be64[0] == cpu_to_be64(0xFFFFFFFFFFFFFFFFULL)); - assert(k.u.be64[1] == cpu_to_be64(0xFFFFFFFFFFFFFFFEULL)); - assert(k.u.be64[2] == cpu_to_be64(0xBAAEDCE6AF48A03BULL)); - assert(k.u.be64[3] == cpu_to_be64(0xBFD25E8CD0364141ULL)); - - k.u.be64[0] = cpu_to_be64(0xFFFFFFFFFFFFFFFFULL); - k.u.be64[1] = cpu_to_be64(0xFFFFFFFFFFFFFFFEULL); - k.u.be64[2] = cpu_to_be64(0xBAAEDCE6AF48A03BULL); - k.u.be64[3] = cpu_to_be64(0xBFD25E8CD0364142ULL); - flip_key(&k); - assert(k.u.be64[0] == 0xFFFFFFFFFFFFFFFFULL); - assert(k.u.be64[1] == 0xFFFFFFFFFFFFFFFFULL); - assert(k.u.be64[2] == 0xFFFFFFFFFFFFFFFFULL); - assert(k.u.be64[3] == 0xFFFFFFFFFFFFFFFFULL); - flip_key(&k); - assert(k.u.be64[0] == cpu_to_be64(0xFFFFFFFFFFFFFFFFULL)); - assert(k.u.be64[1] == cpu_to_be64(0xFFFFFFFFFFFFFFFEULL)); - assert(k.u.be64[2] == cpu_to_be64(0xBAAEDCE6AF48A03BULL)); - assert(k.u.be64[3] == cpu_to_be64(0xBFD25E8CD0364142ULL)); - - return 0; -} -#endif - -static void random_key(secp256k1_context *ctx, - struct seckey *seckey, secp256k1_pubkey *pkey) -{ - do { - random_bytes(seckey->u.u8, sizeof(seckey->u)); - } while (!secp256k1_ec_pubkey_create(ctx, pkey, seckey->u.u8)); -} - -/* We don't want to spend a byte encoding sign, so make sure it's 0x2 */ -static void gen_keys(secp256k1_context *ctx, - struct seckey *seckey, struct onion_pubkey *pubkey) -{ - unsigned char tmp[33]; - secp256k1_pubkey pkey; - size_t len = sizeof(tmp); - - random_key(ctx, seckey, &pkey); - - secp256k1_ec_pubkey_serialize(ctx, tmp, &len, &pkey, - SECP256K1_EC_COMPRESSED); - assert(len == sizeof(tmp)); - if (tmp[0] == 0x3) - flip_key(seckey); - memcpy(pubkey, tmp+1, sizeof(*pubkey)); -} - -/* - * Onion routing: - * - * Each step decrypts the payload, and removes its message. It then - * pads at the end to keep constant size, by encrypting 0 bytes (ZPAD) - * - * You can see the result of the unwrapping here: - * - * ENC1(PKT1 ENC2(PKT2 ENC3(PKT3 ENC4(PKT4 ENC5(PKT5 RPAD))))) - * After 1: ENC2(PKT2 ENC3(PKT3 ENC4(PKT4 ENC5(PKT5 RPAD)))) - * ENC1(ZPAD) - * After 2: ENC3(PKT3 ENC4(PKT4 ENC5(PKT5 RPAD))) - * DEC2(ENC1(ZPAD)) - * ENC2(ZPAD) - * After 3: ENC4(PKT4 ENC5(PKT5 RPAD))) - * DEC3(DEC2(ENC1(ZPAD)) ENC2(ZPAD)) - * ENC3(ZPAD) - * After 4: ENC5(PKT5 RPAD) - * DEC4(DEC3(DEC2(ENC1(ZPAD)) ENC2(ZPAD)) ENC3(ZPAD)) - * ENC4(ZPAD) - * - * ENC1(PKT1 ENC2(PKT2)) - * => ENC2(PKT2) ENC1(ZPAD) - * => PKT2 DEC2(ENC1(ZPAD)) - */ -#define MESSAGE_SIZE 128 -#define MAX_HOPS 20 - -struct hop { - unsigned char msg[MESSAGE_SIZE]; - struct onion_pubkey pubkey; - struct sha256 hmac; -}; - -struct onion { - struct hop hop[MAX_HOPS]; -}; - -/* We peel from the back. */ -static struct hop *myhop(const struct onion *onion) -{ - return (struct hop *)&onion->hop[MAX_HOPS-1]; -} - -static bool aes_encrypt(void *dst, const void *src, size_t len, - const struct enckey *enckey, const struct iv *iv) -{ - return crypto_stream_aes128ctr_xor(dst, src, len, iv->iv, enckey->k.u.u8) == 0; -} - -static bool aes_decrypt(void *dst, const void *src, size_t len, - const struct enckey *enckey, const struct iv *iv) -{ - return crypto_stream_aes128ctr_xor(dst, src, len, iv->iv, enckey->k.u.u8) == 0; -} - -#if 0 -static void dump_contents(const void *data, size_t n) -{ - size_t i; - const unsigned char *p = memcheck(data, n); - - for (i = 0; i < n; i++) { - printf("%02x", p[i]); - if (i % 16 == 15) - printf("\n"); - } -} -#endif - -static bool aes_encrypt_offset(size_t offset, - void *dst, const void *src, size_t len, - const struct enckey *enckey, - const struct iv *iv) -{ - /* - * FIXME: This would be easier if we could set the counter; instead - * we simulate it by encrypting junk before the actual data. - */ - char tmp[offset + len]; - - /* Keep valgrind happy. */ - memset(tmp, 0, offset); - memcpy(tmp + offset, src, len); - - /* FIXME: Assumes we are allowed to encrypt in place! */ - if (!aes_encrypt(tmp, tmp, offset+len, enckey, iv)) - return false; - - memcpy(dst, tmp + offset, len); - return true; -} - -/* Padding is created by encrypting zeroes. */ -static void add_padding(struct hop *padding, - const struct enckey *enckey, - const struct iv *pad_iv) -{ - static struct hop zerohop; - - aes_encrypt(padding, &zerohop, sizeof(zerohop), enckey, pad_iv); -} - -static void make_hmac(const struct hop *hops, size_t num_hops, - const struct hop *padding, - const struct hmackey *hmackey, - struct sha256 *hmac) -{ - crypto_auth_hmacsha256_state state; - size_t len, padlen = (MAX_HOPS - num_hops) * sizeof(struct hop); - len = num_hops*sizeof(struct hop) - sizeof(hops->hmac); - crypto_auth_hmacsha256_init(&state, hmackey->k.u.u8, sizeof(hmackey->k)); - crypto_auth_hmacsha256_update(&state, memcheck((unsigned char *)padding, padlen), padlen); - crypto_auth_hmacsha256_update(&state, memcheck((unsigned char *)hops, len), len); - crypto_auth_hmacsha256_update(&state, memcheck((unsigned char *)padding, padlen), padlen); - crypto_auth_hmacsha256_final(&state, hmac->u.u8); -} - -#if 0 -static void _dump_hex(unsigned char *x, size_t s) { - printf(" "); - while (s > 0) { - printf("%02x", *x); - x++; s--; - } -} -#define dump_hex(x) _dump_hex((void*)&x, sizeof(x)) - -static void dump_pkey(secp256k1_context *ctx, secp256k1_pubkey pkey) { - unsigned char tmp[65]; - size_t len = sizeof(tmp); - secp256k1_ec_pubkey_serialize(ctx, tmp, &len, &pkey, 0); - dump_hex(tmp); -} -#endif - -static bool check_hmac(struct onion *onion, const struct hmackey *hmackey) -{ - struct sha256 hmac; - - make_hmac(onion->hop, MAX_HOPS, NULL, hmackey, &hmac); - return sodium_memcmp(&hmac, &myhop(onion)->hmac, sizeof(hmac)) == 0; -} - -static bool create_onion(const secp256k1_pubkey pubkey[], - char *const msg[], - size_t num, - struct onion *onion) -{ - int i; - struct seckey seckeys[MAX_HOPS]; - struct onion_pubkey pubkeys[MAX_HOPS]; - struct enckey enckeys[MAX_HOPS]; - struct hmackey hmackeys[MAX_HOPS]; - struct iv ivs[MAX_HOPS]; - struct iv pad_ivs[MAX_HOPS]; - crypto_auth_hmacsha256_state padding_hmac[MAX_HOPS]; - struct hop padding[MAX_HOPS]; - size_t junk_hops; - secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - bool ok = false; - - if (num > MAX_HOPS) - goto fail; - - /* FIXME: I think it would be safe to reuse a single disposable key - * here? */ - /* First generate all the keys. */ - for (i = 0; i < num; i++) { - unsigned char secret[32]; - - gen_keys(ctx, &seckeys[i], &pubkeys[i]); - - - /* Make shared secret. */ - if (!secp256k1_ecdh(ctx, secret, &pubkey[i], seckeys[i].u.u8)) - goto fail; - - hmackeys[i] = hmackey_from_secret(memcheck(secret, 32)); - enckeys[i] = enckey_from_secret(secret); - ivs_from_secret(secret, &ivs[i], &pad_ivs[i]); - } - - /* - * Building the onion is a little tricky. - * - * First, there is the padding. That's generated by previous nodes, - * and "decrypted" by the others. So we have to generate that - * forwards. - */ - for (i = 0; i < num; i++) { - if (i > 0) { - /* Previous node decrypts padding before passing on. */ - aes_decrypt(padding, padding, sizeof(struct hop)*(i-1), - &enckeys[i-1], &ivs[i-1]); - memmove(padding + 1, padding, - sizeof(struct hop)*(i-1)); - } - /* And generates more padding for next node. */ - add_padding(&padding[0], &enckeys[i-1], &pad_ivs[i-1]); - crypto_auth_hmacsha256_init(&padding_hmac[i], - hmackeys[i].k.u.u8, - sizeof(hmackeys[i].k)); - crypto_auth_hmacsha256_update(&padding_hmac[i], - memcheck((unsigned char *)padding, - i * sizeof(struct hop)), - i * sizeof(struct hop)); - } - - /* - * Now the normal onion is generated backwards. - */ - - /* Unused hops filled with random, so even recipient can't tell - * how many were used. */ - junk_hops = MAX_HOPS - num; - random_bytes(onion->hop, junk_hops * sizeof(struct hop)); - - for (i = num - 1; i >= 0; i--) { - size_t other_hops, len; - struct hop *myhop; - - other_hops = num - i - 1 + junk_hops; - - /* Our entry is at tail of onion. */ - myhop = onion->hop + other_hops; - - /* Now populate our hop. */ - myhop->pubkey = pubkeys[i]; - /* Set message. */ - assert(strlen(msg[i]) < MESSAGE_SIZE); - memset(myhop->msg, 0, MESSAGE_SIZE); - strcpy((char *)myhop->msg, msg[i]); - - /* Encrypt whole thing, including our message, but we - * aware it will be offset by the prepended padding. */ - if (!aes_encrypt_offset(i * sizeof(struct hop), - onion, onion, - other_hops * sizeof(struct hop) - + sizeof(myhop->msg), - &enckeys[i], &ivs[i])) - goto fail; - - /* HMAC covers entire thing except hmac itself. */ - len = (other_hops + 1)*sizeof(struct hop) - sizeof(myhop->hmac); - crypto_auth_hmacsha256_update(&padding_hmac[i], - memcheck((unsigned char *)onion, len), len); - crypto_auth_hmacsha256_final(&padding_hmac[i], myhop->hmac.u.u8); - } - - ok = true; -fail: - secp256k1_context_destroy(ctx); - return ok; -} - -static bool pubkey_parse(const secp256k1_context *ctx, - secp256k1_pubkey* pubkey, - struct onion_pubkey *pkey) -{ - unsigned char tmp[33]; - - tmp[0] = 0x2; - memcpy(tmp+1, pkey, sizeof(*pkey)); - return secp256k1_ec_pubkey_parse(ctx, pubkey, tmp, sizeof(tmp)); -} - -/* - * Decrypt onion, return true if onion->hop[0] is valid. - * - * Returns enckey and pad_iv for use in unwrap. - */ -static bool decrypt_onion(const struct seckey *myseckey, struct onion *onion, - struct enckey *enckey, struct iv *pad_iv) -{ - secp256k1_context *ctx; - unsigned char secret[32]; - struct hmackey hmackey; - struct iv iv; - secp256k1_pubkey pubkey; - - ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - - if (!pubkey_parse(ctx, &pubkey, &myhop(onion)->pubkey)) - goto fail; - - /* Extract shared secret. */ - if (!secp256k1_ecdh(ctx, secret, &pubkey, myseckey->u.u8)) - goto fail; - - hmackey = hmackey_from_secret(secret); - *enckey = enckey_from_secret(secret); - ivs_from_secret(secret, &iv, pad_iv); - - /* Check HMAC. */ -#if 0 - printf("Checking HMAC using key%02x%02x%02x%02x%02x%02x%02x%02x (offset %u len %zu) for %02x%02x%02x%02x%02x%02x%02x%02x...%02x%02x%02x\n", - hmackey.k[0], hmackey.k[1], - hmackey.k[2], hmackey.k[3], - hmackey.k[4], hmackey.k[5], - hmackey.k[6], hmackey.k[7], - SHA256_DIGEST_LENGTH, - sizeof(*onion) - SHA256_DIGEST_LENGTH, - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[0], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[1], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[2], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[3], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[4], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[5], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[6], - ((unsigned char *)onion + SHA256_DIGEST_LENGTH)[7], - ((unsigned char *)(onion + 1))[-3], - ((unsigned char *)(onion + 1))[-2], - ((unsigned char *)(onion + 1))[-1]); - dump_contents((unsigned char *)onion + SHA256_DIGEST_LENGTH, - sizeof(*onion) - SHA256_DIGEST_LENGTH); -#endif - if (!check_hmac(onion, &hmackey)) - goto fail; - - /* Decrypt everything up to pubkey. */ - /* FIXME: Assumes we can decrypt in place! */ - if (!aes_decrypt(onion, onion, - sizeof(struct hop) * (MAX_HOPS-1) - + sizeof(myhop(onion)->msg), - enckey, &iv)) - goto fail; - - secp256k1_context_destroy(ctx); - return true; - -fail: - secp256k1_context_destroy(ctx); - return false; -} - -/* Get next layer of onion, for forwarding. */ -static bool peel_onion(struct onion *onion, - const struct enckey *enckey, const struct iv *pad_iv) -{ - /* Move next one to back. */ - memmove(&onion->hop[1], &onion->hop[0], - sizeof(*onion) - sizeof(onion->hop[0])); - - /* Add random-looking (but predictable) padding. */ - memset(&onion->hop[0], 0, sizeof(onion->hop[0])); - return aes_encrypt(&onion->hop[0], &onion->hop[0], - sizeof(onion->hop[0]), enckey, pad_iv); -} - -static bool parse_onion_pubkey(secp256k1_context *ctx, - const char *arg, secp256k1_pubkey *pubkey) -{ - unsigned char tmp[33] = { }; - - if (!hex_decode(arg, strlen(arg), tmp, sizeof(tmp))) - return false; - - return secp256k1_ec_pubkey_parse(ctx, pubkey, tmp, sizeof(tmp)); -} - -static char *make_message(secp256k1_context *ctx, - const secp256k1_pubkey *pubkey) -{ - char *m; - unsigned char tmp[33]; - size_t len = sizeof(tmp); - char hexstr[hex_str_size(20)]; - - secp256k1_ec_pubkey_serialize(ctx, tmp, &len, pubkey, - SECP256K1_EC_COMPRESSED); - hex_encode(tmp+1, 20, hexstr, sizeof(hexstr)); - asprintf(&m, "Message for %s...", hexstr); - return m; -} - -int main(int argc, char *argv[]) -{ - secp256k1_context *ctx; - struct onion onion; - bool generate = false, decode = false; - - opt_register_noarg("--help|-h", opt_usage_and_exit, - "--generate ... OR\n" - "--decode \n" - "Either create an onion message, or decode one step", - "Print this message."); - opt_register_noarg("--generate", - opt_set_bool, &generate, - "Generate onion through the given hex pubkeys"); - opt_register_noarg("--decode", - opt_set_bool, &decode, - "Decode onion given the private key"); - opt_register_version(); - - opt_parse(&argc, argv, opt_log_stderr_exit); - - ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - if (generate) { - secp256k1_pubkey pubkeys[MAX_HOPS]; - char *msgs[MAX_HOPS]; - size_t i; - - if (argc == 1) - opt_usage_exit_fail("Expected at least one pubkey"); - if (argc-1 > MAX_HOPS) - opt_usage_exit_fail("Expected at most %u pubkeys", - MAX_HOPS); - for (i = 1; i < argc; i++) { - if (!parse_onion_pubkey(ctx, argv[i], &pubkeys[i-1])) - errx(1, "Bad pubkey '%s'", argv[i]); - msgs[i-1] = make_message(ctx, &pubkeys[i-1]); - } - - if (!create_onion(pubkeys, msgs, argc - 1, &onion)) - errx(1, "Creating onion packet failed"); - if (!write_all(STDOUT_FILENO, &onion, sizeof(onion))) - err(1, "Writing onion packet"); - return 0; - } else if (decode) { - struct seckey seckey; - secp256k1_pubkey pubkey; - struct enckey enckey; - struct iv pad_iv; - - if (argc != 2) - opt_usage_exit_fail("Expect a privkey with --decode"); - - if (!hex_decode(argv[1], strlen(argv[1]), &seckey, sizeof(seckey))) - errx(1, "Invalid private key hex '%s'", argv[1]); - if (!secp256k1_ec_pubkey_create(ctx, &pubkey, seckey.u.u8)) - errx(1, "Invalid private key '%s'", argv[1]); - - if (!read_all(STDIN_FILENO, &onion, sizeof(onion))) - errx(1, "Reading in onion"); - - if (!decrypt_onion(&seckey, &onion, &enckey, &pad_iv)) - errx(1, "Failed decrypting onion for '%s'", argv[1]); - if (strncmp((char *)myhop(&onion)->msg, make_message(ctx, &pubkey), - sizeof(myhop(&onion)->msg))) - errx(1, "Bad message '%s'", (char *)myhop(&onion)->msg); - if (!peel_onion(&onion, &enckey, &pad_iv)) - errx(1, "Peeling onion for '%s'", argv[1]); - if (!write_all(STDOUT_FILENO, &onion, sizeof(onion))) - err(1, "Writing onion packet"); - return 0; - } else - opt_usage_exit_fail("Need --decode or --generate"); - - secp256k1_context_destroy(ctx); - return 0; -} diff --git a/test/test_onion.py b/test/test_onion.py deleted file mode 100644 index a09983fac..000000000 --- a/test/test_onion.py +++ /dev/null @@ -1,345 +0,0 @@ -#!/usr/bin/env python - -import argparse -import sys -import time - -from hashlib import sha256 -from binascii import hexlify, unhexlify -import hmac -import random - -from cryptography.hazmat.primitives.ciphers import Cipher, modes, algorithms -from cryptography.hazmat.primitives.ciphers.algorithms import AES -from cryptography.hazmat.primitives.ciphers.modes import CTR -from cryptography.hazmat.backends import default_backend -# http://cryptography.io - -from pyelliptic import ecc - -class MyEx(Exception): pass - -def hmac_sha256(k, m): - return hmac.new(k, m, sha256).digest() - - - - - - -## pyelliptic doesn't support compressed pubkey representations -## so we have to add some code... -from pyelliptic.openssl import OpenSSL -import ctypes - -OpenSSL.EC_POINT_set_compressed_coordinates_GFp = \ - OpenSSL._lib.EC_POINT_set_compressed_coordinates_GFp -OpenSSL.EC_POINT_set_compressed_coordinates_GFp.restype = ctypes.c_int -OpenSSL.EC_POINT_set_compressed_coordinates_GFp.argtypes = [ - ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, - ctypes.c_void_p] - -def ecc_ecdh_key(sec, pub): - assert isinstance(sec, ecc.ECC) - if isinstance(pub, ecc.ECC): - pub = pub.get_pubkey() - #return sec.get_ecdh_key(pub) - - pubkey_x, pubkey_y = ecc.ECC._decode_pubkey(pub, 'binary') - - other_key = other_pub_key_x = other_pub_key_y = other_pub_key = None - own_priv_key = res = res_x = res_y = None - try: - other_key = OpenSSL.EC_KEY_new_by_curve_name(sec.curve) - if other_key == 0: - raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ... " + OpenSSL.get_error()) - - other_pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), 0) - other_pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), 0) - - other_group = OpenSSL.EC_KEY_get0_group(other_key) - other_pub_key = OpenSSL.EC_POINT_new(other_group) - if (other_pub_key == None): - raise Exception("[OpenSSl] EC_POINT_new FAIL ... " + OpenSSL.get_error()) - - if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(other_group, - other_pub_key, - other_pub_key_x, - other_pub_key_y, - 0)) == 0: - raise Exception( - "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ..." + OpenSSL.get_error()) - - own_priv_key = OpenSSL.BN_bin2bn(sec.privkey, len(sec.privkey), 0) - - res = OpenSSL.EC_POINT_new(other_group) - if (OpenSSL.EC_POINT_mul(other_group, res, 0, other_pub_key, own_priv_key, 0)) == 0: - raise Exception( - "[OpenSSL] EC_POINT_mul FAIL ..." + OpenSSL.get_error()) - - res_x = OpenSSL.BN_new() - res_y = OpenSSL.BN_new() - - if (OpenSSL.EC_POINT_get_affine_coordinates_GFp(other_group, res, - res_x, - res_y, 0 - )) == 0: - raise Exception( - "[OpenSSL] EC_POINT_get_affine_coordinates_GFp FAIL ... " + OpenSSL.get_error()) - - resx = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(res_x)) - resy = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(res_y)) - - OpenSSL.BN_bn2bin(res_x, resx) - resx = resx.raw - OpenSSL.BN_bn2bin(res_y, resy) - resy = resy.raw - - return resx, resy - - finally: - if other_key: OpenSSL.EC_KEY_free(other_key) - if other_pub_key_x: OpenSSL.BN_free(other_pub_key_x) - if other_pub_key_y: OpenSSL.BN_free(other_pub_key_y) - if other_pub_key: OpenSSL.EC_POINT_free(other_pub_key) - if own_priv_key: OpenSSL.BN_free(own_priv_key) - if res: OpenSSL.EC_POINT_free(res) - if res_x: OpenSSL.BN_free(res_x) - if res_y: OpenSSL.BN_free(res_y) - -def get_pos_y_for_x(pubkey_x, yneg=0): - key = pub_key = pub_key_x = pub_key_y = None - try: - key = OpenSSL.EC_KEY_new_by_curve_name(OpenSSL.get_curve('secp256k1')) - group = OpenSSL.EC_KEY_get0_group(key) - pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), 0) - pub_key = OpenSSL.EC_POINT_new(group) - - if OpenSSL.EC_POINT_set_compressed_coordinates_GFp(group, pub_key, - pub_key_x, yneg, 0) == 0: - raise Exception("[OpenSSL] EC_POINT_set_compressed_coordinates_GFp FAIL ... " + OpenSSL.get_error()) - - - pub_key_y = OpenSSL.BN_new() - if (OpenSSL.EC_POINT_get_affine_coordinates_GFp(group, pub_key, - pub_key_x, - pub_key_y, 0 - )) == 0: - raise Exception("[OpenSSL] EC_POINT_get_affine_coordinates_GFp FAIL ... " + OpenSSL.get_error()) - - pubkeyy = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(pub_key_y)) - OpenSSL.BN_bn2bin(pub_key_y, pubkeyy) - pubkeyy = pubkeyy.raw - field_size = OpenSSL.EC_GROUP_get_degree(OpenSSL.EC_KEY_get0_group(key)) - secret_len = int((field_size + 7) / 8) - if len(pubkeyy) < secret_len: - pubkeyy = pubkeyy.rjust(secret_len, b'\0') - return pubkeyy - finally: - if key is not None: OpenSSL.EC_KEY_free(key) - if pub_key is not None: OpenSSL.EC_POINT_free(pub_key) - if pub_key_x is not None: OpenSSL.BN_free(pub_key_x) - if pub_key_y is not None: OpenSSL.BN_free(pub_key_y) - -def ec_decompress(pubkey, curve='secp256k1'): - if pubkey[0] == '\x02' or pubkey[0] == '\x03': - yneg = ord(pubkey[0]) & 1 - pubkey = "\x04" + pubkey[1:] + get_pos_y_for_x(pubkey[1:], yneg=yneg) - elif pubkey[0] == '\x04': - pass - else: - raise Exception("Unrecognised pubkey format: %s" % (pubkey,)) - return pubkey - -class Onion(object): - HMAC_LEN = 32 - PKEY_LEN = 32 - MSG_LEN = 128 - ZEROES = b"\x00" * (HMAC_LEN + PKEY_LEN + MSG_LEN) - - @staticmethod - def tweak_sha(sha, d): - sha = sha.copy() - sha.update(d) - return sha.digest() - - @classmethod - def get_ecdh_secrets(cls, sec, pkey_x, pkey_y): - pkey = unhexlify('04') + pkey_x + pkey_y - tmp_key = ecc.ECC(curve='secp256k1', pubkey=pkey) - sec_x, sec_y = ecc_ecdh_key(sec, tmp_key) - - b = '\x02' if ord(sec_y[-1]) % 2 == 0 else '\x03' - sec = sha256(sha256(b + sec_x).digest()) - - enckey = cls.tweak_sha(sec, b'\x00')[:16] - hmac = cls.tweak_sha(sec, b'\x01') - ivs = cls.tweak_sha(sec, b'\x02') - iv, pad_iv = ivs[:16], ivs[16:] - - return enckey, hmac, iv, pad_iv - - def enc_pad(self, enckey, pad_iv): - aes = Cipher(AES(enckey), CTR(pad_iv), - default_backend()).encryptor() - return aes.update(self.ZEROES) - -class OnionDecrypt(Onion): - def __init__(self, onion, my_ecc): - self.my_ecc = my_ecc - - hmac_end = len(onion) - pkey_end = hmac_end - self.HMAC_LEN - self.msg_end = pkey_end - self.PKEY_LEN - self.fwd_end = self.msg_end - self.MSG_LEN - - self.onion = onion - self.pkey = onion[self.msg_end:pkey_end] - self.hmac = onion[pkey_end:hmac_end] - - self.get_secrets() - - def decrypt(self): - pad = self.enc_pad(self.enckey, self.pad_iv) - - aes = Cipher(AES(self.enckey), CTR(self.iv), - default_backend()).decryptor() - self.fwd = pad + aes.update(self.onion[:self.fwd_end]) - self.msg = aes.update(self.onion[self.fwd_end:self.msg_end]) - - def get_secrets(self): - pkey_x = self.pkey - pkey_y = get_pos_y_for_x(pkey_x) # always positive by design - enckey, hmac, iv, pad_iv = self.get_ecdh_secrets(self.my_ecc, pkey_x, pkey_y) - if not self.check_hmac(hmac): - raise Exception("HMAC did not verify") - self.enckey = enckey - self.iv = iv - self.pad_iv = pad_iv - - def check_hmac(self, hmac_key): - calc = hmac_sha256(hmac_key, self.onion[:-self.HMAC_LEN]) - return calc == self.hmac - -class OnionEncrypt(Onion): - def __init__(self, msgs, pubkeys): - assert len(msgs) == len(pubkeys) - assert 0 < len(msgs) <= 20 - assert all( len(m) <= self.MSG_LEN for m in msgs ) - - msgs = [m + "\0"*(self.MSG_LEN - len(m)) for m in msgs] - pubkeys = [ecc.ECC(pubkey=pk, curve='secp256k1') for pk in pubkeys] - n = len(msgs) - - tmpkeys = [] - tmppubkeys = [] - for i in range(n): - while True: - t = ecc.ECC(curve='secp256k1') - if ord(t.pubkey_y[-1]) % 2 == 0: - break - # or do the math to "flip" the secret key and pub key - tmpkeys.append(t) - tmppubkeys.append(t.pubkey_x) - - enckeys, hmacs, ivs, pad_ivs = zip(*[self.get_ecdh_secrets(tmpkey, pkey.pubkey_x, pkey.pubkey_y) - for tmpkey, pkey in zip(tmpkeys, pubkeys)]) - - # padding takes the form: - # E_(n-1)(0000s) - # D_(n-1)( - # E(n-2)(0000s) - # D(n-2)( - # ... - # ) - # ) - - padding = "" - for i in range(n-1): - pad = self.enc_pad(enckeys[i], pad_ivs[i]) - aes = Cipher(AES(enckeys[i]), CTR(ivs[i]), - default_backend()).decryptor() - padding = pad + aes.update(padding) - - if n < 20: - padding += str(bytearray(random.getrandbits(8) - for _ in range(len(self.ZEROES) * (20-n)))) - - # to encrypt the message we need to bump the counter past all - # the padding, then just encrypt the final message - aes = Cipher(AES(enckeys[-1]), CTR(ivs[-1]), - default_backend()).encryptor() - aes.update(padding) # don't care about cyphertext - msgenc = aes.update(msgs[-1]) - - msgenc = padding + msgenc + tmppubkeys[-1] - del padding - msgenc += hmac_sha256(hmacs[-1], msgenc) - - # *PHEW* - # now iterate - - for i in reversed(range(n-1)): - # drop the padding this node will add - msgenc = msgenc[len(self.ZEROES):] - # adding the msg - msgenc += msgs[i] - # encrypt it - aes = Cipher(AES(enckeys[i]), CTR(ivs[i]), - default_backend()).encryptor() - msgenc = aes.update(msgenc) - # add the tmp key - msgenc += tmppubkeys[i] - # add the hmac - msgenc += hmac_sha256(hmacs[i], msgenc) - self.onion = msgenc - -def generate(args): - server_keys = [] - msgs = [] - for k in args.pubkeys: - k = unhexlify(k) - msgs.append("Message for %s..." % (hexlify(k[1:21]),)) - k = ec_decompress(k) - server_keys.append(k) - o = OnionEncrypt(msgs, server_keys) - sys.stdout.write(o.onion) - return - -def decode(args): - msg = sys.stdin.read() - key = ecc.ECC(privkey=unhexlify(args.seckey), - pubkey=ec_decompress(unhexlify(args.pubkey)), - curve='secp256k1') - o = OnionDecrypt(msg, key) - o.decrypt() - #sys.stderr.write("Message: \"%s\"\n" % (o.msg,)) - want_msg = "Message for %s..." % (args.pubkey[2:42]) - if o.msg != want_msg + "\0"*(Onion.MSG_LEN - len(want_msg)): - raise Exception("Unexpected message: \"%s\" (wanted: %s)" % (o.msg, want_msg)) - - sys.stdout.write(o.fwd) - -def main(argv): - parser = argparse.ArgumentParser(description="Process some integers.") - sp = parser.add_subparsers() - p = sp.add_parser("generate") - p.add_argument("pubkeys", nargs='+', help="public keys of recipients") - p.set_defaults(func=generate) - - p = sp.add_parser("decode") - p.add_argument("seckey", help="secret key for router") - p.add_argument("pubkey", help="public key for router") - p.set_defaults(func=decode) - - args = parser.parse_args(argv) - - return args.func(args) - - - - -if __name__ == "__main__": - main(sys.argv[1:]) - sys.exit(0) -