Merge branch 'onion'

This commit is contained in:
Rusty Russell 2016-01-22 06:35:11 +10:30
commit 45e0ab11ff
6 changed files with 796 additions and 181 deletions

View File

@ -33,6 +33,7 @@ TEST_CLI_PROGRAMS := \
TEST_PROGRAMS := \
test/test_state_coverage \
test/onion_key \
test/test_onion
BITCOIN_OBJS := \
@ -99,8 +100,17 @@ test-cli-tests: $(TEST_CLI_PROGRAMS)
cd test-cli; scripts/shutdown.sh 2>/dev/null || true
set -e; cd test-cli; for args in "" --steal --unilateral --htlc-onchain; do scripts/setup.sh && scripts/test.sh $$args && scripts/shutdown.sh; done
test-onion: test/test_onion
set -e; for i in `seq 20`; do ./test/test_onion $$i; done
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
check: test-cli-tests test-onion

3
test/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
test_onion
test_state_coverage
onion_key

102
test/onion_key.c Normal file
View File

@ -0,0 +1,102 @@
#define _GNU_SOURCE 1
#include "secp256k1.h"
#include "secp256k1_ecdh.h"
#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 <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;
random_key(ctx, seckey, &pkey);
secp256k1_ec_pubkey_serialize(ctx, pubkey->u8, &len, &pkey,
SECP256K1_EC_COMPRESSED);
assert(len == sizeof(pubkey->u8));
}
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 Normal file
View File

@ -0,0 +1,24 @@
#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 */

View File

