diff options
-rw-r--r-- | common/kem.c | 4 | ||||
-rw-r--r-- | g10/ecdh.c | 216 | ||||
-rw-r--r-- | g10/pkglue.c | 366 | ||||
-rw-r--r-- | g10/pkglue.h | 12 |
4 files changed, 346 insertions, 252 deletions
diff --git a/common/kem.c b/common/kem.c index 0e498d37e..bbb450e1b 100644 --- a/common/kem.c +++ b/common/kem.c @@ -144,8 +144,8 @@ compute_kmac256 (void *digest, size_t digestlen, } -/* Compute KEK (shared secret) for ECC with HASHALGO, ECDH result, - ciphertext in ECC_CT, public key in ECC_PK. */ +/* Compute KEK for ECC with HASHALGO, ECDH result, ciphertext in + ECC_CT (which is an ephemeral key), and public key in ECC_PK. */ gpg_error_t gnupg_ecc_kem_kdf (void *kek, size_t kek_len, int hashalgo, const void *ecdh, size_t ecdh_len, diff --git a/g10/ecdh.c b/g10/ecdh.c index 279508bec..ae287ec56 100644 --- a/g10/ecdh.c +++ b/g10/ecdh.c @@ -236,6 +236,68 @@ derive_kek (size_t kek_size, return err; } +/* Compute KEK for ECC with HASHALGO, ECDH result, and public key in + PK, using the method defined in RFC 6637. Note that ephemeral key + is not used to compute KEK here. */ +gpg_error_t +gnupg_ecc_6637_kdf (void *kek, size_t kek_len, + int hashalgo, const void *ecdh, size_t ecdh_len, + PKT_public_key *pk) +{ + gpg_error_t err; + unsigned char kdf_params[256]; + size_t kdf_params_size; + unsigned int nbits; + byte *secret_x; + int secret_x_size; + gcry_kdf_hd_t hd; + unsigned long param[1]; + byte fp[MAX_FINGERPRINT_LEN]; + + fingerprint_from_pk (pk, fp, NULL); + + /* Build kdf_params. */ + err = build_kdf_params (kdf_params, &kdf_params_size, pk->pkey, fp); + if (err) + return err; + + nbits = pubkey_nbits (PUBKEY_ALGO_ECDH, pk->pkey); + if (!nbits) + return gpg_error (GPG_ERR_TOO_SHORT); + + secret_x_size = (nbits+7)/8; + if (kek_len > secret_x_size) + return gpg_error (GPG_ERR_BAD_PUBKEY); + + err = extract_secret_x (&secret_x, ecdh, ecdh_len, + /* pk->pkey[1] is the public point */ + (mpi_get_nbits (pk->pkey[1])+7)/8, + secret_x_size); + if (err) + return err; + + param[0] = kek_len; + err = gcry_kdf_open (&hd, GCRY_KDF_ONESTEP_KDF, hashalgo, param, 1, + secret_x, secret_x_size, NULL, 0, NULL, 0, + kdf_params, kdf_params_size); + if (!err) + { + gcry_kdf_compute (hd, NULL); + gcry_kdf_final (hd, kek_len, secret_x); + gcry_kdf_close (hd); + memcpy (kek, secret_x, kek_len); + /* Clean the tail before returning. */ + memset (secret_x+kek_len, 0, secret_x_size - kek_len); + xfree (secret_x); + return 0; + } + else + { + xfree (secret_x); + return err; + } +} + /* Prepare ECDH using SHARED, PK_FP fingerprint, and PKEY array. Returns the cipher handle in R_HD, which needs to be closed by @@ -358,160 +420,6 @@ prepare_ecdh_with_shared_point (const char *shared, size_t nshared, return err; } -/* Encrypts DATA using a key derived from the ECC shared point SHARED - using the FIPS SP 800-56A compliant method - key_derivation+key_wrapping. PKEY is the public key and PK_FP the - fingerprint of this public key. On success the result is stored at - R_RESULT; on failure NULL is stored at R_RESULT and an error code - returned. */ -gpg_error_t -pk_ecdh_encrypt_with_shared_point (const char *shared, size_t nshared, - const byte pk_fp[MAX_FINGERPRINT_LEN], - const byte *data, size_t ndata, - gcry_mpi_t *pkey, gcry_mpi_t *r_result) -{ - gpg_error_t err; - gcry_cipher_hd_t hd; - byte *data_buf; - int data_buf_size; - gcry_mpi_t result; - byte *in; - - *r_result = NULL; - - err = prepare_ecdh_with_shared_point (shared, nshared, pk_fp, pkey, &hd); - if (err) - return err; - - data_buf_size = ndata; - if ((data_buf_size & 7) != 0) - { - log_error ("can't use a shared secret of %d bytes for ecdh\n", - data_buf_size); - gcry_cipher_close (hd); - return gpg_error (GPG_ERR_BAD_DATA); - } - - data_buf = xtrymalloc_secure( 1 + 2*data_buf_size + 8); - if (!data_buf) - { - err = gpg_error_from_syserror (); - gcry_cipher_close (hd); - return err; - } - - in = data_buf+1+data_buf_size+8; - - /* Write data MPI into the end of data_buf. data_buf is size - aeswrap data. */ - memcpy (in, data, ndata); - - if (DBG_CRYPTO) - log_printhex (in, data_buf_size, "ecdh encrypting :"); - - err = gcry_cipher_encrypt (hd, data_buf+1, data_buf_size+8, - in, data_buf_size); - memset (in, 0, data_buf_size); - gcry_cipher_close (hd); - if (err) - { - log_error ("ecdh failed in gcry_cipher_encrypt: %s\n", - gpg_strerror (err)); - xfree (data_buf); - return err; - } - data_buf[0] = data_buf_size+8; - - if (DBG_CRYPTO) - log_printhex (data_buf+1, data_buf[0], "ecdh encrypted to:"); - - result = gcry_mpi_set_opaque (NULL, data_buf, 8 * (1+data_buf[0])); - if (!result) - { - err = gpg_error_from_syserror (); - xfree (data_buf); - log_error ("ecdh failed to create an MPI: %s\n", - gpg_strerror (err)); - return err; - } - - *r_result = result; - return err; -} - - -static gcry_mpi_t -gen_k (unsigned nbits, int little_endian, int is_opaque) -{ - gcry_mpi_t k; - - if (is_opaque) - { - unsigned char *p; - size_t nbytes = (nbits+7)/8; - - p = gcry_random_bytes_secure (nbytes, GCRY_STRONG_RANDOM); - if ((nbits % 8)) - { - if (little_endian) - p[nbytes-1] &= ((1 << (nbits % 8)) - 1); - else - p[0] &= ((1 << (nbits % 8)) - 1); - } - k = gcry_mpi_set_opaque (NULL, p, nbits); - return k; - } - - k = gcry_mpi_snew (nbits); - if (DBG_CRYPTO) - log_debug ("choosing a random k of %u bits\n", nbits); - - gcry_mpi_randomize (k, nbits-1, GCRY_STRONG_RANDOM); - - if (DBG_CRYPTO) - { - unsigned char *buffer; - if (gcry_mpi_aprint (GCRYMPI_FMT_HEX, &buffer, NULL, k)) - BUG (); - log_debug ("ephemeral scalar MPI #0: %s\n", buffer); - gcry_free (buffer); - } - - return k; -} - - -/* Generate an ephemeral key for the public ECDH key in PKEY. On - success the generated key is stored at R_K; on failure NULL is - stored at R_K and an error code returned. */ -gpg_error_t -pk_ecdh_generate_ephemeral_key (gcry_mpi_t *pkey, gcry_mpi_t *r_k) -{ - unsigned int nbits; - gcry_mpi_t k; - int is_little_endian = 0; - int require_opaque = 0; - - if (openpgp_oid_is_cv448 (pkey[0])) - { - is_little_endian = 1; - require_opaque = 1; - } - - *r_k = NULL; - - nbits = pubkey_nbits (PUBKEY_ALGO_ECDH, pkey); - if (!nbits) - return gpg_error (GPG_ERR_TOO_SHORT); - k = gen_k (nbits, is_little_endian, require_opaque); - if (!k) - BUG (); - - *r_k = k; - return 0; -} - - /* Perform ECDH decryption. */ int diff --git a/g10/pkglue.c b/g10/pkglue.c index db62c9ded..307e39e0c 100644 --- a/g10/pkglue.c +++ b/g10/pkglue.c @@ -183,29 +183,6 @@ sexp_extract_param_sos_nlz (gcry_sexp_t sexp, const char *param, } -static byte * -get_data_from_sexp (gcry_sexp_t sexp, const char *item, size_t *r_size) -{ - gcry_sexp_t list; - size_t valuelen; - const char *value; - byte *v; - - if (DBG_CRYPTO) - log_printsexp ("get_data_from_sexp:", sexp); - - list = gcry_sexp_find_token (sexp, item, 0); - log_assert (list); - value = gcry_sexp_nth_data (list, 1, &valuelen); - log_assert (value); - v = xtrymalloc (valuelen); - memcpy (v, value, valuelen); - gcry_sexp_release (list); - *r_size = valuelen; - return v; -} - - /**************** * Emulate our old PK interface here - sometime in the future we might * change the internal design to directly fit to libgcrypt. @@ -773,95 +750,312 @@ do_encrypt_kem (PKT_public_key *pk, gcry_mpi_t data, int seskey_algo, static gpg_error_t do_encrypt_ecdh (PKT_public_key *pk, gcry_mpi_t data, gcry_mpi_t *resarr) { - gcry_mpi_t *pkey = pk->pkey; - gcry_sexp_t s_ciph = NULL; - gcry_sexp_t s_data = NULL; - gcry_sexp_t s_pkey = NULL; gpg_error_t err; - gcry_mpi_t k = NULL; - char *curve = NULL; - int with_djb_tweak_flag; - gcry_mpi_t public = NULL; - gcry_mpi_t result = NULL; - byte fp[MAX_FINGERPRINT_LEN]; - byte *shared = NULL; - byte *p; - size_t nshared; unsigned int nbits; + gcry_cipher_hd_t hd = NULL; + char *ecc_oid = NULL; + enum gcry_kem_algos ecc_algo; - err = pk_ecdh_generate_ephemeral_key (pkey, &k); - if (err) - goto leave; + const unsigned char *ecc_pubkey; + size_t ecc_pubkey_len; + const unsigned char *seskey; + size_t seskey_len; + unsigned char *enc_seskey = NULL; + size_t enc_seskey_len; - curve = openpgp_oid_to_str (pkey[0]); - if (!curve) + unsigned char ecc_ct[ECC_POINT_LEN_MAX]; + unsigned char ecc_ecdh[ECC_POINT_LEN_MAX]; + size_t ecc_ct_len, ecc_ecdh_len; + + unsigned char *kek = NULL; + size_t kek_len; + + const unsigned char *kek_params; + size_t kek_params_size; + int kdf_hash_algo; + int kdf_encr_algo; + + ecc_oid = openpgp_oid_to_str (pk->pkey[0]); + if (!ecc_oid) { err = gpg_error_from_syserror (); + log_error ("%s: error getting OID for ECC key\n", __func__); + goto leave; + } + ecc_algo = openpgp_oid_to_kem_algo (ecc_oid); + if (ecc_algo == GCRY_KEM_RAW_X25519) + { + /* Legacy OID is OK here. */ + ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits); + ecc_pubkey_len = (nbits+7)/8; + if (ecc_pubkey_len == 33 && *ecc_pubkey == 0x40) + { + ecc_pubkey++; /* Remove the 0x40 prefix. */ + ecc_pubkey_len--; + } + if (ecc_pubkey_len != 32) + { + if (opt.verbose) + log_info ("%s: ECC public key length invalid (%zu)\n", + __func__, ecc_pubkey_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + ecc_ct_len = ecc_ecdh_len = 32; + } + else if (ecc_algo == GCRY_KEM_RAW_X448) + { + ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits); + ecc_pubkey_len = (nbits+7)/8; + if (ecc_pubkey_len != 56) + { + if (opt.verbose) + log_info ("%s: ECC public key length invalid (%zu)\n", + __func__, ecc_pubkey_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + ecc_ct_len = ecc_ecdh_len = 56; + } + else if (ecc_algo == GCRY_KEM_RAW_BP256) + { + ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits); + ecc_pubkey_len = (nbits+7)/8; + if (ecc_pubkey_len != 65) + { + if (opt.verbose) + log_info ("%s: ECC public key length invalid (%zu)\n", + __func__, ecc_pubkey_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + ecc_ct_len = ecc_ecdh_len = 65; + } + else if (ecc_algo == GCRY_KEM_RAW_BP384) + { + ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits); + ecc_pubkey_len = (nbits+7)/8; + if (ecc_pubkey_len != 97) + { + if (opt.verbose) + log_info ("%s: ECC public key length invalid (%zu)\n", + __func__, ecc_pubkey_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + ecc_ct_len = ecc_ecdh_len = 97; + } + else if (ecc_algo == GCRY_KEM_RAW_BP512) + { + ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits); + ecc_pubkey_len = (nbits+7)/8; + if (ecc_pubkey_len != 129) + { + if (opt.verbose) + log_info ("%s: ECC public key length invalid (%zu)\n", + __func__, ecc_pubkey_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + ecc_ct_len = ecc_ecdh_len = 129; + } + else if (ecc_algo == GCRY_KEM_RAW_P256R1) + { + ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits); + ecc_pubkey_len = (nbits+7)/8; + if (ecc_pubkey_len != 65) + { + if (opt.verbose) + log_info ("%s: ECC public key length invalid (%zu)\n", + __func__, ecc_pubkey_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + ecc_ct_len = ecc_ecdh_len = 65; + } + else if (ecc_algo == GCRY_KEM_RAW_P384R1) + { + ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits); + ecc_pubkey_len = (nbits+7)/8; + if (ecc_pubkey_len != 97) + { + if (opt.verbose) + log_info ("%s: ECC public key length invalid (%zu)\n", + __func__, ecc_pubkey_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + ecc_ct_len = ecc_ecdh_len = 97; + } + else if (ecc_algo == GCRY_KEM_RAW_P521R1) + { + ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits); + ecc_pubkey_len = (nbits+7)/8; + if (ecc_pubkey_len != 133) + { + if (opt.verbose) + log_info ("%s: ECC public key length invalid (%zu)\n", + __func__, ecc_pubkey_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + ecc_ct_len = ecc_ecdh_len = 133; + } + else + { + if (opt.verbose) + log_info ("%s: ECC curve %s not supported\n", __func__, ecc_oid); + err = gpg_error (GPG_ERR_INV_DATA); goto leave; } - with_djb_tweak_flag = openpgp_oid_is_cv25519 (pkey[0]); + if (DBG_CRYPTO) + { + log_debug ("ECC curve: %s\n", ecc_oid); + log_printhex (ecc_pubkey, ecc_pubkey_len, "ECC pubkey:"); + } - /* Now use the ephemeral secret to compute the shared point. */ - err = gcry_sexp_build (&s_pkey, NULL, - with_djb_tweak_flag ? - "(public-key(ecdh(curve%s)(flags djb-tweak)(q%m)))" - : "(public-key(ecdh(curve%s)(q%m)))", - curve, pkey[1]); + err = gcry_kem_encap (ecc_algo, + ecc_pubkey, ecc_pubkey_len, + ecc_ct, ecc_ct_len, + ecc_ecdh, ecc_ecdh_len, + NULL, 0); if (err) - goto leave; + { + if (opt.verbose) + log_info ("%s: gcry_kem_encap for ECC (%s) failed\n", + __func__, ecc_oid); + goto leave; + } + if (DBG_CRYPTO) + { + log_printhex (ecc_ct, ecc_ct_len, "ECC ephem:"); + log_printhex (ecc_ecdh, ecc_ecdh_len, "ECC ecdh:"); + } - /* Put K into a simplified S-expression. */ - err = gcry_sexp_build (&s_data, NULL, "%m", k); - if (err) - goto leave; + if (!gcry_mpi_get_flag (pk->pkey[2], GCRYMPI_FLAG_OPAQUE)) + { + err = gpg_error (GPG_ERR_BUG); + goto leave; + } - /* Run encryption. */ - err = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); - if (err) - goto leave; + kek_params = gcry_mpi_get_opaque (pk->pkey[2], &nbits); + kek_params_size = (nbits+7)/8; - gcry_sexp_release (s_data); s_data = NULL; - gcry_sexp_release (s_pkey); s_pkey = NULL; + if (DBG_CRYPTO) + log_printhex (kek_params, kek_params_size, "ecdh KDF params:"); + /* Expect 4 bytes 03 01 hash_alg symm_alg. */ + if (kek_params_size != 4 || kek_params[0] != 3 || kek_params[1] != 1) + { + err = gpg_error (GPG_ERR_BAD_PUBKEY); + goto leave; + } + + kdf_hash_algo = kek_params[2]; + kdf_encr_algo = kek_params[3]; - /* Get the shared point and the ephemeral public key. */ - shared = get_data_from_sexp (s_ciph, "s", &nshared); - if (!shared) + if (DBG_CRYPTO) + log_debug ("ecdh KDF algorithms %s+%s with aeswrap\n", + openpgp_md_algo_name (kdf_hash_algo), + openpgp_cipher_algo_name (kdf_encr_algo)); + + if (kdf_hash_algo != GCRY_MD_SHA256 + && kdf_hash_algo != GCRY_MD_SHA384 + && kdf_hash_algo != GCRY_MD_SHA512) + { + err = gpg_error (GPG_ERR_BAD_PUBKEY); + goto leave; + } + + if (kdf_encr_algo != CIPHER_ALGO_AES + && kdf_encr_algo != CIPHER_ALGO_AES192 + && kdf_encr_algo != CIPHER_ALGO_AES256) + { + err = gpg_error (GPG_ERR_BAD_PUBKEY); + goto leave; + } + + kek_len = gcry_cipher_get_algo_keylen (kdf_encr_algo); + if (kek_len > gcry_md_get_algo_dlen (kdf_hash_algo)) + { + err = gpg_error (GPG_ERR_BAD_PUBKEY); + goto leave; + } + + kek = xtrymalloc (kek_len); + if (!kek) { err = gpg_error_from_syserror (); goto leave; } - err = sexp_extract_param_sos (s_ciph, "e", &public); - gcry_sexp_release (s_ciph); s_ciph = NULL; + + err = gnupg_ecc_6637_kdf (kek, kek_len, kdf_hash_algo, + ecc_ecdh, ecc_ecdh_len, pk); + if (err) + { + if (opt.verbose) + log_info ("%s: kdf for ECC failed\n", __func__); + goto leave; + } + if (DBG_CRYPTO) + log_printhex (kek, kek_len, "KEK:"); + + err = gcry_cipher_open (&hd, kdf_encr_algo, GCRY_CIPHER_MODE_AESWRAP, 0); + if (!err) + err = gcry_cipher_setkey (hd, kek, kek_len); + if (err) { - log_debug ("ECDH ephemeral key:"); - gcry_mpi_dump (public); - log_printf ("\n"); + if (opt.verbose) + log_error ("%s: failed to initialize AESWRAP: %s\n", __func__, + gpg_strerror (err)); + goto leave; } - fingerprint_from_pk (pk, fp, NULL); + seskey = gcry_mpi_get_opaque (data, &nbits); + seskey_len = (nbits+7)/8; + + enc_seskey_len = 1 + seskey_len + 8; + enc_seskey = xtrymalloc (enc_seskey_len); + if (!enc_seskey || enc_seskey_len > 254) + { + err = gpg_error_from_syserror (); + goto leave; + } - p = gcry_mpi_get_opaque (data, &nbits); - result = NULL; - err = pk_ecdh_encrypt_with_shared_point (shared, nshared, fp, p, - (nbits+7)/8, pkey, &result); + enc_seskey[0] = enc_seskey_len - 1; + err = gcry_cipher_encrypt (hd, enc_seskey+1, enc_seskey_len-1, + seskey, seskey_len); if (err) - goto leave; + { + log_error ("%s: wrapping session key failed\n", __func__); + goto leave; + } + if (DBG_CRYPTO) + log_printhex (enc_seskey, enc_seskey_len, "enc_seskey:"); + + resarr[0] = gcry_mpi_set_opaque_copy (NULL, ecc_ct, 8 * ecc_ct_len); + if (!resarr[0]) + { + err = gpg_error_from_syserror (); + goto leave; + } - resarr[0] = public; public = NULL; - resarr[1] = result; result = NULL; + resarr[1] = gcry_mpi_set_opaque_copy (NULL, enc_seskey, 8 * enc_seskey_len); + if (!resarr[1]) + { + err = gpg_error_from_syserror (); + gcry_mpi_release (resarr[0]); + } leave: - gcry_mpi_release (public); - gcry_mpi_release (result); - xfree (shared); - gcry_sexp_release (s_ciph); - gcry_sexp_release (s_data); - gcry_sexp_release (s_pkey); - xfree (curve); - gcry_mpi_release (k); + xfree (enc_seskey); + gcry_cipher_close (hd); + xfree (kek); + wipememory (ecc_ct, sizeof ecc_ct); + wipememory (ecc_ecdh, sizeof ecc_ecdh); + xfree (ecc_oid); return err; } @@ -917,9 +1111,9 @@ do_encrypt_rsa_elg (PKT_public_key *pk, gcry_mpi_t data, gcry_mpi_t *resarr) * change the internal design to directly fit to libgcrypt. PK is is * the OpenPGP public key packet, DATA is an MPI with the to be * encrypted data, and RESARR receives the encrypted data. RESARRAY - * is expected to be an two item array which will be filled with newly - * allocated MPIs. SESKEY_ALGO is required for public key algorithms - * which do not encode it in DATA. + * is expected to be an two/three item array which will be filled with + * newly allocated MPIs. SESKEY_ALGO is required for public key + * algorithms which do not encode it in DATA. */ gpg_error_t pk_encrypt (PKT_public_key *pk, gcry_mpi_t data, int seskey_algo, diff --git a/g10/pkglue.h b/g10/pkglue.h index 2b5c8b143..3516dca47 100644 --- a/g10/pkglue.h +++ b/g10/pkglue.h @@ -38,20 +38,12 @@ int pk_check_secret_key (pubkey_algo_t algo, gcry_mpi_t *skey); /*-- ecdh.c --*/ gcry_mpi_t pk_ecdh_default_params (unsigned int qbits); -gpg_error_t pk_ecdh_generate_ephemeral_key (gcry_mpi_t *pkey, gcry_mpi_t *r_k); -gpg_error_t pk_ecdh_encrypt_with_shared_point -/* */ (const char *shared, size_t nshared, - const byte pk_fp[MAX_FINGERPRINT_LEN], - const byte *data, size_t ndata, - gcry_mpi_t *pkey, - gcry_mpi_t *out); - -int pk_ecdh_encrypt (gcry_mpi_t *resarr, const byte pk_fp[MAX_FINGERPRINT_LEN], - gcry_mpi_t data, gcry_mpi_t * pkey); int pk_ecdh_decrypt (gcry_mpi_t *result, const byte sk_fp[MAX_FINGERPRINT_LEN], gcry_mpi_t data, const byte *frame, size_t nframe, gcry_mpi_t * skey); +gpg_error_t gnupg_ecc_6637_kdf (void *kek, size_t kek_len, int hashalgo, + const void *ecdh, size_t ecdh_len, PKT_public_key *pk); #endif /*GNUPG_G10_PKGLUE_H*/ |