agent: adding KEYTOCARD command (master branch)
NIIBE Yutaka
gniibe at fsij.org
Tue Feb 5 05:55:33 CET 2013
On 2013-02-04 at 14:40 +0100, Werner Koch wrote:
> On Mon, 4 Feb 2013 01:34, gniibe at fsij.org said:
>
> > I think that the reason why it is not yet supported is because of the
> > shift of secret key handling to gpg-agent.
>
> Right, after that change some card specific commands are not yet
> working.
Here's the patch.
It works for me, but the behavior of gpg command has been changed. In
2.0, we invoke --edit-key to invoke keytocard subcommand. After we
store private keys to card, it goes like this:
gpg> quit
Save changes? (y/N) n
Quit without saving? (y/N) y
$
But, master branch don't ask "Save changes?", and it has been changed
already in gpg-agent's storage.
gpg: Implement card_store_subkey again.
* g10/call-agent.h (agent_keytocard): New.
* g10/call-agent.c (agent_keytocard): New.
* g10/card-util.c (replace_existing_key_p): Returns 1 when replace.
(card_generate_subkey): Check return value of replace_existing_key_p.
(card_store_subkey): Implement again using agent_keytocard.
agent: Add KEYTOCARD command.
* agent/agent.h (divert_writekey, agent_card_writekey): New.
* agent/call-scd.c (inq_writekey_parms, agent_card_writekey): New.
* agent/command.c (cmd_keytocard, hlp_keytocard): New.
(register_commands): Add cmd_keytocard.
* agent/divert-scd.c (divert_writekey): New.
diff --git a/agent/agent.h b/agent/agent.h
index 45bc507..8457e34 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -421,6 +421,8 @@ int divert_pkdecrypt (ctrl_t ctrl,
char **r_buf, size_t *r_len);
int divert_generic_cmd (ctrl_t ctrl,
const char *cmdline, void *assuan_context);
+int divert_writekey (ctrl_t ctrl, int force, const char *serialno,
+ const char *id, const char *keydata, size_t keydatalen);
/*-- call-scd.c --*/
@@ -454,6 +456,11 @@ int agent_card_pkdecrypt (ctrl_t ctrl,
int agent_card_readcert (ctrl_t ctrl,
const char *id, char **r_buf, size_t *r_buflen);
int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf);
+int agent_card_writekey (ctrl_t ctrl, int force, const char *serialno,
+ const char *id, const char *keydata,
+ size_t keydatalen,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg);
gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result);
int agent_card_scd (ctrl_t ctrl, const char *cmdline,
int (*getpin_cb)(void *, const char *, char*, size_t),
diff --git a/agent/call-scd.c b/agent/call-scd.c
index 2bda377..ed80be2 100644
--- a/agent/call-scd.c
+++ b/agent/call-scd.c
@@ -1050,6 +1050,64 @@ agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf)
}
+struct writekey_parm_s
+{
+ assuan_context_t ctx;
+ int (*getpin_cb)(void *, const char *, char*, size_t);
+ void *getpin_cb_arg;
+ assuan_context_t passthru;
+ int any_inq_seen;
+ /**/
+ const unsigned char *keydata;
+ size_t keydatalen;
+};
+
+/* Handle a KEYDATA inquiry. Note, we only send the data,
+ assuan_transact takes care of flushing and writing the end */
+static gpg_error_t
+inq_writekey_parms (void *opaque, const char *line)
+{
+ struct writekey_parm_s *parm = opaque;
+
+ if (!strncmp (line, "KEYDATA", 7) && (line[7]==' '||!line[7]))
+ return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen);
+ else
+ return inq_needpin (opaque, line);
+}
+
+
+int
+agent_card_writekey (ctrl_t ctrl, int force, const char *serialno,
+ const char *id, const char *keydata, size_t keydatalen,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ struct writekey_parm_s parms;
+
+ (void)serialno;
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ snprintf (line, DIM(line)-1, "WRITEKEY %s%s", force ? "--force " : "", id);
+ line[DIM(line)-1] = 0;
+ parms.ctx = ctrl->scd_local->ctx;
+ parms.getpin_cb = getpin_cb;
+ parms.getpin_cb_arg = getpin_cb_arg;
+ parms.passthru = 0;
+ parms.any_inq_seen = 0;
+ parms.keydata = keydata;
+ parms.keydatalen = keydatalen;
+
+ rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL,
+ inq_writekey_parms, &parms, NULL, NULL);
+ if (parms.any_inq_seen && (gpg_err_code(rc) == GPG_ERR_CANCELED ||
+ gpg_err_code(rc) == GPG_ERR_ASS_CANCELED))
+ rc = cancel_inquire (ctrl, rc);
+ return unlock_scd (ctrl, rc);
+}
/* Type used with the card_getattr_cb. */
struct card_getattr_parm_s {
diff --git a/agent/command.c b/agent/command.c
index 3ba921b..17d266b 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -2119,9 +2119,123 @@ cmd_export_key (assuan_context_t ctx, char *line)
return leave_cmd (ctx, err);
}
+
+static const char hlp_keytocard[] =
+ "KEYTOCARD [--force] <hexstring_with_keygrip> <serialno> <id> <timestamp>\n"
+ "\n";
+static gpg_error_t
+cmd_keytocard (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int force;
+ gpg_error_t err = 0;
+ unsigned char grip[20];
+ gcry_sexp_t s_skey = NULL;
+ gcry_sexp_t s_pkey = NULL;
+ char keydata[2048];
+ size_t keydatalen, timestamplen;
+ const char *serialno, *timestamp_str, *id;
+ unsigned char *shadow_info;
+ unsigned char *shdkey;
+ time_t timestamp;
+
+ force = has_option (line, "--force");
+ line = skip_options (line);
+ err = parse_keygrip (ctx, line, grip);
+ if (err)
+ return err;
+ if (agent_key_available (grip))
+ return gpg_error (GPG_ERR_NO_SECKEY);
+ line += 40;
+ while (*line && (*line == ' ' || *line == '\t'))
+ line++;
+ serialno = line;
+ while (*line && (*line != ' ' && *line != '\t'))
+ line++;
+ if (!*line)
+ return gpg_error (GPG_ERR_MISSING_VALUE);
+ *line = '\0';
+ line++;
+ while (*line && (*line == ' ' || *line == '\t'))
+ line++;
+ id = line;
+ while (*line && (*line != ' ' && *line != '\t'))
+ line++;
+ if (!*line)
+ return gpg_error (GPG_ERR_MISSING_VALUE);
+ *line = '\0';
+ line++;
+ while (*line && (*line == ' ' || *line == '\t'))
+ line++;
+ timestamp_str = line;
+ while (*line && (*line != ' ' && *line != '\t'))
+ line++;
+ if (*line)
+ *line = '\0';
+ timestamplen = line - timestamp_str;
+ if (timestamplen != 15)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
+ NULL, CACHE_MODE_IGNORE, NULL, &s_skey, NULL);
+ if (err)
+ return err;
+ if (!s_skey)
+ /* Key is on a smartcard already. */
+ return gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+
+ keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata,
+ sizeof (keydata));
+ /* Add timestamp "created-at" in the private key */
+ timestamp = isotime2epoch (timestamp_str);
+ snprintf (keydata+keydatalen-1, sizeof (keydata) - keydatalen -1,
+ "(10:created-at10:%010lu))", timestamp);
+ keydatalen += 10 + 19 - 1;
+ err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen);
+ if (err)
+ {
+ gcry_sexp_release (s_skey);
+ goto leave;
+ }
+
+ err = agent_public_key_from_file (ctrl, grip, &s_pkey);
+ if (err)
+ {
+ gcry_sexp_release (s_skey);
+ goto leave;
+ }
+
+ shadow_info = make_shadow_info (serialno, id);
+ if (!shadow_info)
+ {
+ err = gpg_error (GPG_ERR_ENOMEM);
+ gcry_sexp_release (s_pkey);
+ gcry_sexp_release (s_skey);
+ goto leave;
+ }
+ keydatalen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keydata,
+ sizeof (keydata));
+ err = agent_shadow_key (keydata, shadow_info, &shdkey);
+ xfree (shadow_info);
+ if (err)
+ {
+ log_error ("shadowing the key failed: %s\n", gpg_strerror (err));
+ gcry_sexp_release (s_pkey);
+ gcry_sexp_release (s_skey);
+ goto leave;
+ }
+ keydatalen = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
+ err = agent_write_private_key (grip, shdkey, keydatalen, 1);
+ xfree (shdkey);
+ gcry_sexp_release (s_pkey);
+ gcry_sexp_release (s_skey);
+
+ leave:
+ return leave_cmd (ctx, err);
+}
static const char hlp_getval[] =
"GETVAL <key>\n"
@@ -2682,6 +2796,7 @@ register_commands (assuan_context_t ctx)
{ "KILLAGENT", cmd_killagent, hlp_killagent },
{ "RELOADAGENT", cmd_reloadagent,hlp_reloadagent },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
+ { "KEYTOCARD", cmd_keytocard, hlp_keytocard },
{ NULL }
};
int i, rc;
diff --git a/agent/divert-scd.c b/agent/divert-scd.c
index 656d5cd..65784a8 100644
--- a/agent/divert-scd.c
+++ b/agent/divert-scd.c
@@ -442,6 +442,13 @@ divert_pkdecrypt (ctrl_t ctrl,
return rc;
}
+int
+divert_writekey (ctrl_t ctrl, int force, const char *serialno,
+ const char *id, const char *keydata, size_t keydatalen)
+{
+ return agent_card_writekey (ctrl, force, serialno, id, keydata, keydatalen,
+ getpin_cb, ctrl);
+}
int
divert_generic_cmd (ctrl_t ctrl, const char *cmdline, void *assuan_context)
diff --git a/g10/call-agent.c b/g10/call-agent.c
index a4d1dbb..2f7f21a 100644
--- a/g10/call-agent.c
+++ b/g10/call-agent.c
@@ -545,6 +545,30 @@ agent_learn (struct agent_card_info_s *info)
return rc;
}
+
+int
+agent_keytocard (const char *hexgrip, int keyno, int force,
+ const char *serialno, const char *timestamp)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+
+ snprintf (line, DIM(line)-1, "KEYTOCARD %s%s %s OPENPGP.%d %s",
+ force?"--force ": "", hexgrip, serialno, keyno, timestamp);
+ line[DIM(line)-1] = 0;
+
+ rc = start_agent (NULL, 1);
+ if (rc)
+ return rc;
+
+ rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb,
+ NULL, NULL, NULL);
+ if (rc)
+ return rc;
+
+ return rc;
+}
+
/* Call the agent to retrieve a data object. This function returns
the data in the same structure as used by the learn command. It is
allowed to update such a structure using this commmand. */
diff --git a/g10/call-agent.h b/g10/call-agent.h
index 43de14f..7ae5e80 100644
--- a/g10/call-agent.h
+++ b/g10/call-agent.h
@@ -81,6 +81,10 @@ int agent_learn (struct agent_card_info_s *info);
/* Update INFO with the attribute NAME. */
int agent_scd_getattr (const char *name, struct agent_card_info_s *info);
+/* Send the KEYTOCARD command. */
+int agent_keytocard (const char *hexgrip, int keyno, int force,
+ const char *serialno, const char *timestamp);
+
/* Send a SETATTR command to the SCdaemon. */
int agent_scd_setattr (const char *name,
const unsigned char *value, size_t valuelen,
diff --git a/g10/card-util.c b/g10/card-util.c
index 8358685..75208cc 100644
--- a/g10/card-util.c
+++ b/g10/card-util.c
@@ -1264,6 +1264,7 @@ replace_existing_key_p (struct agent_card_info_s *info, int keyno)
if ( !cpr_get_answer_is_yes( "cardedit.genkeys.replace_key",
_("Replace existing key? (y/N) ")))
return -1;
+ return 1;
}
return 0;
}
@@ -1484,7 +1485,7 @@ card_generate_subkey (KBNODE pub_keyblock)
tty_printf(_("Invalid selection.\n"));
}
- if (replace_existing_key_p (&info, keyno))
+ if (replace_existing_key_p (&info, keyno) < 0)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
@@ -1531,152 +1532,99 @@ card_generate_subkey (KBNODE pub_keyblock)
int
card_store_subkey (KBNODE node, int use)
{
- log_info ("FIXME: card_store_subkey has not yet been implemented\n");
-/* struct agent_card_info_s info; */
-/* int okay = 0; */
-/* int rc; */
-/* int keyno, i; */
-/* PKT_secret_key *copied_sk = NULL; */
-/* PKT_secret_key *sk; */
-/* size_t n; */
-/* const char *s; */
-/* int allow_keyno[3]; */
-/* unsigned int nbits; */
-
-
-/* assert (node->pkt->pkttype == PKT_SECRET_KEY */
-/* || node->pkt->pkttype == PKT_SECRET_SUBKEY); */
-/* sk = node->pkt->pkt.secret_key; */
-
-/* if (get_info_for_key_operation (&info)) */
-/* return 0; */
-
-/* if (!info.extcap.ki) */
-/* { */
-/* tty_printf ("The card does not support the import of keys\n"); */
-/* tty_printf ("\n"); */
-/* goto leave; */
-/* } */
-
-/* show_card_key_info (&info); */
-
-/* nbits = nbits_from_sk (sk); */
-
-/* if (!is_RSA (sk->pubkey_algo) || (!info.is_v2 && nbits != 1024) ) */
-/* { */
-/* tty_printf ("You may only store a 1024 bit RSA key on the card\n"); */
-/* tty_printf ("\n"); */
-/* goto leave; */
-/* } */
-
-/* allow_keyno[0] = (!use || (use & (PUBKEY_USAGE_SIG))); */
-/* allow_keyno[1] = (!use || (use & (PUBKEY_USAGE_ENC))); */
-/* allow_keyno[2] = (!use || (use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH))); */
-
-/* tty_printf (_("Please select where to store the key:\n")); */
-
-/* if (allow_keyno[0]) */
-/* tty_printf (_(" (1) Signature key\n")); */
-/* if (allow_keyno[1]) */
-/* tty_printf (_(" (2) Encryption key\n")); */
-/* if (allow_keyno[2]) */
-/* tty_printf (_(" (3) Authentication key\n")); */
-
-/* for (;;) */
-/* { */
-/* char *answer = cpr_get ("cardedit.genkeys.storekeytype", */
-/* _("Your selection? ")); */
-/* cpr_kill_prompt(); */
-/* if (*answer == CONTROL_D || !*answer) */
-/* { */
-/* xfree (answer); */
-/* goto leave; */
-/* } */
-/* keyno = *answer? atoi(answer): 0; */
-/* xfree(answer); */
-/* if (keyno >= 1 && keyno <= 3 && allow_keyno[keyno-1]) */
-/* { */
-/* if (info.is_v2 && !info.extcap.aac */
-/* && info.key_attr[keyno-1].nbits != nbits) */
-/* { */
-/* tty_printf ("Key does not match the card's capability.\n"); */
-/* } */
-/* else */
-/* break; /\* Okay. *\/ */
-/* } */
-/* else */
-/* tty_printf(_("Invalid selection.\n")); */
-/* } */
-
-/* if (replace_existing_key_p (&info, keyno)) */
-/* goto leave; */
-
-/* /\* Unprotect key. *\/ */
-/* switch (is_secret_key_protected (sk) ) */
-/* { */
-/* case 0: /\* Not protected. *\/ */
-/* break; */
-/* case -1: */
-/* log_error (_("unknown key protection algorithm\n")); */
-/* goto leave; */
-/* default: */
-/* if (sk->protect.s2k.mode == 1001) */
-/* { */
-/* log_error (_("secret parts of key are not available\n")); */
-/* goto leave; */
-/* } */
-/* if (sk->protect.s2k.mode == 1002) */
-/* { */
-/* log_error (_("secret key already stored on a card\n")); */
-/* goto leave; */
-/* } */
-/* /\* We better copy the key before we unprotect it. *\/ */
-/* copied_sk = sk = copy_secret_key (NULL, sk); */
-/* rc = 0/\*check_secret_key (sk, 0)*\/; */
-/* if (rc) */
-/* goto leave; */
-/* } */
-
-/* #warning code save_unprotected_key_to_card */
-/* /\* rc = save_unprotected_key_to_card (sk, keyno); *\/ */
-/* /\* if (rc) *\/ */
-/* /\* { *\/ */
-/* /\* log_error (_("error writing key to card: %s\n"), gpg_strerror (rc)); *\/ */
-/* /\* goto leave; *\/ */
-/* /\* } *\/ */
-
-/* /\* Get back to the maybe protected original secret key. *\/ */
-/* if (copied_sk) */
-/* { */
-/* free_secret_key (copied_sk); */
-/* copied_sk = NULL; */
-/* } */
-/* sk = node->pkt->pkt.secret_key; */
-
-/* /\* Get rid of the secret key parameters and store the serial numer. *\/ */
-/* n = pubkey_get_nskey (sk->pubkey_algo); */
-/* for (i=pubkey_get_npkey (sk->pubkey_algo); i < n; i++) */
-/* { */
-/* gcry_mpi_release (sk->skey[i]); */
-/* sk->skey[i] = NULL; */
-/* } */
-/* i = pubkey_get_npkey (sk->pubkey_algo); */
-/* sk->skey[i] = gcry_mpi_set_opaque (NULL, xstrdup ("dummydata"), 10*8); */
-/* sk->is_protected = 1; */
-/* sk->protect.s2k.mode = 1002; */
-/* s = info.serialno; */
-/* for (sk->protect.ivlen=0; sk->protect.ivlen < 16 && *s && s[1]; */
-/* sk->protect.ivlen++, s += 2) */
-/* sk->protect.iv[sk->protect.ivlen] = xtoi_2 (s); */
-
-/* okay = 1; */
-
-/* leave: */
-/* if (copied_sk) */
-/* free_secret_key (copied_sk); */
-/* agent_release_card_info (&info); */
-/* return okay; */
- return -1;
+ struct agent_card_info_s info;
+ int okay = 0;
+ unsigned int nbits;
+ int allow_keyno[3];
+ int keyno;
+ PKT_public_key *pk;
+ gpg_error_t err;
+ char *hexgrip;
+ int rc;
+ gnupg_isotime_t timebuf;
+
+ assert (node->pkt->pkttype == PKT_PUBLIC_KEY
+ || node->pkt->pkttype == PKT_PUBLIC_SUBKEY);
+
+ pk = node->pkt->pkt.public_key;
+
+ if (get_info_for_key_operation (&info))
+ return 0;
+
+ if (!info.extcap.ki)
+ {
+ tty_printf ("The card does not support the import of keys\n");
+ tty_printf ("\n");
+ goto leave;
+ }
+
+ nbits = nbits_from_pk (pk);
+
+ if (!is_RSA (pk->pubkey_algo) || (!info.is_v2 && nbits != 1024) )
+ {
+ tty_printf ("You may only store a 1024 bit RSA key on the card\n");
+ tty_printf ("\n");
+ goto leave;
+ }
+
+ allow_keyno[0] = (!use || (use & (PUBKEY_USAGE_SIG)));
+ allow_keyno[1] = (!use || (use & (PUBKEY_USAGE_ENC)));
+ allow_keyno[2] = (!use || (use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH)));
+
+ tty_printf (_("Please select where to store the key:\n"));
+
+ if (allow_keyno[0])
+ tty_printf (_(" (1) Signature key\n"));
+ if (allow_keyno[1])
+ tty_printf (_(" (2) Encryption key\n"));
+ if (allow_keyno[2])
+ tty_printf (_(" (3) Authentication key\n"));
+
+ for (;;)
+ {
+ char *answer = cpr_get ("cardedit.genkeys.storekeytype",
+ _("Your selection? "));
+ cpr_kill_prompt();
+ if (*answer == CONTROL_D || !*answer)
+ {
+ xfree (answer);
+ goto leave;
+ }
+ keyno = *answer? atoi(answer): 0;
+ xfree(answer);
+ if (keyno >= 1 && keyno <= 3 && allow_keyno[keyno-1])
+ {
+ if (info.is_v2 && !info.extcap.aac
+ && info.key_attr[keyno-1].nbits != nbits)
+ {
+ tty_printf ("Key does not match the card's capability.\n");
+ }
+ else
+ break; /* Okay. */
+ }
+ else
+ tty_printf(_("Invalid selection.\n"));
+ }
+
+ if ((rc = replace_existing_key_p (&info, keyno)) < 0)
+ goto leave;
+
+ err = hexkeygrip_from_pk (pk, &hexgrip);
+ if (err)
+ goto leave;
+
+ epoch2isotime (timebuf, (time_t)pk->timestamp);
+ agent_keytocard (hexgrip, keyno, rc, info.serialno, timebuf);
+
+ if (rc)
+ log_error (_("KEYTOCARD failed: %s\n"), gpg_strerror (rc));
+ else
+ okay = 1;
+ xfree (hexgrip);
+
+ leave:
+ agent_release_card_info (&info);
+ return okay;
}
--
More information about the Gnupg-devel
mailing list