@ -1,6 +1,8 @@
#define _GNU_SOURCE 1
#include "onion_key.h"
#include "secp256k1.h"
#include "secp256k1_ecdh.h"
#include "version.h"
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
@ -10,9 +12,14 @@
#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>
/*
* The client knows the server's public key S (which has corresponding
@ -27,13 +34,6 @@
of m, for each message.
*/
//#define EXPORT_FRIENDLY 1 /* No crypto! */
//#define NO_HMAC 1 /* No real hmac */
struct seckey {
struct sha256 k;
};
struct enckey {
struct sha256 k;
};
@ -74,28 +74,14 @@ static struct hmackey hmackey_from_secret(const unsigned char secret[32])
}
static struct iv iv_from_secret(const unsigned char secret[32], size_t i)
static void ivs_from_secret(const unsigned char secret[32],
struct iv *iv, struct iv *pad_iv)
{
struct iv iv;
struct sha256 sha;
sha_with_seed(secret, 2, &sha);
memcpy(iv.iv, sha.u.u8, sizeof(iv.iv));
#ifdef EXPORT_FRIENDLY
iv.iv[0] = i*2;
#endif
return iv;
}
static struct iv pad_iv_from_secret(const unsigned char secret[32], size_t i)
{
struct iv iv;
struct sha256 sha;
sha_with_seed(secret, 3, &sha);
memcpy(iv.iv, sha.u.u8, sizeof(iv.iv));
#ifdef EXPORT_FRIENDLY
iv.iv[0] = i*2 + 1;
#endif
return iv;
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! */
@ -108,12 +94,97 @@ static void random_bytes(void *dst, size_t n)
d[i] = random() % 256;
}
static void gen_keys(secp256k1_context *ctx,
struct seckey *seckey, secp256k1_pubkey *pubkey)
/* 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->k.u.u8, sizeof(seckey->k));
} while (!secp256k1_ec_pubkey_create(ctx, pubkey, seckey->k.u.u8));
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;
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));
}
/*
@ -145,33 +216,29 @@ static void gen_keys(secp256k1_context *ctx,
#define MAX_HOPS 20
struct hop {
struct sha256 hmac;
/* FIXME: Must use parse/serialize functions. */
secp256k1_pubkey pubkey;
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)
{
#ifdef EXPORT_FRIENDLY
unsigned char *dptr = dst;
const unsigned char *sptr = memcheck(src, len);
size_t i;
for (i = 0; i < len; i++)
dptr[i] = sptr[i] + iv->iv[0] + i / sizeof(struct hop);
return true;
#else
EVP_CIPHER_CTX evpctx;
int outlen;
/* Counter mode allows parallelism in future. */
if (EVP_EncryptInit(&evpctx, EVP_aes_256_ctr(),
if (EVP_EncryptInit(&evpctx, EVP_aes_128_ctr(),
memcheck(enckey->k.u.u8, sizeof(enckey->k)),
memcheck(iv->iv, sizeof(iv->iv))) != 1)
return false;
@ -187,26 +254,16 @@ static bool aes_encrypt(void *dst, const void *src, size_t len,
return false;
assert(outlen == 0);
return true;
#endif
}
static bool aes_decrypt(void *dst, const void *src, size_t len,
const struct enckey *enckey, const struct iv *iv)
{
#ifdef EXPORT_FRIENDLY
unsigned char *dptr = dst;
const unsigned char *sptr = memcheck(src, len);
size_t i;
for (i = 0; i < len; i++)
dptr[i] = sptr[i] - iv->iv[0] - i / sizeof(struct hop);
return true;
#else
EVP_CIPHER_CTX evpctx;
int outlen;
/* Counter mode allows parallelism in future. */
if (EVP_DecryptInit(&evpctx, EVP_aes_256_ctr(),
if (EVP_DecryptInit(&evpctx, EVP_aes_128_ctr(),
memcheck(enckey->k.u.u8, sizeof(enckey->k)),
memcheck(iv->iv, sizeof(iv->iv))) != 1)
return false;
@ -222,7 +279,6 @@ static bool aes_decrypt(void *dst, const void *src, size_t len,
return false;
assert(outlen == 0);
return true;
#endif
}
void dump_contents(const void *data, size_t n)
@ -237,28 +293,26 @@ void dump_contents(const void *data, size_t n)
}
}
static bool decrypt_padding(struct hop *padding, size_t nhops,
const struct enckey *enckey,
const struct iv *iv)
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 decrypting junk before the actual padding.
* we simulate it by encrypting junk before the actual data.
*/
struct hop tmp[MAX_HOPS];
char tmp[offset + len];
/* Keep valgrind happy. */
memset(tmp, 0, (MAX_HOPS - nhops) * sizeof(struct hop));
memset(tmp, 0, offset);
memcpy(tmp + offset, src, len);
memcpy(tmp + MAX_HOPS - nhops, padding, nhops * sizeof(struct hop));
/* FIXME: Assumes we are allowed to decrypt in place! */
if (!aes_decrypt((char *)tmp + offsetof(struct hop, msg),
(char *)tmp + offsetof(struct hop, msg),
sizeof(tmp) - offsetof(struct hop, msg), enckey, iv))
/* FIXME: Assumes we are allowed to encrypt in place! */
if (!aes_encrypt(tmp, tmp, offset+len, enckey, iv))
return false;
memcpy(padding, tmp + MAX_HOPS - nhops, nhops * sizeof(struct hop));
memcpy(dst, tmp + offset, len);
return true;
}
@ -277,32 +331,33 @@ static void make_hmac(const struct hop *hops, size_t num_hops,
const struct hmackey *hmackey,
struct sha256 *hmac)
{
#ifdef NO_HMAC
/* Copy first byte of message on each hop. */
size_t i;
memset(hmac, 0, sizeof(*hmac));
for (i = 0; i < MAX_HOPS; i++) {
if (i < num_hops)
hmac->u.u8[i] = hops[i].msg[0];
else
hmac->u.u8[i] = padding[i - num_hops].msg[0];
}
#else
HMAC_CTX ctx;
size_t len, padlen;
/* Calculate HMAC of pubkey onwards, plus padding. */
/* Calculate HMAC of padding then onion up to and including pubkey. */
HMAC_CTX_init(&ctx);
HMAC_Init_ex(&ctx, memcheck(hmackey->k.u.u8, sizeof(hmackey->k)),
sizeof(hmackey->k), EVP_sha256(), NULL);
len = num_hops*sizeof(struct hop) - offsetof(struct hop, pubkey);
HMAC_Update(&ctx, memcheck((unsigned char *)hops + offsetof(struct hop, pubkey),
len), len);
padlen = (MAX_HOPS - num_hops) * sizeof(struct hop);
HMAC_Update(&ctx, memcheck((unsigned char *)padding, padlen), padlen);
len = num_hops*sizeof(struct hop) - sizeof(hops->hmac);
HMAC_Update(&ctx, memcheck((unsigned char *)hops, len), len);
HMAC_Final(&ctx, hmac->u.u8, NULL);
#endif
}
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))
void dump_pkey(secp256k1_context *ctx, secp256k1_pubkey pkey) {
unsigned char tmp[65];
size_t len;
secp256k1_ec_pubkey_serialize(ctx, tmp, &len, &pkey, 0);
dump_hex(tmp);
}
static bool check_hmac(struct onion *onion, const struct hmackey *hmackey)
@ -310,7 +365,7 @@ static bool check_hmac(struct onion *onion, const struct hmackey *hmackey)
struct sha256 hmac;
make_hmac(onion->hop, MAX_HOPS, NULL, hmackey, &hmac);
return CRYPTO_memcmp(&hmac, &onion->hop[0].hmac, sizeof(hmac)) == 0;
return CRYPTO_memcmp(&hmac, &myhop(onion)->hmac, sizeof(hmac)) == 0;
}
bool create_onion(const secp256k1_pubkey pubkey[],
@ -319,37 +374,37 @@ bool create_onion(const secp256k1_pubkey pubkey[],
struct onion *onion)
{
int i;
struct seckey *seckeys = tal_arr(NULL, struct seckey, num);
secp256k1_pubkey *pubkeys = tal_arr(seckeys, secp256k1_pubkey, num);
struct enckey *enckeys = tal_arr(seckeys, struct enckey, num);
struct hmackey *hmackeys = tal_arr(seckeys, struct hmackey, num);
struct iv *ivs = tal_arr(seckeys, struct iv, num);
struct iv *pad_ivs = tal_arr(seckeys, struct iv, num);
struct hop **padding = tal_arr(seckeys, struct hop *, num);
struct hop **hops = tal_arr(seckeys, struct hop *, num);
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];
HMAC_CTX padding_hmac[MAX_HOPS];
struct hop padding[MAX_HOPS];
size_t junk_hops;
secp256k1_context *ctx;
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
bool ok = false;
if (num > MAX_HOPS)
goto fail;
ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
/* 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].k.u.u8))
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[i] = iv_from_secret(secret, i);
pad_ivs[i] = pad_iv_from_secret(secret, i);
ivs_from_secret(secret, &ivs[i], &pad_ivs[i]);
}
/*
@ -359,18 +414,24 @@ bool create_onion(const secp256k1_pubkey pubkey[],
* and "decrypted" by the others. So we have to generate that
* forwards.
*/
for (i = 1; i < num; i++) {
/* Each one has 1 padding from previous. */
padding[i] = tal_arr(padding, struct hop, i);
/* Copy padding from previous node. */
memcpy(padding[i], padding[i-1], sizeof(struct hop)*(i-1));
/* Previous node "decrypts" it before handing to us */
if (!decrypt_padding(padding[i], i-1,
&enckeys[i-1], &ivs[i-1]))
goto fail;
/* And generates another lot of padding. */
add_padding(padding[i]+i-1, &enckeys[i-1], &pad_ivs[i-1]);
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]);
HMAC_CTX_init(&padding_hmac[i]);
HMAC_Init_ex(&padding_hmac[i],
hmackeys[i].k.u.u8, sizeof(hmackeys[i].k),
EVP_sha256(), NULL);
HMAC_Update(&padding_hmac[i],
memcheck((unsigned char *)padding,
i * sizeof(struct hop)),
i * sizeof(struct hop));
}
/*
@ -380,77 +441,83 @@ bool create_onion(const secp256k1_pubkey pubkey[],
/* 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;
struct hop *myonion;
size_t other_hops, len;
struct hop *myhop;
other_hops = num - i - 1 + junk_hops;
myonion = hops[i] = tal_arr(hops, struct hop, 1 + other_hops);
if (i == num - 1) {
/* Fill with junk. */
random_bytes(myonion + 1,
other_hops * sizeof(struct hop));
} else {
/* Copy from next hop. */
memcpy(myonion + 1, hops[i+1],
other_hops * sizeof(struct hop));
}
/* Our entry is at tail of onion. */
myhop = onion->hop + other_hops;
/* Now populate our hop. */
myonion->pubkey = pubkeys[i];
myhop->pubkey = pubkeys[i];
/* Set message. */
assert(strlen(msg[i]) < MESSAGE_SIZE);
memset(myonion->msg, 0, MESSAGE_SIZE);
strcpy((char *)myonion->msg, msg[i]);
memset(myhop->msg, 0, MESSAGE_SIZE);
strcpy((char *)myhop->msg, msg[i]);
/* Encrypt whole thing from message onwards. */
if (!aes_encrypt(&myonion->msg, &myonion->msg,
(1 + other_hops) * sizeof(struct hop)
- offsetof(struct hop, msg),
&enckeys[i], &ivs[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. */
make_hmac(myonion, 1 + other_hops, padding[i],
&hmackeys[i], &myonion->hmac);
len = (other_hops + 1)*sizeof(struct hop) - sizeof(myhop->hmac);
HMAC_Update(&padding_hmac[i],
memcheck((unsigned char *)onion, len), len);
HMAC_Final(&padding_hmac[i], myhop->hmac.u.u8, NULL);
}
/* Transfer results to onion, for first node. */
assert(tal_count(hops[0]) == MAX_HOPS);
memcpy(onion->hop, hops[0], sizeof(onion->hop));
ok = true;
fail:
tal_free(seckeys);
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.
*/
bool decrypt_onion(const struct seckey *myseckey, struct onion *onion,
struct enckey *enckey, struct iv *pad_iv, size_t i)
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, &onion->hop[0].pubkey,
myseckey->k.u.u8))
if (!secp256k1_ecdh(ctx, secret, &pubkey, myseckey->u.u8))
goto fail;
hmackey = hmackey_from_secret(secret);
*enckey = enckey_from_secret(secret);
iv = iv_from_secret(secret, i);
*pad_iv = pad_iv_from_secret(secret, i);
ivs_from_secret(secret, &iv, pad_iv);
/* Check HMAC. */
#if 0
@ -478,9 +545,11 @@ bool decrypt_onion(const struct seckey *myseckey, struct onion *onion,
if (!check_hmac(onion, &hmackey))
goto fail;
/* Decrypt everything after pubkey. */
if (!aes_decrypt(onion->hop[0].msg, onion->hop[0].msg,
sizeof(*onion) - offsetof(struct hop, msg),
/* 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;
@ -496,55 +565,117 @@ fail:
bool peel_onion(struct onion *onion,
const struct enckey *enckey, const struct iv *pad_iv)
{
/* Move next one to front. */
memmove(&onion->hop[0], &onion->hop[1],
/* 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[MAX_HOPS-1], 0, sizeof(onion->hop[MAX_HOPS-1]));
return aes_encrypt(&onion->hop[MAX_HOPS-1], &onion->hop[MAX_HOPS-1],
sizeof(onion->hop[MAX_HOPS-1]), enckey, pad_iv);
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;
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;
size_t i, hops;
struct seckey seckeys[MAX_HOPS];
secp256k1_pubkey pubkeys[MAX_HOPS];
char *msgs[MAX_HOPS];
struct onion onion;
bool generate = false, decode = false;
assert(EVP_CIPHER_iv_length(EVP_aes_256_ctr()) == sizeof(struct iv));
if (argc != 2)
errx(1, "Usage: %s <num hops>", argv[0]);
hops = atoi(argv[1]);
if (hops == 0 || hops > MAX_HOPS)
errx(1, "%s is invalid number of hops", argv[1]);
assert(EVP_CIPHER_iv_length(EVP_aes_128_ctr()) == sizeof(struct iv));
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);
for (i = 0; i < hops; i++) {
asprintf(&msgs[i], "Message to %zu", i);
gen_keys(ctx, &seckeys[i], &pubkeys[i]);
}
if (generate) {
secp256k1_pubkey pubkeys[MAX_HOPS];
char *msgs[MAX_HOPS];
size_t i;
if (!create_onion(pubkeys, msgs, hops, &onion))
errx(1, "Creating onion packet failed");
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]);
}
/* Now parse and peel. */
for (i = 0; i < hops; i++) {
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;
printf("Decrypting with key %zi\n", i);
if (!decrypt_onion(&seckeys[i], &onion, &enckey, &pad_iv, i))
errx(1, "Decrypting onion for hop %zi", i);
if (strcmp((char *)onion.hop[0].msg, msgs[i]) != 0)
errx(1, "Bad message for hop %zi", i);
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 hop %zi", i);
}
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 Normal file
View File

@ -0,0 +1,345 @@
#!/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)