[PATCH] export smartcard stub (was: Private key transfer format)
NIIBE Yutaka
gniibe at fsij.org
Thu Apr 9 13:52:45 CEST 2015
On 04/08/2015 05:40 PM, NIIBE Yutaka wrote:
> Hello,
>
> I'm trying to fix the issue: https://bugs.g10code.com/gnupg/issue1937
>
> Here, we need to enhance the OpenPGP Private Key Transfer Format.
>
> Currently, as it is described in agent/keyformat.txt, it's like:
>
> (openpgp-private-key
> (version V)
> (algo PUBKEYALGO)
> (curve CURVENAME)
> (skey _ P1 _ P2 _ P3 ... e PN)
> (csum n)
> (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT))
>
> For private keys in smartcard, it can be something like following:
>
> (openpgp-private-key
> (version V)
> (algo PUBKEYALGO)
> (curve CURVENAME)
> (skey _ P1 _ P2 _ P3 ... _ PN_minus_1) # ??? pkey???
> (csum n)
> (shadowed PROTOCOL (INFO)))
>
> How about this?
I implemented, but reading the implementation of GnuPG 2.0.x and
OpenPGP format for smartcard stub, I think that it is more natural
to represent it like:
(openpgp-private-key
(version V)
(algo PUBKEYALGO)
(curve CURVENAME)
(skey _ P1 _ P2 _ P3 ... _ PN)
(csum n)
(protection none plain SERIAL 101 none GNU 2))
Then, it will have 1-to-1 mapping to the representation in OpenPGP
format.
Here is the patch. I tested and confirmed that GnuPG can export
secret key (with smartcard serial number).
diff --git a/agent/command.c b/agent/command.c
index 3188bbd..9fa2e90 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -2221,6 +2221,7 @@ cmd_export_key (assuan_context_t ctx, char *line)
char *cache_nonce;
char *passphrase = NULL;
unsigned char *shadow_info = NULL;
+ char *serialno = NULL;
char *pend;
int c;
@@ -2271,9 +2272,10 @@ cmd_export_key (assuan_context_t ctx, char *line)
goto leave;
if (shadow_info)
{
- /* Key is on a smartcard. */
- err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
- goto leave;
+ err = parse_shadow_info (shadow_info, &serialno, NULL, NULL);
+ xfree (shadow_info);
+ if (err)
+ goto leave;
}
if (openpgp)
@@ -2281,7 +2283,7 @@ cmd_export_key (assuan_context_t ctx, char *line)
/* The openpgp option changes the key format into the OpenPGP
key transfer format. The result is already a padded
canonical S-expression. */
- if (!passphrase)
+ if (!passphrase && !shadow_info)
{
err = agent_ask_new_passphrase
(ctrl, _("This key (or subkey) is not protected with a passphrase."
@@ -2290,7 +2292,9 @@ cmd_export_key (assuan_context_t ctx, char *line)
if (err)
goto leave;
}
- err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen);
+
+ err = convert_to_openpgp (ctrl, s_skey, serialno, passphrase,
+ &key, &keylen);
if (!err && passphrase)
{
if (!cache_nonce)
@@ -2312,6 +2316,12 @@ cmd_export_key (assuan_context_t ctx, char *line)
}
else
{
+ if (serialno)
+ { /* Key is on a smartcard. */
+ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+ goto leave;
+ }
+
/* Convert into a canonical S-expression and wrap that. */
err = make_canon_sexp_pad (s_skey, 1, &key, &keylen);
}
@@ -2351,6 +2361,7 @@ cmd_export_key (assuan_context_t ctx, char *line)
leave:
+ xfree (serialno);
xfree (cache_nonce);
xfree (passphrase);
xfree (wrappedkey);
@@ -2359,7 +2370,6 @@ cmd_export_key (assuan_context_t ctx, char *line)
gcry_sexp_release (s_skey);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
- xfree (shadow_info);
return leave_cmd (ctx, err);
}
diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c
index b00f032..bd75bd5 100644
--- a/agent/cvt-openpgp.c
+++ b/agent/cvt-openpgp.c
@@ -1391,9 +1391,13 @@ extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
/* Convert our key S_KEY into an OpenPGP key transfer format. On
success a canonical encoded S-expression is stored at R_TRANSFERKEY
and its length at R_TRANSFERKEYLEN; this S-expression is also
- padded to a multiple of 64 bits. */
+ padded to a multiple of 64 bits.
+ When SERIALNO is non-NULL, it's for smartcard serialno hex-string.
+ PASSPHRASE points to the memory for encrypting the private key.
+ */
gpg_error_t
-convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
+convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key,
+ const unsigned char *serialno, const char *passphrase,
unsigned char **r_transferkey, size_t *r_transferkeylen)
{
gpg_error_t err;
@@ -1402,8 +1406,10 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
gcry_mpi_t array[10];
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
+ const char *proto_type, *proto_algo, *s2k_hash;
char protect_iv[16];
char salt[8];
+ unsigned int s2k_mode;
unsigned long s2k_count;
int i, j;
@@ -1414,32 +1420,49 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
for (i=0; i < DIM (array); i++)
array[i] = NULL;
- err = extract_private_key (s_key, 1, &algoname, &npkey, &nskey, NULL,
- array, DIM (array), &curve, &flags);
+ err = extract_private_key (s_key, !serialno, &algoname, &npkey, &nskey,
+ NULL, array, DIM (array), &curve, &flags);
if (err)
return err;
- gcry_create_nonce (protect_iv, sizeof protect_iv);
- gcry_create_nonce (salt, sizeof salt);
- /* We need to use the encoded S2k count. It is not possible to
- encode it after it has been used because the encoding procedure
- may round the value up. */
- s2k_count = get_standard_s2k_count_rfc4880 ();
- err = apply_protection (array, npkey, nskey, passphrase,
- GCRY_CIPHER_AES, protect_iv, sizeof protect_iv,
- 3, GCRY_MD_SHA1, salt, s2k_count);
+ if (serialno)
+ {
+ s2k_mode = 101; /* GnuPG extension. */
+ proto_type = "none";
+ proto_algo = "plain";
+ if (hex2bin (serialno, protect_iv, sizeof protect_iv) < 0)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ s2k_hash = "none";
+ strcpy (salt, "GNU");
+ s2k_count = 2; /* Divert to OpenPGP smartcard. */
+ array[npkey] = GCRYMPI_CONST_ONE;
+ }
+ else
+ {
+ s2k_mode = 3;
+ proto_type = "sha1";
+ proto_algo = "aes";
+ gcry_create_nonce (protect_iv, sizeof protect_iv);
+ s2k_hash = "sha1";
+ gcry_create_nonce (salt, sizeof salt);
+ /* We need to use the encoded S2k count. It is not possible to
+ encode it after it has been used because the encoding procedure
+ may round the value up. */
+ s2k_count = get_standard_s2k_count_rfc4880 ();
+ err = apply_protection (array, npkey, nskey, passphrase,
+ GCRY_CIPHER_AES, protect_iv, sizeof protect_iv,
+ 3, GCRY_MD_SHA1, salt, s2k_count);
+ }
+
/* Turn it into the transfer key S-expression. Note that we always
return a protected key. */
if (!err)
{
- char countbuf[35];
membuf_t mbuf;
void *format_args[10+2];
gcry_sexp_t tmpkey;
gcry_sexp_t tmpsexp = NULL;
- snprintf (countbuf, sizeof countbuf, "%lu", s2k_count);
-
init_membuf (&mbuf, 50);
put_membuf_str (&mbuf, "(skey");
for (i=j=0; i < npkey; i++)
@@ -1447,7 +1470,7 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = array + i;
}
- put_membuf_str (&mbuf, " e %m");
+ put_membuf_str (&mbuf, serialno ? " _ %m" : " e %m");
format_args[j++] = array + npkey;
put_membuf_str (&mbuf, ")\n");
put_membuf (&mbuf, "", 1);
@@ -1461,19 +1484,27 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
err = gcry_sexp_build_array (&tmpkey, NULL, format, format_args);
xfree (format);
}
+
if (!err)
- err = gcry_sexp_build (&tmpsexp, NULL,
- "(openpgp-private-key\n"
- " (version 1:4)\n"
- " (algo %s)\n"
- " %S%S\n"
- " (protection sha1 aes %b 1:3 sha1 %b %s))\n",
- algoname,
- curve,
- tmpkey,
- (int)sizeof protect_iv, protect_iv,
- (int)sizeof salt, salt,
- countbuf);
+ {
+ char countbuf[35];
+
+ snprintf (countbuf, sizeof countbuf, "%lu", s2k_count);
+ err = gcry_sexp_build (&tmpsexp, NULL,
+ "(openpgp-private-key\n"
+ " (version 1:4)\n"
+ " (algo %s)\n"
+ " %S%S\n"
+ " (protection %s %s %b %u %s %b %s))\n",
+ algoname,
+ curve,
+ tmpkey,
+ proto_type, proto_algo,
+ (int)sizeof protect_iv, protect_iv, s2k_mode,
+ s2k_hash, serialno ? 3: (int)sizeof salt, salt,
+ countbuf);
+ }
+
gcry_sexp_release (tmpkey);
if (!err)
err = make_canon_sexp_pad (tmpsexp, 0, r_transferkey, r_transferkeylen);
diff --git a/agent/cvt-openpgp.h b/agent/cvt-openpgp.h
index d27a776..9bacb06 100644
--- a/agent/cvt-openpgp.h
+++ b/agent/cvt-openpgp.h
@@ -29,6 +29,7 @@ gpg_error_t convert_from_openpgp_native (ctrl_t ctrl,
unsigned char **r_key);
gpg_error_t convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key,
+ const unsigned char *serialno,
const char *passphrase,
unsigned char **r_transferkey,
size_t *r_transferkeylen);
diff --git a/agent/keyformat.txt b/agent/keyformat.txt
index 42c4b1f..56a7688 100644
--- a/agent/keyformat.txt
+++ b/agent/keyformat.txt
@@ -236,6 +236,11 @@ This format is used to transfer keys between gpg and gpg-agent.
* S2KSALT is the 8 byte salt
* S2KCOUNT is the count value from RFC-4880.
+For smartcard stub, it's:
+
+* PN is dummy data: 1 (and not encrypted)
+* PROTECTION is: (protection none plain SERIAL 101 none GNU 2)
+
Persistent Passphrase Format
============================
diff --git a/doc/DETAILS b/doc/DETAILS
index fd72b88..6fd01e0 100644
--- a/doc/DETAILS
+++ b/doc/DETAILS
@@ -1137,6 +1137,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
1 octet - S2K Usage: either 254 or 255.
1 octet - S2K Cipher Algo: 0
1 octet - S2K Specifier: 101
+ 1 octet - S2K Hash Algorithm: 0
3 octets - "GNU"
1 octet - GNU S2K Extension Number.
diff --git a/g10/export.c b/g10/export.c
index b65fb8d..5711021 100644
--- a/g10/export.c
+++ b/g10/export.c
@@ -449,6 +449,32 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
s2k_count = strtoul (string, NULL, 10);
xfree (string);
}
+ else
+ { /* It's smartcard stub. */
+ protect_algo = 0;
+ s2k_algo = 0;
+
+ value = gcry_sexp_nth_data (list, 3, &valuelen);
+ if (!value || !valuelen || valuelen > sizeof iv)
+ goto bad_seckey;
+ memcpy (iv, value, valuelen);
+ ivlen = valuelen;
+log_printhex ("XXX iv", iv, ivlen);
+ string = gcry_sexp_nth_string (list, 4);
+ if (!string)
+ goto bad_seckey;
+ s2k_mode = strtol (string, NULL, 10);
+ xfree (string);
+ value = gcry_sexp_nth_data (list, 6, &valuelen);
+ if (!value || !valuelen || valuelen > 3 || memcmp (value, "GNU", 3))
+ goto bad_seckey;
+ memset (s2k_salt, 0, sizeof s2k_salt);
+ string = gcry_sexp_nth_string (list, 7);
+ if (!string)
+ goto bad_seckey;
+ s2k_count = strtoul (string, NULL, 10);
+ xfree (string);
+ }
/* Parse the gcrypt PK algo and check that it is okay. */
gcry_sexp_release (list);
@@ -606,7 +632,7 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
/* log_printf ("\n"); */
/* } */
- if (!is_v4 || is_protected != 2 )
+ if (!is_v4 || (is_protected != 0 && is_protected != 2) )
{
/* We only support the v4 format and a SHA-1 checksum. */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
@@ -675,18 +701,21 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
}
/* Do some sanity checks. */
- if (s2k_count > 255)
+ if (is_protected)
{
- /* We expect an already encoded S2K count. */
- err = gpg_error (GPG_ERR_INV_DATA);
- goto leave;
+ if (s2k_count > 255)
+ {
+ /* We expect an already encoded S2K count. */
+ err = gpg_error (GPG_ERR_INV_DATA);
+ goto leave;
+ }
+ err = openpgp_cipher_test_algo (protect_algo);
+ if (err)
+ goto leave;
+ err = openpgp_md_test_algo (s2k_algo);
+ if (err)
+ goto leave;
}
- err = openpgp_cipher_test_algo (protect_algo);
- if (err)
- goto leave;
- err = openpgp_md_test_algo (s2k_algo);
- if (err)
- goto leave;
/* Check that the public key parameters match. Note that since
Libgcrypt 1.5 gcry_mpi_cmp handles opaque MPI correctly. */
@@ -700,7 +729,7 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
/* Check that the first secret key parameter in SKEY is encrypted
and that there are no more secret key parameters. The latter is
guaranteed by the v4 packet format. */
- if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE))
+ if (is_protected && !gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE))
goto bad_seckey;
if (npkey+1 < DIM (skey) && skey[npkey+1])
goto bad_seckey;
@@ -721,18 +750,33 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
ski->is_protected = 1;
ski->sha1chk = 1;
ski->algo = protect_algo;
- ski->s2k.mode = s2k_mode;
+ if (is_protected == 0)
+ ski->s2k.mode = 1000 + s2k_count;
+ else
+ {
+ ski->s2k.mode = s2k_mode;
+ ski->s2k.count = s2k_count;
+ }
ski->s2k.hash_algo = s2k_algo;
assert (sizeof ski->s2k.salt == sizeof s2k_salt);
memcpy (ski->s2k.salt, s2k_salt, sizeof s2k_salt);
- ski->s2k.count = s2k_count;
assert (ivlen <= sizeof ski->iv);
memcpy (ski->iv, iv, ivlen);
ski->ivlen = ivlen;
/* Store the protected secret key parameter. */
- pk->pkey[npkey] = skey[npkey];
- skey[npkey] = NULL;
+ if (is_protected)
+ {
+ pk->pkey[npkey] = skey[npkey];
+ skey[npkey] = NULL;
+ }
+ else
+ {
+ pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
+ xstrdup ("dummydata"),
+ 10 * 8);
+ }
+
/* That's it. */
--
More information about the Gnupg-devel
mailing list