1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-06-06 23:17:47 +02:00
gnupg/g10/ecdh.c
Werner Koch 90b0ff23b7 Editorial changes and allow building with old libgcrypts.
Changed order of some conditional to make to put the special case into
the true branch.  Indentation changes.  Minor other changes to make the
ECC code more similar to the rest of our code.

It builds but many sefltests still fail.  Need to fix that before
using it with an ECDH enabled libgcrypt.

[/]
2011-01-21  Werner Koch  <wk@g10code.com>

	* configure.ac: Need Libgcrypt 1.4.6 due to AESWRAP.
	(HAVE_GCRY_PK_ECDH): Add new test.

[agent/]
2011-01-21  Werner Koch  <wk@g10code.com>

	* cvt-openpgp.c (GCRY_PK_ECDH) [!HAVE_GCRY_PK_ECDH]: New.

[include/]
2011-01-21  Werner Koch  <wk@g10code.com>

	* cipher.h (GCRY_PK_USAGE_CERT): Remove compatibility macros
	because we now require libgcrypt 1.4.6.
	(GCRY_PK_ECDH): Add replacement.
2011-01-21 12:00:57 +01:00

535 lines
15 KiB
C

/* ecdh.c - ECDH public key operations used in public key glue code
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "gpg.h"
#include "util.h"
#include "pkglue.h"
#include "main.h"
#include "options.h"
gcry_mpi_t
pk_ecdh_default_params_to_mpi (int qbits)
{
gpg_error_t err;
gcry_mpi_t result;
/* Defaults are the strongest possible choices. Performance is not
an issue here, only interoperability. */
byte kek_params[4] = {
3 /*size of following field*/,
1 /*fixed version for KDF+AESWRAP*/,
DIGEST_ALGO_SHA512 /* KEK MD */,
CIPHER_ALGO_AES256 /*KEK AESWRAP alg*/
};
int i;
static const struct {
int qbits;
int openpgp_hash_id;
int openpgp_cipher_id;
} kek_params_table[] = {
{ 256, DIGEST_ALGO_SHA256, CIPHER_ALGO_AES },
{ 384, DIGEST_ALGO_SHA384, CIPHER_ALGO_AES256 },
/* Note: 528 is 521 rounded to the 8 bit boundary */
{ 528, DIGEST_ALGO_SHA512, CIPHER_ALGO_AES256 }
};
for (i=0; i<sizeof(kek_params_table)/sizeof(kek_params_table[0]); i++)
{
if (kek_params_table[i].qbits >= qbits)
{
kek_params[2] = kek_params_table[i].openpgp_hash_id;
kek_params[3] = kek_params_table[i].openpgp_cipher_id;
break;
}
}
if (DBG_CIPHER)
log_printhex ("ecdh kek params are", kek_params, sizeof(kek_params) );
err = gcry_mpi_scan (&result, GCRYMPI_FMT_USG,
kek_params, sizeof(kek_params), NULL);
if (err)
log_fatal ("mpi_scan failed: %s\n", gpg_strerror (err));
return result;
}
/* Returns allocated (binary) KEK parameters; the size is returned in
* sizeout. The caller must free the returned value with xfree.
* Returns NULL on error.
*/
byte *
pk_ecdh_default_params (int qbits, size_t *sizeout)
{
/* Defaults are the strongest possible choices. Performance is not
an issue here, only interoperability. */
byte kek_params[4] = {
3 /*size of following field*/,
1 /*fixed version for KDF+AESWRAP*/,
DIGEST_ALGO_SHA512 /* KEK MD */,
CIPHER_ALGO_AES256 /* KEK AESWRAP alg */
};
int i;
static const struct {
int qbits;
int openpgp_hash_id;
int openpgp_cipher_id;
} kek_params_table[] = {
{ 256, DIGEST_ALGO_SHA256, CIPHER_ALGO_AES },
{ 384, DIGEST_ALGO_SHA384, CIPHER_ALGO_AES256 },
/* Note: 528 is 521 rounded to the 8 bit boundary */
{ 528, DIGEST_ALGO_SHA512, CIPHER_ALGO_AES256 }
};
byte *p;
*sizeout = 0;
for (i=0; i<sizeof(kek_params_table)/sizeof(kek_params_table[0]); i++)
{
if (kek_params_table[i].qbits >= qbits)
{
kek_params[2] = kek_params_table[i].openpgp_hash_id;
kek_params[3] = kek_params_table[i].openpgp_cipher_id;
break;
}
}
if (DBG_CIPHER )
log_printhex ("ecdh kek params are", kek_params, sizeof(kek_params));
p = xtrymalloc (sizeof(kek_params));
if (!p)
return NULL;
memcpy (p, kek_params, sizeof(kek_params));
*sizeout = sizeof(kek_params);
return p;
}
/* Encrypts/decrypts 'data' with a key derived from shared_mpi ECC
* point using FIPS SP 800-56A compliant method, which is key
* derivation + key wrapping. The direction is determined by the first
* parameter (is_encrypt=1 --> this is encryption). The result is
* returned in out as a size+value MPI.
*
* TODO: memory leaks (x_secret).
*/
static int
pk_ecdh_encrypt_with_shared_point (int is_encrypt, gcry_mpi_t shared_mpi,
const byte pk_fp[MAX_FINGERPRINT_LEN],
gcry_mpi_t data, gcry_mpi_t *pkey,
gcry_mpi_t *out)
{
byte *secret_x;
int secret_x_size;
byte kdf_params[256];
int kdf_params_size=0;
int nbits;
int kdf_hash_algo;
int kdf_encr_algo;
int rc;
*out = NULL;
nbits = pubkey_nbits( PUBKEY_ALGO_ECDH, pkey );
{
size_t nbytes;
/* Extract x component of the shared point: this is the actual
shared secret */
nbytes = (mpi_get_nbits (pkey[1] /* public point */)+7)/8;
secret_x = xmalloc_secure( nbytes );
rc = gcry_mpi_print (GCRYMPI_FMT_USG, secret_x, nbytes,
&nbytes, shared_mpi);
if (rc)
{
xfree (secret_x);
log_error ("ec ephemeral export of shared point failed: %s\n",
gpg_strerror (rc));
return rc;
}
secret_x_size = (nbits+7)/8;
assert (nbytes > secret_x_size);
memmove (secret_x, secret_x+1, secret_x_size);
memset (secret_x+secret_x_size, 0, nbytes-secret_x_size);
if (DBG_CIPHER)
log_printhex ("ecdh shared secret X is:", secret_x, secret_x_size );
}
/*** We have now the shared secret bytes in secret_x. ***/
/* At this point we are done with PK encryption and the rest of the
* function uses symmetric key encryption techniques to protect the
* input 'data'. The following two sections will simply replace
* current secret_x with a value derived from it. This will become
* a KEK.
*/
{
IOBUF obuf = iobuf_temp();
rc = iobuf_write_size_body_mpi ( obuf, pkey[2] ); /* KEK params */
kdf_params_size = iobuf_temp_to_buffer (obuf,
kdf_params, sizeof(kdf_params));
if (DBG_CIPHER)
log_printhex ("ecdh KDF public key params are:",
kdf_params, kdf_params_size );
/* Expect 4 bytes 03 01 hash_alg symm_alg. */
if (kdf_params_size != 4 || kdf_params[0] != 3 || kdf_params[1] != 1)
return GPG_ERR_BAD_PUBKEY;
kdf_hash_algo = kdf_params[2];
kdf_encr_algo = kdf_params[3];
if (DBG_CIPHER)
log_debug ("ecdh KDF algorithms %s+%s with aeswrap\n",
gcry_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)
return GPG_ERR_BAD_PUBKEY;
if (kdf_encr_algo != GCRY_CIPHER_AES128
&& kdf_encr_algo != GCRY_CIPHER_AES192
&& kdf_encr_algo != GCRY_CIPHER_AES256)
return GPG_ERR_BAD_PUBKEY;
}
/* Build kdf_params. */
{
IOBUF obuf;
obuf = iobuf_temp();
/* variable-length field 1, curve name OID */
rc = iobuf_write_size_body_mpi ( obuf, pkey[0] );
/* fixed-length field 2 */
iobuf_put (obuf, PUBKEY_ALGO_ECDH);
/* variable-length field 3, KDF params */
rc = (rc ? rc : iobuf_write_size_body_mpi ( obuf, pkey[2] ));
/* fixed-length field 4 */
iobuf_write (obuf, "Anonymous Sender ", 20);
/* fixed-length field 5, recipient fp */
iobuf_write (obuf, pk_fp, 20);
kdf_params_size = iobuf_temp_to_buffer (obuf,
kdf_params, sizeof(kdf_params));
iobuf_close (obuf);
if (rc)
return rc;
if(DBG_CIPHER)
log_printhex ("ecdh KDF message params are:",
kdf_params, kdf_params_size );
}
/* Derive a KEK (key wrapping key) using kdf_params and secret_x. */
{
gcry_md_hd_t h;
int old_size;
rc = gcry_md_open (&h, kdf_hash_algo, 0);
if(rc)
log_bug ("gcry_md_open failed for algo %d: %s",
kdf_hash_algo, gpg_strerror (gcry_error(rc)));
gcry_md_write(h, "\x00\x00\x00\x01", 4); /* counter = 1 */
gcry_md_write(h, secret_x, secret_x_size); /* x of the point X */
gcry_md_write(h, kdf_params, kdf_params_size); /* KDF parameters */
gcry_md_final (h);
assert( gcry_md_get_algo_dlen (kdf_hash_algo) >= 32 );
memcpy (secret_x, gcry_md_read (h, kdf_hash_algo),
gcry_md_get_algo_dlen (kdf_hash_algo));
gcry_md_close (h);
old_size = secret_x_size;
assert( old_size >= gcry_cipher_get_algo_keylen( kdf_encr_algo ) );
secret_x_size = gcry_cipher_get_algo_keylen( kdf_encr_algo );
assert( secret_x_size <= gcry_md_get_algo_dlen (kdf_hash_algo) );
/* We could have allocated more, so clean the tail before returning. */
memset( secret_x+secret_x_size, old_size-secret_x_size, 0 );
if (DBG_CIPHER)
log_printhex ("ecdh KEK is:", secret_x, secret_x_size );
}
/* And, finally, aeswrap with key secret_x. */
{
gcry_cipher_hd_t hd;
size_t nbytes;
byte *data_buf;
int data_buf_size;
gcry_mpi_t result;
rc = gcry_cipher_open (&hd, kdf_encr_algo, GCRY_CIPHER_MODE_AESWRAP, 0);
if (rc)
{
log_error ("ecdh failed to initialize AESWRAP: %s\n",
gpg_strerror (rc));
return rc;
}
rc = gcry_cipher_setkey (hd, secret_x, secret_x_size);
xfree( secret_x );
if (rc)
{
gcry_cipher_close (hd);
log_error ("ecdh failed in gcry_cipher_setkey: %s\n",
gpg_strerror (rc));
return rc;
}
data_buf_size = (gcry_mpi_get_nbits(data)+7)/8;
assert ((data_buf_size & 7) == (is_encrypt ? 0 : 1));
data_buf = xtrymalloc_secure( 1 + 2*data_buf_size + 8);
if (!data_buf)
{
gcry_cipher_close (hd);
return GPG_ERR_ENOMEM;
}
if (is_encrypt)
{
byte *in = data_buf+1+data_buf_size+8;
/* Write data MPI into the end of data_buf. data_buf is size
aeswrap data. */
rc = gcry_mpi_print (GCRYMPI_FMT_USG, in,
data_buf_size, &nbytes, data/*in*/);
if (rc)
{
log_error ("ecdh failed to export DEK: %s\n", gpg_strerror (rc));
gcry_cipher_close (hd);
xfree (data_buf);
return rc;
}
if (DBG_CIPHER)
log_printhex ("ecdh encrypting :", in, data_buf_size );
rc = 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 (rc)
{
log_error ("ecdh failed in gcry_cipher_encrypt: %s\n",
gpg_strerror (rc));
xfree (data_buf);
return rc;
}
data_buf[0] = data_buf_size+8;
if (DBG_CIPHER)
log_printhex ("ecdh encrypted to:", data_buf+1, data_buf[0] );
rc = gcry_mpi_scan (&result, GCRYMPI_FMT_USG,
data_buf, 1+data_buf[0], NULL);
/* (byte)size + aeswrap of DEK */
xfree( data_buf );
if (rc)
{
log_error ("ecdh failed to create an MPI: %s\n", gpg_strerror (rc));
return rc;
}
*out = result;
}
else
{
byte *in;
rc = gcry_mpi_print (GCRYMPI_FMT_USG, data_buf, data_buf_size,
&nbytes, data/*in*/);
if (nbytes != data_buf_size || data_buf[0] != data_buf_size-1)
{
log_error ("ecdh inconsistent size\n");
xfree (data_buf);
return GPG_ERR_BAD_MPI;
}
in = data_buf+data_buf_size;
data_buf_size = data_buf[0];
if (DBG_CIPHER)
log_printhex ("ecdh decrypting :", data_buf+1, data_buf_size);
rc = gcry_cipher_decrypt (hd, in, data_buf_size, data_buf+1,
data_buf_size);
gcry_cipher_close (hd);
if (rc)
{
log_error ("ecdh failed in gcry_cipher_decrypt: %s\n",
gpg_strerror (rc));
xfree (data_buf);
return rc;
}
data_buf_size -= 8;
if (DBG_CIPHER)
log_printhex ("ecdh decrypted to :", in, data_buf_size);
/* Padding is removed later. */
/* if (in[data_buf_size-1] > 8 ) */
/* { */
/* log_error("ecdh failed at decryption: invalid padding. %02x > 8\n", */
/* in[data_buf_size-1] ); */
/* return GPG_ERR_BAD_KEY; */
/* } */
rc = gcry_mpi_scan ( &result, GCRYMPI_FMT_USG, in, data_buf_size, NULL);
xfree (data_buf);
if (rc)
{
log_error ("ecdh failed to create a plain text MPI: %s\n",
gpg_strerror (rc));
return rc;
}
*out = result;
}
}
return rc;
}
static gcry_mpi_t
gen_k (unsigned nbits)
{
gcry_mpi_t k;
k = gcry_mpi_snew (nbits);
if (DBG_CIPHER)
log_debug ("choosing a random k of %u bits\n", nbits);
gcry_mpi_randomize (k, nbits-1, GCRY_STRONG_RANDOM);
if (DBG_CIPHER)
{
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;
}
/* Perform ECDH encryption, which involves ECDH key generation. */
int
pk_ecdh_encrypt (gcry_mpi_t *resarr, const byte pk_fp[MAX_FINGERPRINT_LEN],
gcry_mpi_t data, gcry_mpi_t * pkey)
{
gcry_sexp_t s_ciph, s_data, s_pkey;
int nbits;
int rc;
gcry_mpi_t k;
nbits = pubkey_nbits (PUBKEY_ALGO_ECDH, pkey);
/*** Generate an ephemeral key, actually, a scalar. ***/
k = gen_k (nbits);
if( k == NULL )
BUG ();
/*** Done with ephemeral key generation.
* Now use ephemeral secret to get the shared secret. ***/
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(ecdh(c%m)(q%m)(p%m)))",
pkey[0], pkey[1], pkey[2]);
if (rc)
BUG ();
/* Put the data into a simple list. */
/* Ephemeral scalar goes as data. */
if (gcry_sexp_build (&s_data, NULL, "%m", k))
BUG ();
/* 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 (rc)
return rc;
/* Finally, perform encryption. */
{
/* ... and get the shared point/ */
gcry_mpi_t shared;
shared = mpi_from_sexp (s_ciph, "a");
gcry_sexp_release (s_ciph);
/* Ephemeral public key. */
resarr[0] = mpi_from_sexp (s_ciph, "b");
if (DBG_CIPHER)
{
unsigned char *buffer;
if (gcry_mpi_aprint (GCRYMPI_FMT_HEX, &buffer, NULL, resarr[0]))
BUG ();
log_debug("ephemeral key MPI: %s\n", buffer);
gcry_free( buffer );
}
rc = pk_ecdh_encrypt_with_shared_point (1 /*=encrypton*/, shared,
pk_fp, data, pkey, resarr+1);
mpi_release (shared);
}
return rc;
}
/* Perform ECDH decryption. */
int
pk_ecdh_decrypt (gcry_mpi_t * result, const byte sk_fp[MAX_FINGERPRINT_LEN],
gcry_mpi_t data, gcry_mpi_t shared, gcry_mpi_t * skey)
{
if (!data)
return gpg_error (GPG_ERR_BAD_MPI);
return pk_ecdh_encrypt_with_shared_point (0 /*=decryption*/, shared,
sk_fp, data/*encr data as an MPI*/,
skey, result);
}