Hello, I found a few issues regarding the openpgp card implementation (tested with opensc-0.10.1).
1.) The openpgp card (http://www.g10code.de/p-card.html) stores 3 pairs of RSA-Keys on a card but no certs. OpenSSH needs a cert on the card (only to get the public-key), it ignores crls, valid time settings and other informations stored in a cert. I wrote a patch for OpenSSH (tested with openssh-4.2p1 and openssh-4.3p2) which works in combination with opensc for openpgp and maybe other cards. After applying this patch openssh tests for the insertion of an openpgp card. If such a card is found then openssh searchs for information about all stored private keys. Only those private keys are valid which have an according public key and all these key pairs are used for openssh. It works well in openssh for authentication purposes. The ssh-agent works too. 2.) In the opensc-0.10.1 (maybe 0.11 too, not checked yet) implementation of card-openpgp.c seems to be a problem in pgp_compute_signature. The line "apdu.le = 256" allows a response of 256 Bytes. But if the buffer "out" given to pgp_compute_signature has an "outlen" less then 256 Bytes (openssh only uses a smaller size) then sc_check_apdu called by sc_transmit_apdu fails because ... case SC_APDU_CASE_4_SHORT: if (apdu->resplen < apdu->le) { sc_error(ctx, "Le > response buffer size\n"); ... The second patch modifies card-openpgp.c to set apdu.le=outlen. I don't know if this cause problems somewhere else. Tests with openssh shows that it works well. 3.) A problem for which I cannot provide a fix until now in openssh is the storage of the pin in the ssh-agent. Public keys are readable without verification. If somebody calls "ssh-add -s XXX" he is asked for the pin, but the pin isn't verified because the public-keys, which the agent stores to offer them to a remote ssh-server, are readable without the correct pin. If the user connects to a remote server and the agent has a valid key, then the remote server sends the challenge which has to be signed by the card. The agent sends the request to the card and uses the stored pin. In case of a false pin, this request fails instead of the ssh-add command. This is currently under investigation. Regards Sandro Wefel
--- openssh-4.2p1-suse-patched/scard-opensc.c +++ openssh-4.2p1-suse-saw-patched/scard-opensc.c @@ -39,6 +39,8 @@ #include "scard.h" int ask_for_pin=0; +// SAW: +static int is_openpgp=0; #if OPENSSL_VERSION_NUMBER < 0x00907000L && defined(CRYPTO_LOCK_ENGINE) #define USE_ENGINE @@ -98,11 +100,23 @@ goto err; } r = sc_connect_card(ctx->reader[sc_reader_id], 0, &card); + // Sa.W: What happens if the card isn't in slot 0 ? + // A usefull error message would be nice. if (r) goto err; r = sc_pkcs15_bind(card, &p15card); if (r) goto err; + + // Test for OpenPGP Card without cert + // FIXME: Maybe there are other cards with the same problem, + // so a test for cards without certs could be nifty. + if (!strncmp(card->driver->short_name, "openpgp", 7)) { + is_openpgp = 1; + } else { + is_openpgp = 0; + } + return 0; err: sc_close(); @@ -250,8 +264,18 @@ * for authentication is not recommended. Note: This does not * prevent the use of a non-repudiation key for authentication * if the sign or signrecover flag is set as well. - */ - r = sc_prkey_op_init(rsa, &key_obj, SC_USAGE_SIGN); + */ + /* SAW: the problem is, that the openpgp-card uses key 3 (id 03) + * as "authentication key" but has only set the nonRepudiation + * flag. Key 1 (id 01) has the sign flag and may be a candidate + * but due to the label "signature key" it is IMHO not suitable for + * authentication purposes. SSH uses sc_sign for authentication. + * FIXME : Change for openpgp, but I'm not sure. */ + if(is_openpgp) + r = sc_prkey_op_init(rsa, &key_obj, SC_USAGE_SIGN | \ + SC_PKCS15_PRKEY_USAGE_NONREPUDIATION); + else + r = sc_prkey_op_init(rsa, &key_obj, SC_USAGE_SIGN); if (r) return -1; /* FIXME: length of sigret correct? */ @@ -429,6 +453,55 @@ return r; } + +static int +sc_read_pubkey_from_card_without_cert(Key * k, + const struct sc_pkcs15_object *pub_key_obj) +{ + int r; + struct sc_priv_data *priv = NULL; + u8 *p; + char *tmp; + sc_pkcs15_pubkey_t *pubkey = NULL; + + r = sc_pkcs15_read_pubkey(p15card, pub_key_obj, &pubkey); + if (r) { + error("sc_pkcs15_read_pubkey failed: %s", sc_strerror(r)); + goto err; + } + + p = pubkey->data.value; + if (!d2i_RSAPublicKey(&k->rsa, &p, pubkey->data.len)) { + logit("Unable to parse public key"); + r = -1; + goto err; + } + + sc_pkcs15_free_pubkey(pubkey); + k->rsa->flags |= RSA_FLAG_SIGN_VER; + + RSA_set_method(k->rsa, sc_get_rsa_method()); + priv = xmalloc(sizeof(struct sc_priv_data)); + // we don't have the id of the cert, but the id of the priv-key + // and use it like a cert_id + // sc_read_pubkey also stores the the privkey-id here + priv->cert_id = ((sc_pkcs15_pubkey_info_t *)pub_key_obj->data)->id; + priv->ref_count = 1; + RSA_set_app_data(k->rsa, priv); + + k->flags = KEY_FLAG_EXT; + tmp = key_fingerprint(k, SSH_FP_MD5, SSH_FP_HEX); + debug("fingerprint %d %s", key_size(k), tmp); + xfree(tmp); + + return 0; +err: + if(pubkey) + sc_pkcs15_free_pubkey(pubkey); + return r; + +} + Key ** sc_get_keys(const char *id, const char *pin) { @@ -438,6 +511,9 @@ sc_pkcs15_object_t *certs[32]; char *buf = xstrdup(id), *p; + sc_pkcs15_object_t *prkeys[32]; + sc_pkcs15_id_t pubkey_id; + debug("sc_get_keys called: id = %s", id); if (sc_pin != NULL) @@ -462,9 +538,30 @@ goto err; } } - if (cert_id.len) { + + if (is_openpgp) { + /* SaW: It's an OpenPGP card without certs. + * SSH needs the public key only. Modify the code + * that openpgp cards are searched only for pairs + * of public and private keys. + */ + r = sc_pkcs15_get_objects(p15card, SC_PKCS15_TYPE_PRKEY_RSA, + prkeys, 32); + if (r == 0) { + error("No RSA private key found on smartcard!"); + r = -1; + goto err; + } else if (r < 0) { + error("Key enumeration failed: %s", + sc_strerror(r)); + goto err; + } + key_count = r; + } + else if (cert_id.len) { r = sc_pkcs15_find_cert_by_id(p15card, &cert_id, &certs[0]); if (r < 0) + //FIXME: How about a useful error message? goto err; key_count = 1; } else { @@ -484,15 +581,31 @@ keys = xmalloc(sizeof(Key *) * (key_count*2+1)); for (i = 0; i < key_count; i++) { sc_pkcs15_object_t *tmp_obj = NULL; - cert_id = ((sc_pkcs15_cert_info_t *)(certs[i]->data))->id; - if (sc_pkcs15_find_prkey_by_id(p15card, &cert_id, &tmp_obj)) - /* skip the public key (certificate) if no - * corresponding private key is present */ - continue; + sc_pkcs15_object_t *pub_key_obj = NULL; + + if(is_openpgp) { + // prkeys[i] store the infos of the private key + // skip the keypair if no appropriate public key is present + pubkey_id = ((sc_pkcs15_prkey_info_t *) prkeys[i]->data)->id; + if (sc_pkcs15_find_pubkey_by_id(p15card, &pubkey_id, &pub_key_obj)){ + logit("No public key for %s", prkeys[i]->label); + continue; + } + } else { + cert_id = ((sc_pkcs15_cert_info_t *)(certs[i]->data))->id; + if (sc_pkcs15_find_prkey_by_id(p15card, &cert_id, &tmp_obj)) + // skip the public key (certificate) if no + // corresponding private key is present + continue; + // FIXME: error message ? + } k = key_new(KEY_RSA); if (k == NULL) break; - r = sc_read_pubkey(k, certs[i]); + if(is_openpgp) + r = sc_read_pubkey_from_card_without_cert(k, pub_key_obj); + else + r = sc_read_pubkey(k, certs[i]); if (r) { error("sc_read_pubkey failed: %s", sc_strerror(r)); key_free(k); @@ -527,7 +640,7 @@ { int r; const struct sc_priv_data *priv; - struct sc_pkcs15_object *key_obj; + struct sc_pkcs15_object *key_obj = NULL; priv = (const struct sc_priv_data *) RSA_get_app_data(key->rsa); if (priv == NULL || p15card == NULL) {
--- opensc-0.10.1-org/src/libopensc/card-openpgp.c +++ opensc-0.10.1-saw/src/libopensc/card-openpgp.c @@ -614,7 +614,24 @@ apdu.lc = data_len; apdu.data = data; apdu.datalen = data_len; - apdu.le = 256; + /* FIXME: apdu.le has to be less or equal than the size of the + * response buffer "out". The size outlen is stored in + * apdu.resplen. + * Due to the line + * apdu.le = 256 + * the output buffer has to be 256 bytes at least. + * But the signature could be less then 256 bytes, + * e.g. in signature function calls from openssh. + * http://g10code.com/docs/openpgp-card-1.1.pdf + * This documentation of openpgp-cards says that the + * apdu.le is zero for signature command but this cause + * a failure of sc_check_apdu. + * Therefore apdu.le is set to outlen. Tests with an + * openpgp card version 1.1 runnung PROTO_T1 seems to + * work well. + */ + apdu.le = outlen; + sc_debug(card->ctx, "saw: modified source"); apdu.resp = out; apdu.resplen = outlen; @@ -689,7 +705,11 @@ static int pgp_logout(sc_card_t *card) { - sc_error(card->ctx, "OpenPGP card: logout not supported\n"); + /* sc_error(card->ctx, "OpenPGP card: logout not supported\n"); + * FIXME: this cause a screen fullfilled with error messages :-) + * Make it only a debug output to use this cards really. + */ + sc_debug(card->ctx, "OpenPGP card: logout not supported\n"); return SC_ERROR_NOT_SUPPORTED; }
_______________________________________________ opensc-devel mailing list opensc-devel@lists.opensc-project.org http://www.opensc-project.org/mailman/listinfo/opensc-devel