aboutsummaryrefslogtreecommitdiffstats
path: root/sm/decrypt.c
diff options
context:
space:
mode:
authorNIIBE Yutaka <[email protected]>2025-10-07 02:47:41 +0000
committerNIIBE Yutaka <[email protected]>2025-10-07 02:47:41 +0000
commit035d0dd4adf345f3e9645545814944bc6041fdef (patch)
tree8cb43b4831e2df69ffa44d90c8d1484453919c62 /sm/decrypt.c
parentgpgsm: Rearrange the cases for decryption. (diff)
downloadgnupg-035d0dd4adf345f3e9645545814944bc6041fdef.tar.gz
gnupg-035d0dd4adf345f3e9645545814944bc6041fdef.zip
gpgsm: Use KEM API for decryption.
* sm/call-agent.c (gpgsm_agent_pkdecrypt): Add USE_KEM argument. * sm/decrypt.c (ecdh_decrypt): Remove. (determine_wrap_cipher, determine_hashalgo): New. (build_shared_info, ecc_kem_pkdecrypt): New. (prepare_decryption): Remove NBITS argument. Call ecc_kem_pkdecrypt, when it's ECC. (gpgsm_decrypt): Follow the removal of NBITS argument. * sm/gpgsm.h (gpgsm_agent_pkdecrypt): Add USE_KEM argument. -- GnuPG-bug-id: 7811 Signed-off-by: NIIBE Yutaka <[email protected]>
Diffstat (limited to 'sm/decrypt.c')
-rw-r--r--sm/decrypt.c474
1 files changed, 244 insertions, 230 deletions
diff --git a/sm/decrypt.c b/sm/decrypt.c
index 81294eeff..b3867e072 100644
--- a/sm/decrypt.c
+++ b/sm/decrypt.c
@@ -193,215 +193,6 @@ ecdh_derive_kek (unsigned char *key, unsigned int keylen,
}
-/* This function will modify SECRET. NBITS is the size of the curve
- * which which we took from the certificate. */
-static gpg_error_t
-ecdh_decrypt (unsigned char *secret, size_t secretlen,
- unsigned int nbits, gcry_sexp_t enc_val,
- unsigned char **r_result, unsigned int *r_resultlen)
-{
- gpg_error_t err;
- gcry_buffer_t ioarray[4] = { {0}, {0}, {0}, {0} };
- char *encr_algo_str = NULL;
- char *wrap_algo_str = NULL;
- int hash_algo, cipher_algo;
- const unsigned char *ukm; /* Alias for ioarray[2]. */
- unsigned int ukmlen;
- const unsigned char *data; /* Alias for ioarray[3]. */
- unsigned int datalen;
- unsigned int keylen;
- unsigned char key[32];
- gcry_cipher_hd_t cipher_hd = NULL;
- unsigned char *result = NULL;
- unsigned int resultlen;
-
- *r_resultlen = 0;
- *r_result = NULL;
-
- /* Extract X from SECRET; this is the actual secret. Unless a
- * smartcard diretcly returns X, it must be in the format of:
- *
- * 04 || X || Y
- * 40 || X
- * 41 || X
- */
- if (secretlen < 2)
- return gpg_error (GPG_ERR_BAD_DATA);
- if (secretlen == (nbits+7)/8)
- ; /* Matches curve length - this is already the X coordinate. */
- else if (*secret == 0x04)
- {
- secretlen--;
- memmove (secret, secret+1, secretlen);
- if ((secretlen & 1))
- return gpg_error (GPG_ERR_BAD_DATA);
- secretlen /= 2;
- }
- else if (*secret == 0x40 || *secret == 0x41)
- {
- secretlen--;
- memmove (secret, secret+1, secretlen);
- }
- else
- return gpg_error (GPG_ERR_BAD_DATA);
- if (!secretlen)
- return gpg_error (GPG_ERR_BAD_DATA);
-
- if (DBG_CRYPTO)
- log_printhex (secret, secretlen, "ECDH X ..:");
-
- /* We have now the shared secret bytes in (SECRET,SECRETLEN). Now
- * we will compute the KEK using a value derived from the secret
- * bytes. */
- err = gcry_sexp_extract_param (enc_val, "enc-val",
- "&'encr-algo''wrap-algo''ukm'?s",
- ioarray+0, ioarray+1,
- ioarray+2, ioarray+3, NULL);
- if (err)
- {
- log_error ("extracting ECDH parameter failed: %s\n", gpg_strerror (err));
- goto leave;
- }
- encr_algo_str = string_from_gcry_buffer (ioarray);
- if (!encr_algo_str)
- {
- err = gpg_error_from_syserror ();
- goto leave;
- }
- wrap_algo_str = string_from_gcry_buffer (ioarray+1);
- if (!wrap_algo_str)
- {
- err = gpg_error_from_syserror ();
- goto leave;
- }
- ukm = ioarray[2].data;
- ukmlen = ioarray[2].len;
- data = ioarray[3].data;
- datalen = ioarray[3].len;
-
- /* Check parameters. */
- if (DBG_CRYPTO)
- {
- log_debug ("encr_algo: %s\n", encr_algo_str);
- log_debug ("wrap_algo: %s\n", wrap_algo_str);
- log_printhex (ukm, ukmlen, "ukm .....:");
- log_printhex (data, datalen, "data ....:");
- }
-
- if (!strcmp (encr_algo_str, "1.3.132.1.11.1"))
- {
- /* dhSinglePass-stdDH-sha256kdf-scheme */
- hash_algo = GCRY_MD_SHA256;
- }
- else if (!strcmp (encr_algo_str, "1.3.132.1.11.2"))
- {
- /* dhSinglePass-stdDH-sha384kdf-scheme */
- hash_algo = GCRY_MD_SHA384;
- }
- else if (!strcmp (encr_algo_str, "1.3.132.1.11.3"))
- {
- /* dhSinglePass-stdDH-sha512kdf-scheme */
- hash_algo = GCRY_MD_SHA512;
- }
- else if (!strcmp (encr_algo_str, "1.3.133.16.840.63.0.2"))
- {
- /* dhSinglePass-stdDH-sha1kdf-scheme */
- hash_algo = GCRY_MD_SHA1;
- }
- else
- {
- err = gpg_error (GPG_ERR_PUBKEY_ALGO);
- goto leave;
- }
-
- if (!strcmp (wrap_algo_str, "2.16.840.1.101.3.4.1.5"))
- {
- cipher_algo = GCRY_CIPHER_AES128;
- keylen = 16;
- }
- else if (!strcmp (wrap_algo_str, "2.16.840.1.101.3.4.1.25"))
- {
- cipher_algo = GCRY_CIPHER_AES192;
- keylen = 24;
- }
- else if (!strcmp (wrap_algo_str, "2.16.840.1.101.3.4.1.45"))
- {
- cipher_algo = GCRY_CIPHER_AES256;
- keylen = 32;
- }
- else
- {
- err = gpg_error (GPG_ERR_PUBKEY_ALGO);
- goto leave;
- }
-
- err = ecdh_derive_kek (key, keylen, hash_algo, wrap_algo_str,
- secret, secretlen, ukm, ukmlen);
- if (err)
- goto leave;
-
- if (DBG_CRYPTO)
- log_printhex (key, keylen, "KEK .....:");
-
- /* Unwrap the key. */
- if ((datalen % 8) || datalen < 16)
- {
- log_error ("can't use a shared secret of %u bytes for ecdh\n", datalen);
- err = gpg_error (GPG_ERR_BAD_DATA);
- goto leave;
- }
-
- resultlen = datalen - 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 ("ecdh failed to initialize AESWRAP: %s\n", gpg_strerror (err));
- goto leave;
- }
-
- err = gcry_cipher_setkey (cipher_hd, key, keylen);
- wipememory (key, sizeof key);
- if (err)
- {
- log_error ("ecdh failed in gcry_cipher_setkey: %s\n", gpg_strerror (err));
- goto leave;
- }
-
- err = gcry_cipher_decrypt (cipher_hd, result, resultlen, data, datalen);
- if (err)
- {
- log_error ("ecdh failed in gcry_cipher_decrypt: %s\n",gpg_strerror (err));
- goto leave;
- }
-
- *r_resultlen = resultlen;
- *r_result = result;
- result = NULL;
-
- leave:
- if (result)
- {
- wipememory (result, resultlen);
- xfree (result);
- }
- gcry_cipher_close (cipher_hd);
- xfree (encr_algo_str);
- xfree (wrap_algo_str);
- xfree (ioarray[0].data);
- xfree (ioarray[1].data);
- xfree (ioarray[2].data);
- xfree (ioarray[3].data);
- return err;
-}
-
-
/* Helper for pwri_decrypt to parse the derive info.
* Example data for (DER,DERLEN):
* SEQUENCE {
@@ -764,11 +555,244 @@ pwri_decrypt (ctrl_t ctrl, gcry_sexp_t enc_val,
}
+static int
+determine_wrap_cipher (unsigned char *wrap_algo, size_t len)
+{
+ int cipher = -1;
+
+ if (len == 22 && !memcmp (wrap_algo, "2.16.840.1.101.3.4.1.5", len))
+ cipher = GCRY_CIPHER_AES128;
+ else if (len == 23 && !memcmp (wrap_algo, "2.16.840.1.101.3.4.1.25", len))
+ cipher = GCRY_CIPHER_AES192;
+ else if (len == 23 && !memcmp (wrap_algo, "2.16.840.1.101.3.4.1.45", len))
+ cipher = GCRY_CIPHER_AES256;
+
+ return cipher;
+}
+
+static int
+determine_hashalgo (unsigned char *kdf_scheme, size_t len)
+{
+ int hashalgo = -1;
+
+ if (len == 14 && !memcmp (kdf_scheme, "1.3.132.1.11.1", len))
+ {
+ /* dhSinglePass-stdDH-sha256kdf-scheme */
+ hashalgo = GCRY_MD_SHA256;
+ }
+ else if (len == 14 && !memcmp (kdf_scheme, "1.3.132.1.11.2", len))
+ {
+ /* dhSinglePass-stdDH-sha384kdf-scheme */
+ hashalgo = GCRY_MD_SHA384;
+ }
+ else if (len == 14 && !memcmp (kdf_scheme, "1.3.132.1.11.3", len))
+ {
+ /* dhSinglePass-stdDH-sha512kdf-scheme */
+ hashalgo = GCRY_MD_SHA512;
+ }
+ else if (len == 21 && !memcmp (kdf_scheme, "1.3.133.16.840.63.0.2", len))
+ {
+ /* dhSinglePass-stdDH-sha1kdf-scheme */
+ hashalgo = GCRY_MD_SHA1;
+ }
+
+ return hashalgo;
+}
+
+
+/* Construct the
+ * ECC-CMS-SharedInfo ::= SEQUENCE {
+ * keyInfo AlgorithmIdentifier,
+ * entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL,
+ * suppPubInfo [2] EXPLICIT OCTET STRING }
+ * as described in RFC-5753, 7.2. */
+static gpg_error_t
+build_shared_info (size_t kek_len, const char *wrap_algo_str,
+ const void *ukm, size_t ukm_len,
+ unsigned char **result_p, size_t *len_p)
+{
+ gpg_error_t err;
+ unsigned char *oid;
+ size_t oidlen, toidlen, tkeyinfo, tukmlen, tsupppubinfo;
+ unsigned char keylenbuf[6];
+ membuf_t mb = MEMBUF_ZERO;
+
+ err = ksba_oid_from_str (wrap_algo_str, &oid, &oidlen);
+ if (err)
+ return err;
+
+ toidlen = get_tlv_length (CLASS_UNIVERSAL, TAG_OBJECT_ID, 0, oidlen);
+ tkeyinfo = get_tlv_length (CLASS_UNIVERSAL, TAG_SEQUENCE, 1, toidlen);
+
+ tukmlen = ukm? get_tlv_length (CLASS_CONTEXT, 0, 1, ukm_len) : 0;
+
+ kek_len *= 8;
+ keylenbuf[0] = TAG_OCTET_STRING;
+ keylenbuf[1] = 4;
+ keylenbuf[2] = (kek_len >> 24);
+ keylenbuf[3] = (kek_len >> 16);
+ keylenbuf[4] = (kek_len >> 8);
+ keylenbuf[5] = kek_len;
+
+ tsupppubinfo = get_tlv_length (CLASS_CONTEXT, 2, 1, sizeof keylenbuf);
+
+ put_tlv_to_membuf (&mb, CLASS_UNIVERSAL, TAG_SEQUENCE, 1,
+ tkeyinfo + tukmlen + tsupppubinfo);
+ put_tlv_to_membuf (&mb, CLASS_UNIVERSAL, TAG_SEQUENCE, 1,
+ toidlen);
+ put_tlv_to_membuf (&mb, CLASS_UNIVERSAL, TAG_OBJECT_ID, 0, oidlen);
+ put_membuf (&mb, oid, oidlen);
+ ksba_free (oid);
+
+ if (ukm)
+ {
+ put_tlv_to_membuf (&mb, CLASS_CONTEXT, 0, 1, ukm_len);
+ put_membuf (&mb, ukm, ukm_len);
+ }
+
+ put_tlv_to_membuf (&mb, CLASS_CONTEXT, 2, 1, sizeof keylenbuf);
+ put_membuf (&mb, keylenbuf, sizeof keylenbuf);
+
+ if (!(*result_p = get_membuf (&mb, len_p)))
+ err = gpg_error_from_syserror ();
+
+ return err;
+}
+
+
+/* Maximum buffer length of encrypted session key in S-Expressoin for
+ * KEM API. */
+#define ENC_VAL_KEM_MAX 1024
+
+/* For ECC, get the session key by asking gpg-agent with KEM API. */
+static gpg_error_t
+ecc_kem_pkdecrypt (ctrl_t ctrl, const char *hexkeygrip,
+ const char *desc, ksba_const_sexp_t enc_val,
+ char **r_seskey, size_t *r_seskeylen)
+{
+ gcry_error_t err;
+ gcry_sexp_t s_enc_val;
+
+ gcry_mpi_t encrypted_sessionkey_mpi = NULL;
+ gcry_mpi_t ecc_ct_mpi = NULL;
+ gcry_buffer_t encr_algo = { 0, 0, 0, NULL };
+ gcry_buffer_t wrap_algo = { 0, 0, 0, NULL };
+ gcry_buffer_t ukm = { 0, 0, 0, NULL };
+
+ int keywrap_cipher_algo, kdf_hash_algo;
+ size_t kek_len;
+
+ char *wrap_algo_str;
+
+ unsigned char *kdf_params = NULL;
+ size_t kdf_params_len;
+
+ gcry_sexp_t s_enc_val_kem;
+ unsigned char enc_val_kem[ENC_VAL_KEM_MAX];
+ size_t enc_val_kem_len;
+
+ /*
+ * Firstly, from ENC_VAL, extract hash algo for KDF, key
+ * wrap cipher algo, cipher text, wrapped session key, and
+ * optional user key material (UKM).
+ */
+ err = gcry_sexp_sscan (&s_enc_val, NULL, enc_val,
+ gcry_sexp_canon_len (enc_val, 0, NULL, NULL));
+ if (err)
+ goto leave;
+
+ err = gcry_sexp_extract_param (s_enc_val, "enc-val",
+ "/es&'encr-algo''wrap-algo''ukm'?",
+ &ecc_ct_mpi, &encrypted_sessionkey_mpi,
+ &encr_algo, &wrap_algo, &ukm, NULL);
+ gcry_sexp_release (s_enc_val);
+ if (err)
+ {
+ if (opt.verbose)
+ log_info ("%s: extracting parameters failed\n", __func__);
+ goto leave;
+ }
+
+ if (!encr_algo.data || !wrap_algo.data)
+ {
+ if (opt.verbose)
+ log_info ("%s: the KDF parameters are required\n", __func__);
+ err = gpg_error (GPG_ERR_INV_DATA);
+ goto leave;
+ }
+
+ kdf_hash_algo = determine_hashalgo (encr_algo.data, encr_algo.size);
+ if (kdf_hash_algo < 0)
+ {
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ goto leave;
+ }
+
+ keywrap_cipher_algo = determine_wrap_cipher (wrap_algo.data,
+ wrap_algo.size);
+ if (keywrap_cipher_algo < 0)
+ {
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ goto leave;
+ }
+
+ kek_len = gcry_cipher_get_algo_keylen (keywrap_cipher_algo);
+ if (kek_len == 0)
+ {
+ err = gpg_error (GPG_ERR_INV_DATA);
+ goto leave;
+ }
+
+ wrap_algo_str = string_from_gcry_buffer (&wrap_algo);
+ if (!wrap_algo_str)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ /* Secondly, compose the KDF parameters. */
+ err = build_shared_info (kek_len, wrap_algo_str, ukm.data, ukm.size,
+ &kdf_params, &kdf_params_len);
+ xfree (wrap_algo_str);
+ if (err)
+ goto leave;
+
+ /* Thirdly, build the S-EXP for the request to the gpg-agent. */
+ err = gcry_sexp_build (&s_enc_val_kem, NULL,
+ "(enc-val(ecc(c%d)(h%d)(e%m)(s%m)(kdf-params%b)))",
+ keywrap_cipher_algo, kdf_hash_algo,
+ ecc_ct_mpi, encrypted_sessionkey_mpi,
+ (int)kdf_params_len, kdf_params);
+ if (err)
+ goto leave;
+
+ enc_val_kem_len = gcry_sexp_sprint (s_enc_val_kem, GCRYSEXP_FMT_CANON,
+ enc_val_kem, ENC_VAL_KEM_MAX);
+ gcry_sexp_release (s_enc_val_kem);
+ if (!enc_val_kem_len)
+ {
+ err = gpg_error (GPG_ERR_TOO_LARGE);
+ goto leave;
+ }
+
+ /* Ask gpg-agent to decrypt the wrapped session key with KEM API. */
+ err = gpgsm_agent_pkdecrypt (ctrl, hexkeygrip, desc, 1, enc_val_kem,
+ r_seskey, r_seskeylen);
+ leave:
+ xfree (kdf_params);
+ gcry_free (encr_algo.data);
+ gcry_free (wrap_algo.data);
+ gcry_free (ukm.data);
+ gcry_mpi_release (ecc_ct_mpi);
+ gcry_mpi_release (encrypted_sessionkey_mpi);
+ return err;
+}
+
/* Decrypt the session key and fill in the parm structure. The
algo and the IV is expected to be already in PARM. */
static int
prepare_decryption (ctrl_t ctrl, const char *hexkeygrip,
- int pk_algo, unsigned int nbits, const char *desc,
+ int pk_algo, const char *desc,
ksba_const_sexp_t enc_val,
struct decrypt_filter_parm_s *parm)
{
@@ -802,8 +826,13 @@ prepare_decryption (ctrl_t ctrl, const char *hexkeygrip,
}
else
{
- rc = gpgsm_agent_pkdecrypt (ctrl, hexkeygrip, desc, enc_val,
- &seskey, &seskeylen);
+ if (pk_algo == GCRY_PK_ECC)
+ rc = ecc_kem_pkdecrypt (ctrl, hexkeygrip, desc, enc_val,
+ &seskey, &seskeylen);
+ else
+ rc = gpgsm_agent_pkdecrypt (ctrl, hexkeygrip, desc, 0, enc_val,
+ &seskey, &seskeylen);
+
if (rc)
{
log_error ("error decrypting session key: %s\n", gpg_strerror (rc));
@@ -816,23 +845,8 @@ prepare_decryption (ctrl_t ctrl, const char *hexkeygrip,
n = 0;
if (pk_algo == GCRY_PK_ECC)
{
- gcry_sexp_t s_enc_val;
- unsigned char *decrypted;
- unsigned int decryptedlen;
-
- rc = gcry_sexp_sscan (&s_enc_val, NULL, enc_val,
- gcry_sexp_canon_len (enc_val, 0, NULL, NULL));
- if (rc)
- goto leave;
-
- rc = ecdh_decrypt (seskey, seskeylen, nbits, s_enc_val,
- &decrypted, &decryptedlen);
- gcry_sexp_release (s_enc_val);
- if (rc)
- goto leave;
- xfree (seskey);
- seskey = decrypted;
- seskeylen = decryptedlen;
+ /* When it's ECC, no more checks are needed. It's done in
+ * the agent side. */
}
else if (seskeylen == 32 || seskeylen == 24 || seskeylen == 16)
{
@@ -1356,7 +1370,7 @@ gpgsm_decrypt (ctrl_t ctrl, estream_t in_fp, estream_t out_fp)
if (maybe_pwri && opt.verbose)
log_info ("recp %d - KEKRI or PWRI\n", recp);
- rc = prepare_decryption (ctrl, hexkeygrip, pk_algo, nbits,
+ rc = prepare_decryption (ctrl, hexkeygrip, pk_algo,
desc, enc_val, &dfparm);
xfree (enc_val);
if (rc)