[PATCH] scd: writekey support of ECC
NIIBE Yutaka
gniibe at fsij.org
Mon Feb 24 05:21:00 CET 2014
Hello,
Last year, I lost the opportunity to merge my work of ECC smartcard
support (specifically, ECDSA). IIRC, we didn't have any agreements on
ECDH. I implemented ECDSA (with NIST P-256) on Gnuk around March
2013, and submitted partial change of GnuPG. Perhaps, it was just the
code enough to use with OpenSSH, and I forgot to send writekey change.
Here are changes. It will by by two commits.
(1) Support of secp256k1 curve.
(2) Enhance do_writekey to support ECC keys. Now, we have two
routines: rsa_writekey and ecdsa_writekey. ecdh_writekey is
not yet supported.
Built successfully and tested with development version of Gnuk.
diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c
index 3d7136f..23947fc 100644
--- a/scd/app-openpgp.c
+++ b/scd/app-openpgp.c
@@ -45,6 +45,7 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
+#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <time.h>
@@ -143,7 +144,9 @@ enum
{
CURVE_NIST_P256,
CURVE_NIST_P384,
- CURVE_NIST_P521
+ CURVE_NIST_P521,
+ CURVE_SEC_P256K1,
+ CURVE_UNKOWN,
};
@@ -735,24 +738,56 @@ parse_login_data (app_t app)
xfree (relptr);
}
+
+static unsigned char
+get_algo_byte (key_type_t key_type)
+{
+ if (key_type == KEY_TYPE_ECDSA)
+ return 19;
+ else if (key_type == KEY_TYPE_ECDH)
+ return 18;
+ else
+ return 1; /* RSA */
+}
+
/* Note, that FPR must be at least 20 bytes. */
static gpg_error_t
store_fpr (app_t app, int keynumber, u32 timestamp,
- const unsigned char *m, size_t mlen,
- const unsigned char *e, size_t elen,
- unsigned char *fpr, unsigned int card_version)
+ unsigned char *fpr, unsigned int card_version,
+ key_type_t key_type,
+ ...)
{
unsigned int n, nbits;
unsigned char *buffer, *p;
int tag, tag2;
int rc;
+ const unsigned char *m;
+ size_t mlen;
+ va_list ap;
+ int argc;
+ int i;
- for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */
- ;
- for (; elen && !*e; elen--, e++) /* strip leading zeroes */
- ;
+ n = 6; /* key packet version, 4-byte timestamps, and algorithm */
+ if (key_type == KEY_TYPE_RSA || key_type == KEY_TYPE_ECDSA)
+ argc = 2;
+ else if (key_type == KEY_TYPE_ECDH)
+ argc = 3;
+ else
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+ va_start (ap, key_type);
+ for (i = 0; i < argc; i++)
+ {
+ m = va_arg (ap, const unsigned char *);
+ mlen = va_arg (ap, size_t);
+ for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */
+ ;
+ if (key_type == KEY_TYPE_RSA || i == 1)
+ n += 2;
+ n += mlen;
+ }
+ va_end (ap);
- n = 6 + 2 + mlen + 2 + elen;
p = buffer = xtrymalloc (3 + n);
if (!buffer)
return gpg_error_from_syserror ();
@@ -765,15 +800,24 @@ store_fpr (app_t app, int keynumber, u32 timestamp,
*p++ = timestamp >> 16;
*p++ = timestamp >> 8;
*p++ = timestamp;
- *p++ = 1; /* RSA */
- nbits = count_bits (m, mlen);
- *p++ = nbits >> 8;
- *p++ = nbits;
- memcpy (p, m, mlen); p += mlen;
- nbits = count_bits (e, elen);
- *p++ = nbits >> 8;
- *p++ = nbits;
- memcpy (p, e, elen); p += elen;
+ *p++ = get_algo_byte (key_type);
+
+ va_start (ap, key_type);
+ for (i = 0; i < argc; i++)
+ {
+ m = va_arg (ap, const unsigned char *);
+ mlen = va_arg (ap, size_t);
+ for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */
+ ;
+ if (key_type == KEY_TYPE_RSA || i == 1)
+ {
+ nbits = count_bits (m, mlen);
+ *p++ = nbits >> 8;
+ *p++ = nbits;
+ }
+ memcpy (p, m, mlen); p += mlen;
+ }
+ va_end (ap);
gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3);
@@ -889,11 +933,16 @@ get_ecc_key_parameters (int curve, int *r_n_bits, const char **r_curve_oid)
*r_n_bits = 384;
*r_curve_oid = "1.3.132.0.34";
}
- else
+ else if (curve == CURVE_NIST_P521)
{
*r_n_bits = 521;
*r_curve_oid = "1.3.132.0.35";
}
+ else
+ {
+ *r_n_bits = 256;
+ *r_curve_oid = "1.3.132.0.10";
+ }
}
static void
@@ -1234,8 +1283,10 @@ get_curve_name (int curve)
return "NIST P-256";
else if (curve == CURVE_NIST_P384)
return "NIST P-384";
- else
+ else if (curve == CURVE_NIST_P521)
return "NIST P-521";
+ else
+ return "secp256k1";
}
@@ -1456,7 +1507,7 @@ get_public_key (app_t app, int keyno)
= get_curve_name (app->app_local->keyattr[keyno].ecdsa.curve);
err = gcry_sexp_build (&s_pkey, NULL,
- "(public-key(ecdsa(curve%s)(q%b)))",
+ "(public-key(ecc(curve%s)(q%b)))",
curve_name, mlen, mbuf);
if (err)
goto leave;
@@ -2500,8 +2551,6 @@ add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
}
-/* Build the private key template as specified in the OpenPGP specs
- v2.0 section 4.3.3.7. */
static gpg_error_t
build_privkey_template (app_t app, int keyno,
const unsigned char *rsa_n, size_t rsa_n_len,
@@ -2648,6 +2697,74 @@ build_privkey_template (app_t app, int keyno,
return 0;
}
+static gpg_error_t
+build_ecdsa_privkey_template (app_t app, int keyno,
+ const unsigned char *ecc_d, size_t ecc_d_len,
+ unsigned char **result, size_t *resultlen)
+{
+ unsigned char privkey[2];
+ size_t privkey_len;
+ unsigned char exthdr[2+2+1];
+ size_t exthdr_len;
+ unsigned char suffix[2+1];
+ size_t suffix_len;
+ unsigned char *tp;
+ size_t datalen;
+ unsigned char *template;
+ size_t template_size;
+
+ *result = NULL;
+ *resultlen = 0;
+
+ /* Build the 7f48 cardholder private key template. */
+ datalen = 0;
+ tp = privkey;
+
+ tp += add_tlv (tp, 0x91, ecc_d_len);
+ datalen += ecc_d_len;
+
+ privkey_len = tp - privkey;
+
+ /* Build the extended header list without the private key template. */
+ tp = exthdr;
+ *tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4;
+ *tp++ = 0;
+ tp += add_tlv (tp, 0x7f48, privkey_len);
+ exthdr_len = tp - exthdr;
+
+ /* Build the 5f48 suffix of the data. */
+ tp = suffix;
+ tp += add_tlv (tp, 0x5f48, datalen);
+ suffix_len = tp - suffix;
+
+ /* Now concatenate everything. */
+ template_size = (1 + 1 /* 0x4d and len. */
+ + exthdr_len
+ + privkey_len
+ + suffix_len
+ + datalen);
+ tp = template = xtrymalloc_secure (template_size);
+ if (!template)
+ return gpg_error_from_syserror ();
+
+ tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen);
+ memcpy (tp, exthdr, exthdr_len);
+ tp += exthdr_len;
+ memcpy (tp, privkey, privkey_len);
+ tp += privkey_len;
+ memcpy (tp, suffix, suffix_len);
+ tp += suffix_len;
+
+ memcpy (tp, ecc_d, ecc_d_len);
+ tp += ecc_d_len;
+
+ assert (tp - template == template_size);
+
+ *result = template;
+ *resultlen = tp - template;
+ return 0;
+}
+
/* Helper for do_writekley to change the size of a key. Not ethat
this deletes the entire key without asking. */
@@ -2749,26 +2866,15 @@ change_keyattr_from_string (app_t app,
}
-/* Handle the WRITEKEY command for OpenPGP. This function expects a
- canonical encoded S-expression with the secret key in KEYDATA and
- its length (for assertions) in KEYDATALEN. KEYID needs to be the
- usual keyid which for OpenPGP is the string "OPENPGP.n" with
- n=1,2,3. Bit 0 of FLAGS indicates whether an existing key shall
- get overwritten. PINCB and PINCB_ARG are the usual arguments for
- the pinentry callback. */
static gpg_error_t
-do_writekey (app_t app, ctrl_t ctrl,
- const char *keyid, unsigned int flags,
- gpg_error_t (*pincb)(void*, const char *, char **),
- void *pincb_arg,
- const unsigned char *keydata, size_t keydatalen)
+rsa_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
+ void *pincb_arg, int keyno,
+ const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
- int force = (flags & 1);
- int keyno;
- const unsigned char *buf, *tok;
- size_t buflen, toklen;
- int depth, last_depth1, last_depth2;
+ const unsigned char *tok;
+ size_t toklen;
+ int last_depth1, last_depth2;
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
const unsigned char *rsa_p = NULL;
@@ -2782,52 +2888,6 @@ do_writekey (app_t app, ctrl_t ctrl,
unsigned char fprbuf[20];
u32 created_at = 0;
- (void)ctrl;
-
- if (!strcmp (keyid, "OPENPGP.1"))
- keyno = 0;
- else if (!strcmp (keyid, "OPENPGP.2"))
- keyno = 1;
- else if (!strcmp (keyid, "OPENPGP.3"))
- keyno = 2;
- else
- return gpg_error (GPG_ERR_INV_ID);
-
- err = does_key_exist (app, keyno, 0, force);
- if (err)
- return err;
-
-
- /*
- Parse the S-expression
- */
- buf = keydata;
- buflen = keydatalen;
- depth = 0;
- if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
- goto leave;
- if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
- goto leave;
- if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen))
- {
- if (!tok)
- ;
- else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen))
- log_info ("protected-private-key passed to writekey\n");
- else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
- log_info ("shadowed-private-key passed to writekey\n");
- err = gpg_error (GPG_ERR_BAD_SECKEY);
- goto leave;
- }
- if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
- goto leave;
- if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
- goto leave;
- if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen))
- {
- err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
- goto leave;
- }
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
@@ -3100,9 +3160,198 @@ do_writekey (app_t app, ctrl_t ctrl,
goto leave;
}
- err = store_fpr (app, keyno, created_at,
- rsa_n, rsa_n_len, rsa_e, rsa_e_len,
- fprbuf, app->card_version);
+ err = store_fpr (app, keyno, created_at, fprbuf, app->card_version,
+ KEY_TYPE_RSA, rsa_n, rsa_n_len, rsa_e, rsa_e_len);
+ if (err)
+ goto leave;
+
+
+ leave:
+ xfree (template);
+ return err;
+}
+
+
+static gpg_error_t
+ecdh_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
+ void *pincb_arg, int keyno,
+ const unsigned char *buf, size_t buflen, int depth)
+{
+ return GPG_ERR_NOT_IMPLEMENTED;
+}
+
+
+static gpg_error_t
+ecdsa_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
+ void *pincb_arg, int keyno,
+ const unsigned char *buf, size_t buflen, int depth)
+{
+ gpg_error_t err;
+ const unsigned char *tok;
+ size_t toklen;
+ int last_depth1, last_depth2;
+ const unsigned char *ecc_q = NULL;
+ const unsigned char *ecc_d = NULL;
+ size_t ecc_q_len, ecc_d_len;
+ unsigned char *template = NULL;
+ size_t template_len;
+ unsigned char fprbuf[20];
+ u32 created_at = 0;
+ int curve = CURVE_UNKOWN;
+
+ /* (private-key(ecdsa(curve%s)(q%m)(d%m))): curve = "1.2.840.10045.3.1.7" */
+ /* (private-key(ecc(curve%s)(q%m)(d%m))): curve = "secp256k1" */
+ last_depth1 = depth;
+ while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
+ && depth && depth >= last_depth1)
+ {
+ if (tok)
+ {
+ err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ goto leave;
+ }
+ if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+ goto leave;
+
+ if (tok && toklen == 5 && !memcmp (tok, "curve", 5))
+ {
+ if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+ goto leave;
+
+ if (tok && toklen == 19 && !memcmp (tok, "1.2.840.10045.3.1.7", 19))
+ curve = CURVE_NIST_P256;
+ else if (tok && toklen == 9 && !memcmp (tok, "secp256k1", 9))
+ curve = CURVE_SEC_P256K1;
+ }
+ else if (tok && toklen == 1)
+ {
+ const unsigned char **mpi;
+ size_t *mpi_len;
+
+ switch (*tok)
+ {
+ case 'q': mpi = &ecc_q; mpi_len = &ecc_q_len; break;
+ case 'd': mpi = &ecc_d; mpi_len = &ecc_d_len; break;
+ default: mpi = NULL; mpi_len = NULL; break;
+ }
+ if (mpi && *mpi)
+ {
+ err = gpg_error (GPG_ERR_DUP_VALUE);
+ goto leave;
+ }
+ if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+ goto leave;
+ if (tok && mpi)
+ {
+ /* Strip off leading zero bytes and save. */
+ for (;toklen && !*tok; toklen--, tok++)
+ ;
+ *mpi = tok;
+ *mpi_len = toklen;
+ }
+ }
+ /* Skip until end of list. */
+ last_depth2 = depth;
+ while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
+ && depth && depth >= last_depth2)
+ ;
+ if (err)
+ goto leave;
+ }
+ /* Parse other attributes. */
+ last_depth1 = depth;
+ while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
+ && depth && depth >= last_depth1)
+ {
+ if (tok)
+ {
+ err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ goto leave;
+ }
+ if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+ goto leave;
+ if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen))
+ {
+ if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen)))
+ goto leave;
+ if (tok)
+ {
+ for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9';
+ tok++, toklen--)
+ created_at = created_at*10 + (*tok - '0');
+ }
+ }
+ /* Skip until end of list. */
+ last_depth2 = depth;
+ while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
+ && depth && depth >= last_depth2)
+ ;
+ if (err)
+ goto leave;
+ }
+
+
+ /* Check that we have all parameters and that they match the card
+ description. */
+ if (!created_at)
+ {
+ log_error (_("creation timestamp missing\n"));
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len);
+
+ /* We need to remove the cached public key. */
+ xfree (app->app_local->pk[keyno].key);
+ app->app_local->pk[keyno].key = NULL;
+ app->app_local->pk[keyno].keylen = 0;
+ app->app_local->pk[keyno].read_done = 0;
+
+ if (app->app_local->extcap.is_v2)
+ {
+ /* Build the private key template as described in section 4.3.3.7 of
+ the OpenPGP card specs version 2.0. */
+ int exmode;
+
+ err = build_ecdsa_privkey_template (app, keyno,
+ ecc_d, ecc_d_len,
+ &template, &template_len);
+ if (err)
+ goto leave;
+
+ /* Prepare for storing the key. */
+ err = verify_chv3 (app, pincb, pincb_arg);
+ if (err)
+ goto leave;
+
+ /* Store the key. */
+ if (app->app_local->cardcap.ext_lc_le && template_len > 254)
+ exmode = 1; /* Use extended length w/o a limit. */
+ else if (app->app_local->cardcap.cmd_chaining && template_len > 254)
+ exmode = -254;
+ else
+ exmode = 0;
+ err = iso7816_put_data_odd (app->slot, exmode, 0x3fff,
+ template, template_len);
+ }
+ else
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+ if (err)
+ {
+ log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ err = store_fpr (app, keyno, created_at, fprbuf, app->card_version,
+ KEY_TYPE_ECDSA,
+ curve == CURVE_NIST_P256?
+ "\x08\x2a\x86\x48\xce\x3d\x03\x01\x07"
+ : "\05\x2b\x81\x04\x00\x0a",
+ curve == CURVE_NIST_P256? 9 : 6,
+ ecc_q, ecc_q_len);
if (err)
goto leave;
@@ -3112,6 +3361,89 @@ do_writekey (app_t app, ctrl_t ctrl,
return err;
}
+/* Handle the WRITEKEY command for OpenPGP. This function expects a
+ canonical encoded S-expression with the secret key in KEYDATA and
+ its length (for assertions) in KEYDATALEN. KEYID needs to be the
+ usual keyid which for OpenPGP is the string "OPENPGP.n" with
+ n=1,2,3. Bit 0 of FLAGS indicates whether an existing key shall
+ get overwritten. PINCB and PINCB_ARG are the usual arguments for
+ the pinentry callback. */
+static gpg_error_t
+do_writekey (app_t app, ctrl_t ctrl,
+ const char *keyid, unsigned int flags,
+ gpg_error_t (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const unsigned char *keydata, size_t keydatalen)
+{
+ gpg_error_t err;
+ int force = (flags & 1);
+ int keyno;
+ const unsigned char *buf, *tok;
+ size_t buflen, toklen;
+ int depth;
+
+ (void)ctrl;
+
+ if (!strcmp (keyid, "OPENPGP.1"))
+ keyno = 0;
+ else if (!strcmp (keyid, "OPENPGP.2"))
+ keyno = 1;
+ else if (!strcmp (keyid, "OPENPGP.3"))
+ keyno = 2;
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ err = does_key_exist (app, keyno, 0, force);
+ if (err)
+ return err;
+
+
+ /*
+ Parse the S-expression
+ */
+ buf = keydata;
+ buflen = keydatalen;
+ depth = 0;
+ if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+ goto leave;
+ if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+ goto leave;
+ if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen))
+ {
+ if (!tok)
+ ;
+ else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen))
+ log_info ("protected-private-key passed to writekey\n");
+ else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
+ log_info ("shadowed-private-key passed to writekey\n");
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ goto leave;
+ }
+ if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+ goto leave;
+ if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+ goto leave;
+ if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0)
+ rsa_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth);
+ else if ((tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0
+ && (keyno == 0 || keyno == 2))
+ || (tok && toklen == 5 && memcmp ("ecdsa", tok, toklen) == 0))
+ ecdsa_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth);
+ else if ((tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0
+ && keyno == 1)
+ || (tok && toklen == 4 && memcmp ("ecdh", tok, toklen) == 0))
+ ecdh_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth);
+ else
+ {
+ err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
+ goto leave;
+ }
+
+ leave:
+ return err;
+}
+
+
/* Handle the GENKEY command. */
static gpg_error_t
@@ -3234,8 +3566,8 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags,
send_status_info (ctrl, "KEY-CREATED-AT",
numbuf, (size_t)strlen(numbuf), NULL, 0);
- rc = store_fpr (app, keyno, (u32)created_at,
- m, mlen, e, elen, fprbuf, app->card_version);
+ rc = store_fpr (app, keyno, (u32)created_at, fprbuf, app->card_version,
+ KEY_TYPE_RSA, m, mlen, e, elen);
if (rc)
goto leave;
send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf);
@@ -3973,8 +4305,10 @@ parse_ecc_curve (const unsigned char *buffer, size_t buflen)
curve = CURVE_NIST_P384;
else if (buflen == 6 && buffer[5] == 0x23)
curve = CURVE_NIST_P521;
- else
+ else if (buflen == 9)
curve = CURVE_NIST_P256;
+ else
+ curve = CURVE_SEC_P256K1;
return curve;
}
--
More information about the Gnupg-devel
mailing list