diff --git a/agent/agent.h b/agent/agent.h index f7e96fcff..18d60fb36 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -607,7 +607,8 @@ int agent_card_pkdecrypt (ctrl_t ctrl, char **r_buf, size_t *r_buflen, int *r_padding); 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_readkey (ctrl_t ctrl, const char *id, + unsigned char **r_buf, char **r_keyref); gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, const char *keyref, const char *keydata, size_t keydatalen, diff --git a/agent/call-scd.c b/agent/call-scd.c index 347160a40..60365c980 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -686,7 +686,8 @@ handle_pincache_put (const char *args) } -/* This status callback is to intercept the PINCACHE_PUT status messages. */ +/* This status callback is to intercept the PINCACHE_PUT status + * messages. OPAQUE is not used. */ static gpg_error_t pincache_put_cb (void *opaque, const char *line) { @@ -840,8 +841,11 @@ get_serialno_cb (void *opaque, const char *line) return err; } + /* Return the serial number of the card or an appropriate error. The - serial number is returned as a hexstring. */ + * serial number is returned as a hexstring. If the serial number is + * not required by the caller R_SERIALNO can be NULL; this might be + * useful to test whether a card is available. */ int agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand) { @@ -866,7 +870,10 @@ agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand) xfree (serialno); return unlock_scd (ctrl, rc); } - *r_serialno = serialno; + if (r_serialno) + *r_serialno = serialno; + else + xfree (serialno); return unlock_scd (ctrl, 0); } @@ -1165,41 +1172,107 @@ agent_card_readcert (ctrl_t ctrl, -/* Read a key with ID and return it in an allocate buffer pointed to - by r_BUF as a valid S-expression. */ +struct readkey_status_parm_s +{ + char *keyref; +}; + +static gpg_error_t +readkey_status_cb (void *opaque, const char *line) +{ + struct readkey_status_parm_s *parm = opaque; + gpg_error_t err = 0; + char *line_buffer = NULL; + const char *s; + + if ((s = has_leading_keyword (line, "KEYPAIRINFO")) + && !parm->keyref) + { + /* The format of such a line is: + * KEYPAIRINFO [usage] [keytime] + * + * Here we only need the keyref. We use only the first received + * KEYPAIRINFO; it is possible to receive several if there are + * two or more active cards with the same key. */ + char *fields[2]; + int nfields; + + line_buffer = xtrystrdup (line); + if (!line_buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2) + goto leave; /* Not enough args; invalid status line - skip. */ + + parm->keyref = xtrystrdup (fields[1]); + if (!parm->keyref) + err = gpg_error_from_syserror (); + } + else + err = pincache_put_cb (NULL, line); + + leave: + xfree (line_buffer); + return err; +} + + +/* Read a key with ID (keyref or keygrip) and return it in a malloced + * buffer pointed to by R_BUF as a valid S-expression. If R_KEYREF is + * not NULL the keyref is stored there. */ int -agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) +agent_card_readkey (ctrl_t ctrl, const char *id, + unsigned char **r_buf, char **r_keyref) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len, buflen; + struct readkey_status_parm_s parm; + + memset (&parm, 0, sizeof parm); *r_buf = NULL; + if (r_keyref) + *r_keyref = NULL; + rc = start_scd (ctrl); if (rc) return rc; init_membuf (&data, 1024); - snprintf (line, DIM(line), "READKEY %s", id); + snprintf (line, DIM(line), "READKEY%s -- %s", + r_keyref? " --info":"", id); rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, NULL, NULL, - pincache_put_cb, NULL); + readkey_status_cb, &parm); if (rc) { xfree (get_membuf (&data, &len)); + xfree (parm.keyref); return unlock_scd (ctrl, rc); } *r_buf = get_membuf (&data, &buflen); if (!*r_buf) - return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); + { + xfree (parm.keyref); + return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); + } if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL)) { + xfree (parm.keyref); xfree (*r_buf); *r_buf = NULL; return unlock_scd (ctrl, gpg_error (GPG_ERR_INV_VALUE)); } + if (r_keyref) + *r_keyref = parm.keyref; + else + xfree (parm.keyref); return unlock_scd (ctrl, 0); } diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 243ce76bb..8adbe01cd 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -2394,7 +2394,7 @@ card_key_available (ctrl_t ctrl, const struct card_key_info_s *keyinfo, *cardsn = NULL; /* Read the public key. */ - err = agent_card_readkey (ctrl, keyinfo->keygrip, &pkbuf); + err = agent_card_readkey (ctrl, keyinfo->keygrip, &pkbuf, NULL); if (err) { if (opt.verbose) diff --git a/agent/command.c b/agent/command.c index 5f29adbe6..1b07600c3 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1035,7 +1035,7 @@ cmd_readkey (assuan_context_t ctx, char *line) goto leave; } - rc = agent_card_readkey (ctrl, keyid, &pkbuf); + rc = agent_card_readkey (ctrl, keyid, &pkbuf, NULL); if (rc) goto leave; pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); diff --git a/agent/divert-scd.c b/agent/divert-scd.c index de072e629..d8076d158 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -46,11 +46,16 @@ ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info, *r_kid = NULL; bin2hex (grip, 20, hexgrip); - err = parse_shadow_info (shadow_info, &want_sn, NULL, NULL); - if (err) - return err; + if (shadow_info) + { + err = parse_shadow_info (shadow_info, &want_sn, NULL, NULL); + if (err) + return err; + } + else + want_sn = NULL; - len = strlen (want_sn); + len = want_sn? strlen (want_sn) : 0; if (len == 32 && !strncmp (want_sn, "D27600012401", 12)) { /* This is an OpenPGP card - reformat */ @@ -90,7 +95,9 @@ ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info, } } - if (asprintf (&desc, + if (!want_sn) + ; /* No shadow info so we can't ask; ERR is already set. */ + else if (asprintf (&desc, "%s:%%0A%%0A" " %s", L_("Please insert the card with serial number"), @@ -407,6 +414,9 @@ getpin_cb (void *opaque, const char *desc_text, const char *info, * smartcard. DESC_TEXT is the original text for a prompt has send by * gpg to gpg-agent. * + * Note: If SHADOW_INFO is NULL the user can't be asked to insert the + * card, we simply try to use an inserted card with the given keygrip. + * * FIXME: Explain the other args. */ int divert_pksign (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, @@ -424,6 +434,8 @@ divert_pksign (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, rc = ask_for_card (ctrl, shadow_info, grip, &kid); if (rc) return rc; + /* Note that the KID may be an keyref or a keygrip. The signing + * functions handle both. */ if (algo == MD_USER_TLS_MD5SHA1) { diff --git a/agent/findkey.c b/agent/findkey.c index 370050d8b..69c90b37f 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1165,6 +1165,8 @@ key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list, list = gcry_sexp_find_token (s_key, "protected-private-key", 0 ); if (!list) list = gcry_sexp_find_token (s_key, "private-key", 0 ); + if (!list) + list = gcry_sexp_find_token (s_key, "public-key", 0 ); if (!list) { log_error ("invalid private key format\n"); diff --git a/agent/learncard.c b/agent/learncard.c index f40f5ac4d..678ff9b96 100644 --- a/agent/learncard.c +++ b/agent/learncard.c @@ -401,7 +401,7 @@ agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force) continue; /* The key is already available. */ /* Unknown key - store it. */ - rc = agent_card_readkey (ctrl, item->id, &pubkey); + rc = agent_card_readkey (ctrl, item->id, &pubkey, NULL); if (rc) { log_debug ("agent_card_readkey failed: %s\n", gpg_strerror (rc)); diff --git a/agent/pksign.c b/agent/pksign.c index 4a43b09de..8e88deecc 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -294,6 +294,7 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, gcry_sexp_t s_hash = NULL; gcry_sexp_t s_pkey = NULL; unsigned char *shadow_info = NULL; + int no_shadow_info = 0; const unsigned char *data; int datalen; int check_signature = 0; @@ -315,16 +316,19 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, err = agent_key_from_file (ctrl, cache_nonce, desc_text, ctrl->keygrip, &shadow_info, cache_mode, lookup_ttl, &s_skey, NULL); - if (err) + if (gpg_err_code (err) == GPG_ERR_NO_SECKEY) + no_shadow_info = 1; + else if (err) { - if (gpg_err_code (err) != GPG_ERR_NO_SECKEY) - log_error ("failed to read the secret key\n"); + log_error ("failed to read the secret key\n"); goto leave; } - if (shadow_info) + if (shadow_info || no_shadow_info) { - /* Divert operation to the smartcard */ + /* Divert operation to the smartcard. With NO_SHADOW_INFO set + * we don't have the keystub but we want to see whether the key + * is on the active card. */ size_t len; unsigned char *buf = NULL; int key_type; @@ -332,18 +336,65 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, int is_ECDSA = 0; int is_EdDSA = 0; - err = agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey); - if (err) + if (no_shadow_info) { - log_error ("failed to read the public key\n"); - goto leave; + /* Try to get the public key from the card or fail with the + * original NO_SECKEY error. We also write a stub file (we + * are here only because no stub exists). */ + char *serialno; + unsigned char *pkbuf = NULL; + size_t pkbuflen; + char hexgrip[2*KEYGRIP_LEN+1]; + char *keyref; + + if (agent_card_serialno (ctrl, &serialno, NULL)) + { + /* No card availabale or error reading the card. */ + err = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + bin2hex (ctrl->keygrip, KEYGRIP_LEN, hexgrip); + if (agent_card_readkey (ctrl, hexgrip, &pkbuf, &keyref)) + { + /* No such key on the card. */ + xfree (serialno); + err = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); + err = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen); + if (err) + { + xfree (serialno); + xfree (pkbuf); + xfree (keyref); + log_error ("%s: corrupted key returned by scdaemon\n", __func__); + goto leave; + } + + if (keyref) + agent_write_shadow_key (ctrl->keygrip, serialno, keyref, pkbuf, 0); + + xfree (serialno); + xfree (pkbuf); + xfree (keyref); + } + else + { + /* Get the public key from the stub file. */ + err = agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey); + if (err) + { + log_error ("failed to read the public key\n"); + goto leave; + } } - if (agent_is_eddsa_key (s_skey)) + if (agent_is_eddsa_key (s_pkey)) is_EdDSA = 1; else { - key_type = agent_is_dsa_key (s_skey); + key_type = agent_is_dsa_key (s_pkey); if (key_type == 0) is_RSA = 1; else if (key_type == GCRY_PK_ECDSA) @@ -354,7 +405,7 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, char *desc2 = NULL; if (desc_text) - agent_modify_description (desc_text, NULL, s_skey, &desc2); + agent_modify_description (desc_text, NULL, s_pkey, &desc2); err = divert_pksign (ctrl, desc2? desc2 : desc_text, ctrl->keygrip, @@ -444,7 +495,7 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, } else { - /* No smartcard, but a private key */ + /* No smartcard, but a private key (in S_SKEY). */ int dsaalgo = 0; /* Put the hash into a sexp */ @@ -494,7 +545,8 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, /* Check that the signature verification worked and nothing is * fooling us e.g. by a bug in the signature create code or by * deliberately introduced faults. Because Libgcrypt 1.7 does this - * for RSA internally there is no need to do it here again. */ + * for RSA internally there is no need to do it here again. We do + * this always for card based RSA keys, though. */ if (check_signature) { gcry_sexp_t sexp_key = s_pkey? s_pkey: s_skey;