diff options
| -rw-r--r-- | sm/call-agent.c | 4 | ||||
| -rw-r--r-- | sm/decrypt.c | 474 | ||||
| -rw-r--r-- | sm/gpgsm.h | 2 |
3 files changed, 247 insertions, 233 deletions
diff --git a/sm/call-agent.c b/sm/call-agent.c index 16fb10901..370cd8e30 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -554,7 +554,7 @@ inq_ciphertext_cb (void *opaque, const char *line) the hex string KEYGRIP. */ int gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, - ksba_const_sexp_t ciphertext, + int use_kem, ksba_const_sexp_t ciphertext, char **r_buf, size_t *r_buflen ) { int rc; @@ -601,7 +601,7 @@ gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, cipher_parm.ctx = agent_ctx; cipher_parm.ciphertext = ciphertext; cipher_parm.ciphertextlen = ciphertextlen; - rc = assuan_transact (agent_ctx, "PKDECRYPT", + rc = assuan_transact (agent_ctx, use_kem? "PKDECRYPT --kem=CMS": "PKDECRYPT", put_membuf_cb, &data, inq_ciphertext_cb, &cipher_parm, NULL, NULL); if (rc) 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) diff --git a/sm/gpgsm.h b/sm/gpgsm.h index cc049d05b..5f4f9a24a 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -536,7 +536,7 @@ int gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, unsigned char **r_buf, size_t *r_buflen); int gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, - ksba_const_sexp_t ciphertext, + int use_kem, ksba_const_sexp_t ciphertext, char **r_buf, size_t *r_buflen); gpg_error_t gpgsm_agent_genkey (ctrl_t ctrl, int no_protection, ksba_const_sexp_t keyparms, |
