sm: Allow decryption using dhSinglePass-stdDH-sha1kdf-scheme.

* sm/decrypt.c (ecdh_decrypt): Support
dhSinglePass-stdDH-sha1kdf-scheme.  Factor key derive code out to ...
(ecdh_derive_kek): new global function.  Allow for hashs shorter than
the key.
(hash_ecc_cms_shared_info): Make file-only.
* sm/encrypt.c (ecdh_encrypt): Replace derive code by a call to the
new ecdh_derive_kek.  Add test code to create data using
dhSinglePass-stdDH-sha1kdf-scheme.
* sm/gpgsm.h (opt): Add member force_ecdh_sha1kdf.
* sm/gpgsm.c: Add option --debug-force-ecdh-sha1kdf.
--

I did some test against

  Governikus_Signer:       2.9.2.0
  MCard security Provider: 2.4.0
  Algorithm catalog from:  05.12.2019

using a Signature Card v2.0 and a nistp256 certificate.  Encrypting
with Governikus used the stdDH-sha1kdf scheme which we can now
decrypt.  Encrypting with GPGSM uses for that curve the recommended
scheme sha256kdf but Governikus was not able to decrypt this (no
usable error message).  Encrypting using stdDH-sha1kdf with GPGSM by
using the new --debug-force-ecdh-sha1kdf option showed that
Governikus was able to decrypt this.

FWIW: RFC5753 (Use of Elliptic Curve Cryptography (ECC) Algorithms)
has this requirement:

   Implementations that support EnvelopedData with the
   ephemeral-static ECDH standard primitive:

   - MUST support the dhSinglePass-stdDH-sha256kdf-scheme key
     agreement algorithm, the id-aes128-wrap key wrap algorithm, and
     the id-aes128-cbc content encryption algorithm; and

which Governikus seems not to fulfill.

GnuPG-bug-id: 4098
Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2020-05-08 18:08:50 +02:00
parent 439c9b5cb5
commit 68b857df13
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
4 changed files with 92 additions and 67 deletions

View File

