Browse Source

sphinx: Remove obsolete onion implementation

ppa-0.6.1
Christian Decker 8 years ago
parent
commit
d30f3f1a40
  1. 16
      Makefile
  2. 82
      daemon/onion.c
  3. 21
      daemon/onion.h
  4. 102
      test/onion_key.c
  5. 24
      test/onion_key.h
  6. 642
      test/test_onion.c
  7. 345
      test/test_onion.py

16
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

82
daemon/onion.c

@ -1,82 +0,0 @@
#include "log.h"
#include "onion.h"
#include "peer.h"
#include "protobuf_convert.h"
#include "routing.h"
#include <string.h>
/* 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;
}

21
daemon/onion.h

@ -1,21 +0,0 @@
#ifndef LIGHTNING_DAEMON_ONION_H
#define LIGHTNING_DAEMON_ONION_H
#include "config.h"
#include "lightning.pb-c.h"
#include <ccan/short_types/short_types.h>
#include <secp256k1.h>
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 */

102
test/onion_key.c

@ -1,102 +0,0 @@
#define _GNU_SOURCE 1
#include "onion_key.h"
#include "version.h"
#include <time.h>
#include <ccan/str/hex/hex.h>
#include <ccan/opt/opt.h>
#include <assert.h>
#include <secp256k1.h>
#include <secp256k1_ecdh.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
/* 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,
"[<seeds>...]\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;
}

24
test/onion_key.h

@ -1,24 +0,0 @@
#ifndef ONION_KEY_H
#define ONION_KEY_H
#include <ccan/endian/endian.h>
#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 */

642
test/test_onion.c

@ -1,642 +0,0 @@
#define _GNU_SOURCE 1
#include "onion_key.h"
#include "version.h"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <stdbool.h>
#include <assert.h>
#include <ccan/build_assert/build_assert.h>
#include <ccan/tal/tal.h>
#include <ccan/mem/mem.h>
#include <ccan/crypto/sha256/sha256.h>
#include <ccan/endian/endian.h>
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/opt/opt.h>
#include <ccan/str/hex/hex.h>
#include <secp256k1.h>
#include <secp256k1_ecdh.h>
#include <sodium/crypto_stream_aes128ctr.h>
#include <sodium/crypto_auth_hmacsha256.h>
#include <sodium/utils.h>
/*
* 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 <pubkey>... OR\n"
"--decode <privkey>\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;
}

345
test/test_onion.py

@ -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)
Loading…
Cancel
Save