From 28467f3735f7d8073231efcb46954b0d6803ddb0 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 4 May 2020 14:58:37 +0200 Subject: [PATCH] sm: Support encryption using ECDH keys. * sm/decrypt.c (hash_ecc_cms_shared_info): Make global. * sm/encrypt.c (ecdh_encrypt): New. (encrypt_dek): Add arg PK_ALGO and support ECDH. (gpgsm_encrypt): Pass PK_ALGO. -- Note: This has only been tested with a messages created and decrypted by GnuPG. GnuPG-bug-id: 4098 Signed-off-by: Werner Koch Backported-from-master: d5051e31a8fc07c339253c6b82426e0d0115a20a GnuPG-bug-id: 6253 --- sm/decrypt.c | 4 +- sm/encrypt.c | 322 ++++++++++++++++++++++++++++++++++++++++++++++++--- sm/gpgsm.h | 4 + 3 files changed, 313 insertions(+), 17 deletions(-) diff --git a/sm/decrypt.c b/sm/decrypt.c index aed635b28..01260f599 100644 --- a/sm/decrypt.c +++ b/sm/decrypt.c @@ -1,7 +1,7 @@ /* decrypt.c - Decrypt a message * Copyright (C) 2001, 2003, 2010 Free Software Foundation, Inc. * Copyright (C) 2001-2019 Werner Koch - * Copyright (C) 2015-2021 g10 Code GmbH + * Copyright (C) 2015-2020 g10 Code GmbH * * This file is part of GnuPG. * @@ -88,7 +88,7 @@ string_from_gcry_buffer (gcry_buffer_t *buffer) * entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, * suppPubInfo [2] EXPLICIT OCTET STRING } * as described in RFC-5753, 7.2. */ -static gpg_error_t +gpg_error_t hash_ecc_cms_shared_info (gcry_md_hd_t hash_hd, const char *wrap_algo_str, unsigned int keylen, const void *ukm, unsigned int ukmlen) diff --git a/sm/encrypt.c b/sm/encrypt.c index 587b568c6..6210f310a 100644 --- a/sm/encrypt.c +++ b/sm/encrypt.c @@ -1,6 +1,8 @@ /* encrypt.c - Encrypt a message * Copyright (C) 2001, 2003, 2004, 2007, 2008, * 2010 Free Software Foundation, Inc. + * Copyright (C) 2001-2019 Werner Koch + * Copyright (C) 2015-2020 g10 Code GmbH * * This file is part of GnuPG. * @@ -16,6 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -144,7 +147,7 @@ init_dek (DEK dek) return 0; } - +/* Encrypt an RSA session key. */ static int encode_session_key (DEK dek, gcry_sexp_t * r_data) { @@ -165,10 +168,282 @@ encode_session_key (DEK dek, gcry_sexp_t * r_data) } +/* Encrypt DEK using ECDH. S_PKEY is the public key. On success the + * result is stored at R_ENCVAL. Example of a public key: + * + * (public-key (ecc (curve "1.3.132.0.34") (q #04B0[...]B8#))) + * + */ +static gpg_error_t +ecdh_encrypt (DEK dek, gcry_sexp_t s_pkey, gcry_sexp_t *r_encval) +{ + gpg_error_t err; + gcry_sexp_t l1; + char *curvebuf = NULL; + const char *curve; + unsigned int curvebits; + const char *encr_algo_str; + const char *wrap_algo_str; + int hash_algo, cipher_algo; + unsigned int keylen, hashlen; + unsigned char key[32]; + gcry_sexp_t s_data = NULL; + gcry_sexp_t s_encr = NULL; + gcry_buffer_t ioarray[2] = { {0}, {0} }; + unsigned char *secret; /* Alias for ioarray[0]. */ + unsigned int secretlen; + unsigned char *pubkey; /* Alias for ioarray[1]. */ + unsigned int pubkeylen; + gcry_cipher_hd_t cipher_hd = NULL; + unsigned char *result = NULL; + unsigned int resultlen; + + *r_encval = NULL; + + /* Figure out the encryption and wrap algo OIDs. */ + /* Get the curve name if any, */ + l1 = gcry_sexp_find_token (s_pkey, "curve", 0); + if (l1) + { + curvebuf = gcry_sexp_nth_string (l1, 1); + gcry_sexp_release (l1); + } + if (!curvebuf) + { + err = gpg_error (GPG_ERR_INV_CURVE); + log_error ("%s: invalid public key: no curve\n", __func__); + goto leave; + } + + /* We need to use our OpenPGP mapping to turn a curve name into its + * canonical numerical OID. We also use this to get the size of the + * curve which we need to figure out a suitable hash algo. We + * should have a Libgcrypt function to do this; see bug report #4926. */ + curve = openpgp_curve_to_oid (curvebuf, &curvebits, NULL); + if (!curve) + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + log_error ("%s: invalid public key: %s\n", __func__, gpg_strerror (err)); + goto leave; + } + xfree (curvebuf); + curvebuf = NULL; + + /* Our mapping matches the recommended algorithms from RFC-5753 but + * not supporing the short curves which would require 3DES. */ + if (curvebits < 255) + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + log_error ("%s: curve '%s' is not supported\n", __func__, curve); + goto leave; + } + else if (curvebits <= 256) + { + /* dhSinglePass-stdDH-sha256kdf-scheme */ + encr_algo_str = "1.3.132.1.11.1"; + wrap_algo_str = "2.16.840.1.101.3.4.1.5"; + hash_algo = GCRY_MD_SHA256; + hashlen = 32; + cipher_algo = GCRY_CIPHER_AES128; + keylen = 16; + } + else if (curvebits <= 384) + { + /* dhSinglePass-stdDH-sha384kdf-scheme */ + encr_algo_str = "1.3.132.1.11.2"; + wrap_algo_str = "2.16.840.1.101.3.4.1.25"; + hash_algo = GCRY_MD_SHA384; + hashlen = 48; + cipher_algo = GCRY_CIPHER_AES256; + keylen = 24; + } + else + { + /* dhSinglePass-stdDH-sha512kdf-scheme*/ + encr_algo_str = "1.3.132.1.11.3"; + wrap_algo_str = "2.16.840.1.101.3.4.1.45"; + hash_algo = GCRY_MD_SHA512; + hashlen = 64; + cipher_algo = GCRY_CIPHER_AES256; + keylen = 32; + } + + + /* Create a secret and an ephemeral key. */ + { + char *k; + k = gcry_random_bytes_secure ((curvebits+7)/8, GCRY_STRONG_RANDOM); + if (DBG_CRYPTO) + log_printhex (k, (curvebits+7)/8, "ephm. k .:"); + err = gcry_sexp_build (&s_data, NULL, "%b", (int)(curvebits+7)/8, k); + xfree (k); + } + if (err) + { + log_error ("%s: error building ephemeral secret: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + + err = gcry_pk_encrypt (&s_encr, s_data, s_pkey); + if (err) + { + log_error ("%s: error encrypting ephemeral secret: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + err = gcry_sexp_extract_param (s_encr, NULL, "&se", + &ioarray+0, ioarray+1, NULL); + if (err) + { + log_error ("%s: error extracting ephemeral key and secret: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + secret = ioarray[0].data; + secretlen = ioarray[0].len; + pubkey = ioarray[1].data; + pubkeylen = ioarray[1].len; + + if (DBG_CRYPTO) + { + log_printhex (pubkey, pubkeylen, "pubkey ..:"); + log_printhex (secret, secretlen, "secret ..:"); + } + + /* Extract X coordinate from SECRET. */ + if (secretlen < 5) /* 5 because N could be reduced to (n-1)/2. */ + err = gpg_error (GPG_ERR_BAD_DATA); + else if (*secret == 0x04) + { + secretlen--; + memmove (secret, secret+1, secretlen); + if ((secretlen & 1)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + goto leave; + } + secretlen /= 2; + } + else if (*secret == 0x40 || *secret == 0x41) + { + secretlen--; + memmove (secret, secret+1, secretlen); + } + else + err = gpg_error (GPG_ERR_BAD_DATA); + if (err) + goto leave; + + if (DBG_CRYPTO) + log_printhex (secret, secretlen, "ECDH X ..:"); + + /* Derive a KEK (key wrapping key) using MESSAGE and SECRET_X. + * According to SEC1 3.6.1 we should check that + * SECRETLEN + UKMLEN + 4 < maxhashlen + * However, we have no practical limit on the hash length and thus + * there is no point in checking this. The second check that + * KEYLEN < hashlen*(2^32-1) + * is obviously also not needed. Because with our allowed + * parameters KEYLEN is always less or equal to HASHLEN so that we + * do not need to iterate at all. + */ + log_assert (gcry_md_get_algo_dlen (hash_algo) == hashlen); + { + gcry_md_hd_t hash_hd; + err = gcry_md_open (&hash_hd, hash_algo, 0); + if (err) + goto leave; + gcry_md_write(hash_hd, secret, secretlen); + gcry_md_write(hash_hd, "\x00\x00\x00\x01", 4); /* counter */ + err = hash_ecc_cms_shared_info (hash_hd, wrap_algo_str, keylen, NULL, 0); + gcry_md_final (hash_hd); + log_assert (keylen <= sizeof key && keylen <= hashlen); + memcpy (key, gcry_md_read (hash_hd, 0), keylen); + gcry_md_close (hash_hd); + if (err) + goto leave; + } + + if (DBG_CRYPTO) + log_printhex (key, keylen, "KEK .....:"); + + /* Wrap the key. */ + if ((dek->keylen % 8) || dek->keylen < 16) + { + log_error ("%s: can't use a session key of %u bytes\n", + __func__, dek->keylen); + err = gpg_error (GPG_ERR_BAD_DATA); + goto leave; + } + + resultlen = dek->keylen + 8; + result = xtrymalloc_secure (resultlen); + if (!result) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gcry_cipher_open (&cipher_hd, cipher_algo, GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) + { + log_error ("%s: failed to initialize AESWRAP: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + + err = gcry_cipher_setkey (cipher_hd, key, keylen); + wipememory (key, sizeof key); + if (err) + { + log_error ("%s: failed in gcry_cipher_setkey: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + + err = gcry_cipher_encrypt (cipher_hd, result, resultlen, + dek->key, dek->keylen); + if (err) + { + log_error ("%s: failed in gcry_cipher_encrypt: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + + if (DBG_CRYPTO) + log_printhex (result, resultlen, "w(CEK) ..:"); + + err = gcry_sexp_build (r_encval, NULL, + "(enc-val(ecdh(e%b)(s%b)(encr-algo%s)(wrap-algo%s)))", + (int)pubkeylen, pubkey, + (int)resultlen, result, + encr_algo_str, + wrap_algo_str, + NULL); + if (err) + log_error ("%s: failed building final S-exp: %s\n", + __func__, gpg_strerror (err)); + + leave: + gcry_cipher_close (cipher_hd); + wipememory (key, sizeof key); + xfree (result); + xfree (ioarray[0].data); + xfree (ioarray[1].data); + gcry_sexp_release (s_data); + gcry_sexp_release (s_encr); + xfree (curvebuf); + return err; +} + + /* Encrypt the DEK under the key contained in CERT and return it as a - canonical S-Exp in encval. */ + * canonical S-expressions at ENCVAL. PK_ALGO is the public key + * algorithm which the caller has already retrieved from CERT. */ static int -encrypt_dek (const DEK dek, ksba_cert_t cert, unsigned char **encval) +encrypt_dek (const DEK dek, ksba_cert_t cert, int pk_algo, + unsigned char **encval) { gcry_sexp_t s_ciph, s_data, s_pkey; int rc; @@ -198,21 +473,38 @@ encrypt_dek (const DEK dek, ksba_cert_t cert, unsigned char **encval) return rc; } - /* Put the encoded cleartext into a simple list. */ - s_data = NULL; /* (avoid compiler warning) */ - rc = encode_session_key (dek, &s_data); - if (rc) + if (DBG_CRYPTO) { - gcry_sexp_release (s_pkey); - log_error ("encode_session_key failed: %s\n", gpg_strerror (rc)); - return rc; + log_printsexp (" pubkey:", s_pkey); + log_printhex (dek->key, dek->keylen, "CEK .....:"); } - /* pass it to libgcrypt */ - rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); + /* Put the encoded cleartext into a simple list. */ + s_data = NULL; /* (avoid compiler warning) */ + if (pk_algo == GCRY_PK_ECC) + { + rc = ecdh_encrypt (dek, s_pkey, &s_ciph); + } + else + { + rc = encode_session_key (dek, &s_data); + if (rc) + { + log_error ("encode_session_key failed: %s\n", gpg_strerror (rc)); + return rc; + } + if (DBG_CRYPTO) + log_printsexp (" data:", s_data); + + /* pass it to libgcrypt */ + rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); + } gcry_sexp_release (s_data); gcry_sexp_release (s_pkey); + if (DBG_CRYPTO) + log_printsexp ("enc-val:", s_ciph); + /* Reformat it. */ if (!rc) { @@ -387,7 +679,7 @@ gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp) err = ksba_cms_set_reader_writer (cms, reader, writer); if (err) { - log_debug ("ksba_cms_set_reader_writer failed: %s\n", + log_error ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (err)); rc = err; goto leave; @@ -402,7 +694,7 @@ gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp) err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); if (err) { - log_debug ("ksba_cms_set_content_type failed: %s\n", + log_error ("ksba_cms_set_content_type failed: %s\n", gpg_strerror (err)); rc = err; goto leave; @@ -500,7 +792,7 @@ gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp) && !gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, NULL, nbits, NULL)) compliant = 0; - rc = encrypt_dek (dek, cl->cert, &encval); + rc = encrypt_dek (dek, cl->cert, pk_algo, &encval); if (rc) { audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, rc); diff --git a/sm/gpgsm.h b/sm/gpgsm.h index bbbd82b54..b74b41419 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -390,6 +390,10 @@ int gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int in_fd, estream_t out_fp); /*-- decrypt.c --*/ +gpg_error_t hash_ecc_cms_shared_info (gcry_md_hd_t hash_hd, + const char *wrap_algo_str, + unsigned int keylen, + const void *ukm, unsigned int ukmlen); int gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp); /*-- certreqgen.c --*/