@ -78,7 +78,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. */
gpg_error_t
static 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)
@ -135,6 +135,63 @@ hash_ecc_cms_shared_info (gcry_md_hd_t hash_hd, const char *wrap_algo_str,
}
/* Derive a KEK (key wrapping key) using (SECRET,SECRETLEN), an
* optional (UKM,ULMLEN), the wrap algorithm WRAP_ALGO_STR in decimal
* dotted form, and the hash algorithm HASH_ALGO. On success a key of
* length KEYLEN is stored at KEY. */
gpg_error_t
ecdh_derive_kek (unsigned char *key, unsigned int keylen,
int hash_algo, const char *wrap_algo_str,
const void *secret, unsigned int secretlen,
const void *ukm, unsigned int ukmlen)
{
gpg_error_t err = 0;
unsigned int hashlen;
gcry_md_hd_t hash_hd;
unsigned char counter;
unsigned int n, ncopy;
hashlen = gcry_md_get_algo_dlen (hash_algo);
if (!hashlen)
return gpg_error (GPG_ERR_INV_ARG);
err = gcry_md_open (&hash_hd, hash_algo, 0);
if (err)
return err;
/* 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.
*/
for (n=0, counter=1; n < keylen; counter++)
{
if (counter > 1)
gcry_md_reset (hash_hd);
gcry_md_write (hash_hd, secret, secretlen);
gcry_md_write (hash_hd, "\x00\x00\x00", 3); /* MSBs of counter */
gcry_md_write (hash_hd, &counter, 1);
err = hash_ecc_cms_shared_info (hash_hd, wrap_algo_str, keylen,
ukm, ukmlen);
if (err)
break;
gcry_md_final (hash_hd);
if (n + hashlen > keylen)
ncopy = keylen - n;
else
ncopy = hashlen;
memcpy (key+n, gcry_md_read (hash_hd, 0), ncopy);
n += ncopy;
}
gcry_md_close (hash_hd);
return err;
}
/* This function will modify SECRET. NBITS is the size of the curve
* which which we took from the certificate. */
static gpg_error_t
@ -151,7 +208,7 @@ ecdh_decrypt (unsigned char *secret, size_t secretlen,
unsigned int ukmlen;
const unsigned char *data; /* Alias for ioarray[3]. */
unsigned int datalen;
unsigned int keylen, hashlen;
unsigned int keylen;
unsigned char key[32];
gcry_cipher_hd_t cipher_hd = NULL;
unsigned char *result = NULL;
@ -234,19 +291,21 @@ ecdh_decrypt (unsigned char *secret, size_t secretlen,
{
/* dhSinglePass-stdDH-sha256kdf-scheme */
hash_algo = GCRY_MD_SHA256;
hashlen = 32;
}
else if (!strcmp (encr_algo_str, "1.3.132.1.11.2"))
{
/* dhSinglePass-stdDH-sha384kdf-scheme */
hash_algo = GCRY_MD_SHA384;
hashlen = 48;
}
else if (!strcmp (encr_algo_str, "1.3.132.1.11.3"))
{
/* dhSinglePass-stdDH-sha512kdf-scheme */
hash_algo = GCRY_MD_SHA512;
hashlen = 64;
}
else if (!strcmp (encr_algo_str, "1.3.133.16.840.63.0.2"))
{
/* dhSinglePass-stdDH-sha1kdf-scheme */
hash_algo = GCRY_MD_SHA1;
}
else
{
@ -275,33 +334,10 @@ ecdh_decrypt (unsigned char *secret, size_t secretlen,
goto leave;
}
/* 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,
ukm, ukmlen);
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;
}
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 .....:");

View File

@ -185,7 +185,7 @@ ecdh_encrypt (DEK dek, gcry_sexp_t s_pkey, gcry_sexp_t *r_encval)
const char *encr_algo_str;
const char *wrap_algo_str;
int hash_algo, cipher_algo;
unsigned int keylen, hashlen;
unsigned int keylen;
unsigned char key[32];
gcry_sexp_t s_data = NULL;
gcry_sexp_t s_encr = NULL;
@ -230,20 +230,28 @@ ecdh_encrypt (DEK dek, gcry_sexp_t s_pkey, gcry_sexp_t *r_encval)
curvebuf = NULL;
/* Our mapping matches the recommended algorithms from RFC-5753 but
* not supporing the short curves which would require 3DES. */
* not supporting 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 (opt.force_ecdh_sha1kdf)
{
/* dhSinglePass-stdDH-sha1kdf-scheme */
encr_algo_str = "1.3.133.16.840.63.0.2";
wrap_algo_str = "2.16.840.1.101.3.4.1.45";
hash_algo = GCRY_MD_SHA1;
cipher_algo = GCRY_CIPHER_AES256;
keylen = 32;
}
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;
}
@ -253,7 +261,6 @@ ecdh_encrypt (DEK dek, gcry_sexp_t s_pkey, gcry_sexp_t *r_encval)
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;
}
@ -263,7 +270,6 @@ ecdh_encrypt (DEK dek, gcry_sexp_t s_pkey, gcry_sexp_t *r_encval)
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;
}
@ -338,32 +344,10 @@ ecdh_encrypt (DEK dek, gcry_sexp_t s_pkey, gcry_sexp_t *r_encval)
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;
}
err = ecdh_derive_kek (key, keylen, hash_algo, wrap_algo_str,
secret, secretlen, NULL, 0);
if (err)
goto leave;
if (DBG_CRYPTO)
log_printhex (key, keylen, "KEK .....:");

View File

@ -106,6 +106,7 @@ enum cmd_and_opt_values {
oDebugAllowCoreDump,
oDebugNoChainValidation,
oDebugIgnoreExpiration,
oDebugForceECDHSHA1KDF,
oLogFile,
oNoLogFile,
oAuditLog,
@ -270,6 +271,7 @@ static gpgrt_opt_t opts[] = {
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugNoChainValidation, "debug-no-chain-validation", "@"),
ARGPARSE_s_n (oDebugIgnoreExpiration, "debug-ignore-expiration", "@"),
ARGPARSE_s_n (oDebugForceECDHSHA1KDF, "debug-force-ecdh-sha1kdf", "@"),
ARGPARSE_s_s (oLogFile, "log-file",
N_("|FILE|write server mode logs to FILE")),
ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"),
@ -1355,6 +1357,7 @@ main ( int argc, char **argv)
break;
case oDebugNoChainValidation: opt.no_chain_validation = 1; break;
case oDebugIgnoreExpiration: opt.ignore_expiration = 1; break;
case oDebugForceECDHSHA1KDF: opt.force_ecdh_sha1kdf = 1; break;
case oStatusFD:
ctrl.status_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 1);

View File

@ -106,6 +106,8 @@ struct
int forced_digest_algo; /* User forced hash algorithm. */
int force_ecdh_sha1kdf; /* Only for debugging and testing. */
char *def_recipient; /* userID of the default recipient */
int def_recipient_self; /* The default recipient is the default key */
@ -392,10 +394,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);
gpg_error_t ecdh_derive_kek (unsigned char *key, unsigned int keylen,
int hash_algo, const char *wrap_algo_str,
const void *secret, unsigned int secretlen,
const void *ukm, unsigned int ukmlen);
int gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp);
/*-- certreqgen.c --*/