commit 0775073adf3b2b13748af23516080cdfc3a30ee2 Author: Zack Weinberg zackw@cmu.edu Date: Tue Jun 5 16:22:10 2012 -0700
Add ECDH implementation to crypt.cc/h (no tests yet) --- src/crypt.cc | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/crypt.h | 34 +++++++++++++++++ 2 files changed, 149 insertions(+), 0 deletions(-)
diff --git a/src/crypt.cc b/src/crypt.cc index d4af5e0..8c7a6b9 100644 --- a/src/crypt.cc +++ b/src/crypt.cc @@ -8,8 +8,10 @@
#include <openssl/engine.h> #include <openssl/err.h> +#include <openssl/ecdh.h> #include <openssl/evp.h> #include <openssl/hmac.h> +#include <openssl/objects.h>
static bool crypto_initialized = false; static bool crypto_errs_initialized = false; @@ -410,6 +412,107 @@ gcm_decryptor_impl::decrypt(uint8_t *out, const uint8_t *in, size_t inlen, return 0; }
+// We use the slightly lower-level EC_* / ECDH_* routines for +// ecdh_message, instead of the EVP_PKEY_* routines, because we don't +// need algorithmic agility, and it means we only have to puzzle out +// one layer of completely undocumented APIs instead of two. +namespace { + struct ecdh_message_impl : ecdh_message + { + EC_KEY *priv; + BN_CTX *ctx; + ecdh_message_impl(); // generate keypair from randomness + + virtual ~ecdh_message_impl(); + virtual void encode(uint8_t *xcoord_out) const; + virtual int combine(const uint8_t *other, uint8_t *secret_out) const; + }; +} + +ecdh_message_impl::ecdh_message_impl() + : priv(EC_KEY_new_by_curve_name(NID_secp224r1)), + ctx(BN_CTX_new()) +{ + if (!priv || !ctx) + log_crypto_abort("ecdh_message::allocate data"); + if (!EC_KEY_generate_key(priv)) + log_crypto_abort("ecdh_message::generate priv"); +} + +/* static */ ecdh_message * +ecdh_message::generate() +{ + REQUIRE_INIT_CRYPTO(); + return new ecdh_message_impl(); +} + +ecdh_message::~ecdh_message() {} +ecdh_message_impl::~ecdh_message_impl() +{ + EC_KEY_free(priv); + BN_CTX_free(ctx); +} + +void +ecdh_message_impl::encode(uint8_t *xcoord_out) const +{ + const EC_POINT *pub = EC_KEY_get0_public_key(priv); + const EC_GROUP *grp = EC_KEY_get0_group(priv); + if (!pub || !grp) + log_crypto_abort("ecdh_message_encode::extract pubkey"); + + BIGNUM *x = BN_new(); + if (!x) + log_crypto_abort("ecdh_message_encode::allocate data"); + + if (!EC_POINT_get_affine_coordinates_GFp(grp, pub, x, 0, ctx)) + log_crypto_abort("ecdh_message_encode::extract x-coordinate"); + + size_t sbytes = BN_num_bytes(x); + log_assert(sbytes <= EC_P224_LEN); + if (sbytes < EC_P224_LEN) { + memset(xcoord_out, 0, EC_P224_LEN - sbytes); + sbytes += EC_P224_LEN - sbytes; + } + size_t wbytes = BN_bn2bin(x, xcoord_out); + log_assert(sbytes == wbytes); + + BN_free(x); +} + +int +ecdh_message_impl::combine(const uint8_t *xcoord_other, + uint8_t *secret_out) const +{ + const EC_GROUP *grp = EC_KEY_get0_group(priv); + EC_POINT *pub = EC_POINT_new(grp); + if (!grp || !pub) + log_crypto_abort("ecdh_message_combine::allocate data"); + + int rv = -1; + BIGNUM *x = BN_bin2bn(xcoord_other, EC_P224_LEN, 0); + if (!x) { + log_crypto_warn("ecdh_message_combine::decode their x-coordinate"); + goto done; + } + + if (!EC_POINT_set_compressed_coordinates_GFp(grp, pub, x, 0, ctx)) { + log_crypto_warn("ecdh_message_combine::recover their point"); + goto done; + } + + if (!ECDH_compute_key(secret_out, EC_P224_LEN, pub, priv, 0)) { + log_crypto_warn("ecdh_message_combine::compute shared secret"); + goto done; + } + + rv = 0; + done: + BN_free(x); + EC_POINT_free(pub); + return rv; +} + namespace { struct key_generator_impl : key_generator { @@ -462,6 +565,18 @@ key_generator::from_random_secret(const uint8_t *key, size_t klen, }
key_generator * +key_generator::from_ecdh(const ecdh_message *mine, const uint8_t *theirs, + const uint8_t *salt, size_t slen, + const uint8_t *ctxt, size_t clen) +{ + MemBlock ss(EC_P224_LEN); + if (mine->combine(theirs, ss)) + return 0; + + return from_random_secret(ss, EC_P224_LEN, salt, slen, ctxt, clen); +} + +key_generator * key_generator::from_passphrase(const uint8_t *phra, size_t plen, const uint8_t *salt, size_t slen, const uint8_t *ctxt, size_t clen) diff --git a/src/crypt.h b/src/crypt.h index 4da3309..35e3e37 100644 --- a/src/crypt.h +++ b/src/crypt.h @@ -8,6 +8,7 @@ const size_t AES_BLOCK_LEN = 16; const size_t GCM_TAG_LEN = 16; const size_t SHA256_LEN = 32; +const size_t EC_P224_LEN = 28;
/** * Initialize cryptography library. Must be called before anything that @@ -140,6 +141,31 @@ private: gcm_decryptor& operator=(const gcm_decryptor&) DELETE_METHOD; };
+/** Encapsulation of an elliptic curve Diffie-Hellman message + (we use NIST P-224). */ +struct ecdh_message +{ + ecdh_message() {} + virtual ~ecdh_message(); + + /** Generate a new Diffie-Hellman message from randomness. */ + static ecdh_message *generate(); + + /** Encode a Diffie-Hellman message to the wire format. This + produces only the x-coordinate of the chosen curve point. + The argument must point to EC_P224_LEN bytes of buffer space. */ + virtual void encode(uint8_t *xcoord_out) const = 0; + + /** Combine our message with the wire-format message sent by our + peer, and produce the raw ECDH shared secret. |xcoord_other| + must point to EC_P224_LEN bytes of data, and |secret_out| must + point to the same quantity of buffer space. Normally you should + use key_generator::from_ecdh instead of calling this + directly. */ + virtual int combine(const uint8_t *xcoord_other, uint8_t *secret_out) + const = 0; +}; + /** Generate keying material from an initial key of some kind, a salt value, and a context value, all of which are formally bitstrings. See http://tools.ietf.org/html/rfc5869 for the requirements on the @@ -166,6 +192,14 @@ struct key_generator const uint8_t *salt, size_t slen, const uint8_t *ctxt, size_t clen);
+ /** Construct a key generator from two (elliptic curve) Diffie-Hellman + messages. The salt and context arguments are the same as for + from_random_secret. */ + static key_generator *from_ecdh(const ecdh_message *mine, + const uint8_t *theirs, + const uint8_t *salt, size_t slen, + const uint8_t *ctxt, size_t clen); + /** Write LEN bytes of key material to BUF. May be called repeatedly. Note that HKDF has a hard upper limit on the total amount of key material it can generate. The return value is
tor-commits@lists.torproject.org