Dirk-Willem van Gulik
2015-Mar-17 16:41 UTC
Locking down a specific Identity on a smartcard
Using a ~/.ssh/config such as: Host ... IdentitiesOnly yes IdentityFile ?. file ? one can limit/precisely control what Identities are flashed at the server. Which can be useful in settings where it is desirable to not (be forced) show ?what you have?; which is increasingly of interest; especially in environment where the keys are somewhat fleeting and one does not quite own the stack all the way down to the board. One can do the same with: Host ... PKCS11Provider /.../opensc-pkcs11.so IdentitiesOnly yes IdentityFile /tmp/null to lock things down to a smartcart. Or rather, to all smartcards/PKCS11 provided keys on the system (all are then tried). Below patch allows for specifications such as: PKCS11Provider c00d71ce3be7ebf2960aa5efc7fb5a841713280a@/Library/OpenSC/lib/opensc-pkcs11.so IdentitiesOnly yes IdentityFile /tmp/null where there PKCS11Provider is <keyid> @ <path to DLL>; and <keyid> is the one as per PKCS11; along with a few tweaks in the verbose/debug output which makes it easier to understand what is (not) happening in the case of PKCS11. So one can lock things down a bit better; and PKCS11Provider can be repeated at global and at per-host level*. Is that a workable syntax ? Or a really bad idea (I found the alternative (my first attempt): overloading the IdentityFile is messier; as PKCS11Provider is sort of a special IdentityFile in itself). Would love to hear folks their thoughts. Dw. *: This also allows for things such as smartcards with multiple keys; some protected by a PIN which can only be entered in HW, some allowing a keyboard entered PIN and some without it; useful in combination with command=. diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c index b053332..246c61b 100644 --- a/ssh-pkcs11.c +++ b/ssh-pkcs11.c @@ -193,27 +193,28 @@ static int pkcs11_find(struct pkcs11_provider *p, CK_ULONG slotidx, CK_ATTRIBUTE *attr, CK_ULONG nattr, CK_OBJECT_HANDLE *obj) { CK_FUNCTION_LIST *f; CK_SESSION_HANDLE session; CK_ULONG nfound = 0; CK_RV rv; int ret = -1; f = p->function_list; session = p->slotinfo[slotidx].session; if ((rv = f->C_FindObjectsInit(session, attr, nattr)) != CKR_OK) { error("C_FindObjectsInit failed (nattr %lu): %lu", nattr, rv); return (-1); } if ((rv = f->C_FindObjects(session, obj, 1, &nfound)) != CKR_OK || nfound != 1) { debug("C_FindObjects failed (nfound %lu nattr %lu): %lu", nfound, nattr, rv); } else ret = 0; if ((rv = f->C_FindObjectsFinal(session)) != CKR_OK) error("C_FindObjectsFinal failed: %lu", rv); + return (ret); } /* openssl callback doing the actual signing operation */ @@ -221,83 +222,88 @@ static int pkcs11_rsa_private_encrypt(int flen, const u_char *from, u_char *to, RSA *rsa, int padding) { struct pkcs11_key *k11; struct pkcs11_slotinfo *si; CK_FUNCTION_LIST *f; CK_OBJECT_HANDLE obj; CK_ULONG tlen = 0; CK_RV rv; CK_OBJECT_CLASS private_key_class = CKO_PRIVATE_KEY; CK_BBOOL true_val = CK_TRUE; CK_MECHANISM mech = { CKM_RSA_PKCS, NULL_PTR, 0 }; CK_ATTRIBUTE key_filter[] = { {CKA_CLASS, NULL, sizeof(private_key_class) }, {CKA_ID, NULL, 0}, {CKA_SIGN, NULL, sizeof(true_val) } }; char *pin, prompt[1024]; int rval = -1; key_filter[0].pValue = &private_key_class; key_filter[2].pValue = &true_val; if ((k11 = RSA_get_app_data(rsa)) == NULL) { error("RSA_get_app_data failed for rsa %p", rsa); return (-1); } if (!k11->provider || !k11->provider->valid) { error("no pkcs11 (valid) provider for rsa %p", rsa); return (-1); } f = k11->provider->function_list; si = &k11->provider->slotinfo[k11->slotidx]; + if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) { if (!pkcs11_interactive) { error("need pin%s", (si->token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) ? " entry on reader keypad" : ""); return (-1); } if (si->token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) { verbose("Deferring PIN entry to keypad of chipcard reader."); pin = NULL; } else { snprintf(prompt, sizeof(prompt), "Enter PIN for '%s': ", si->token.label); pin = read_passphrase(prompt, RP_ALLOW_EOF); if (pin == NULL) return (-1); /* bail out */ }; rv = f->C_Login(si->session, CKU_USER, - (u_char *)pin, strlen(pin)); + (u_char *)pin, pin ? strlen(pin) : 0); + if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN) { if (pin) free(pin); error("C_Login failed: %lu", rv); return (-1); } if (pin) free(pin); si->logged_in = 1; } key_filter[1].pValue = k11->keyid; key_filter[1].ulValueLen = k11->keyid_len; + /* try to find object w/CKA_SIGN first, retry w/o */ if (pkcs11_find(k11->provider, k11->slotidx, key_filter, 3, &obj) < 0 && pkcs11_find(k11->provider, k11->slotidx, key_filter, 2, &obj) < 0) { - error("cannot find private key"); + char * hexid = tohex(k11->keyid,k11->keyid_len); + error("cannot find pkcs private key id %s", hexid); + free(hexid); } else if ((rv = f->C_SignInit(si->session, &mech, obj)) != CKR_OK) { error("C_SignInit failed: %lu", rv); } else { /* XXX handle CKR_BUFFER_TOO_SMALL */ tlen = RSA_size(rsa); rv = f->C_Sign(si->session, (CK_BYTE *)from, flen, to, &tlen); if (rv == CKR_OK) rval = tlen; else error("C_Sign failed: %lu", rv); } return (rval); } @@ -402,32 +408,34 @@ static int pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx, struct sshkey ***keysp, int *nkeys) { CK_OBJECT_CLASS pubkey_class = CKO_PUBLIC_KEY; CK_OBJECT_CLASS cert_class = CKO_CERTIFICATE; CK_ATTRIBUTE pubkey_filter[] = { { CKA_CLASS, NULL, sizeof(pubkey_class) } }; CK_ATTRIBUTE cert_filter[] = { { CKA_CLASS, NULL, sizeof(cert_class) } }; CK_ATTRIBUTE pubkey_attribs[] = { { CKA_ID, NULL, 0 }, { CKA_MODULUS, NULL, 0 }, - { CKA_PUBLIC_EXPONENT, NULL, 0 } + { CKA_PUBLIC_EXPONENT, NULL, 0 }, + { CKA_LABEL, NULL, 0 } }; CK_ATTRIBUTE cert_attribs[] = { { CKA_ID, NULL, 0 }, { CKA_SUBJECT, NULL, 0 }, - { CKA_VALUE, NULL, 0 } + { CKA_VALUE, NULL, 0 }, + { CKA_LABEL, NULL, 0 } }; pubkey_filter[0].pValue = &pubkey_class; cert_filter[0].pValue = &cert_class; if (pkcs11_fetch_keys_filter(p, slotidx, pubkey_filter, pubkey_attribs, keysp, nkeys) < 0 || pkcs11_fetch_keys_filter(p, slotidx, cert_filter, cert_attribs, keysp, nkeys) < 0) return (-1); return (0); } @@ -444,112 +452,132 @@ pkcs11_key_included(struct sshkey ***keysp, int *nkeys, struct sshkey *key) static int pkcs11_fetch_keys_filter(struct pkcs11_provider *p, CK_ULONG slotidx, - CK_ATTRIBUTE filter[], CK_ATTRIBUTE attribs[3], + CK_ATTRIBUTE filter[], CK_ATTRIBUTE attribs[4], struct sshkey ***keysp, int *nkeys) { struct sshkey *key; RSA *rsa; X509 *x509; EVP_PKEY *evp; int i; const u_char *cp; CK_RV rv; CK_OBJECT_HANDLE obj; CK_ULONG nfound; CK_SESSION_HANDLE session; CK_FUNCTION_LIST *f; f = p->function_list; session = p->slotinfo[slotidx].session; /* setup a filter the looks for public keys */ if ((rv = f->C_FindObjectsInit(session, filter, 1)) != CKR_OK) { error("C_FindObjectsInit failed: %lu", rv); return (-1); } while (1) { - /* XXX 3 attributes in attribs[] */ - for (i = 0; i < 3; i++) { + /* XXX 4 attributes in attribs[] */ + for (i = 0; i < 4; i++) { attribs[i].pValue = NULL; attribs[i].ulValueLen = 0; } if ((rv = f->C_FindObjects(session, &obj, 1, &nfound)) != CKR_OK || nfound == 0) break; /* found a key, so figure out size of the attributes */ - if ((rv = f->C_GetAttributeValue(session, obj, attribs, 3)) + if ((rv = f->C_GetAttributeValue(session, obj, attribs, 4)) != CKR_OK) { error("C_GetAttributeValue failed: %lu", rv); continue; } - /* check that none of the attributes are zero length */ + /* check that none of the attributes that matter are zero length */ if (attribs[0].ulValueLen == 0 || attribs[1].ulValueLen == 0 || attribs[2].ulValueLen == 0) { continue; } /* allocate buffers for attributes */ - for (i = 0; i < 3; i++) + for (i = 0; i < 4; i++) attribs[i].pValue = xmalloc(attribs[i].ulValueLen); /* * retrieve ID, modulus and public exponent of RSA key, * or ID, subject and value for certificates. */ rsa = NULL; - if ((rv = f->C_GetAttributeValue(session, obj, attribs, 3)) + if ((rv = f->C_GetAttributeValue(session, obj, attribs, 4)) != CKR_OK) { error("C_GetAttributeValue failed: %lu", rv); } else if (attribs[1].type == CKA_MODULUS ) { if ((rsa = RSA_new()) == NULL) { error("RSA_new failed"); } else { rsa->n = BN_bin2bn(attribs[1].pValue, attribs[1].ulValueLen, NULL); rsa->e = BN_bin2bn(attribs[2].pValue, attribs[2].ulValueLen, NULL); } } else { cp = attribs[2].pValue; if ((x509 = X509_new()) == NULL) { error("X509_new failed"); } else if (d2i_X509(&x509, &cp, attribs[2].ulValueLen) == NULL) { error("d2i_X509 failed"); } else if ((evp = X509_get_pubkey(x509)) == NULL || evp->type != EVP_PKEY_RSA || evp->pkey.rsa == NULL) { debug("X509_get_pubkey failed or no rsa"); } else if ((rsa = RSAPublicKey_dup(evp->pkey.rsa)) == NULL) { error("RSAPublicKey_dup"); } if (x509) X509_free(x509); } if (rsa && rsa->n && rsa->e && pkcs11_rsa_wrap(p, slotidx, &attribs[0], rsa) == 0) { key = sshkey_new(KEY_UNSPEC); key->rsa = rsa; key->type = KEY_RSA; key->flags |= SSHKEY_FLAG_EXT; + + char * hex = tohex(attribs[0].pValue, (int) attribs[0].ulValueLen); + + char label[128 + 2 + 3 + 1] = ""; + if (attribs[3].ulValueLen > 0) { + unsigned long len = MIN(128, attribs[3].ulValueLen); + + strcat(label," <"); + strncpy(label+2, attribs[3].pValue, len); + if (len == 128) + strcat(label,"..."); + strcat(label,">"); + }; + if (pkcs11_key_included(keysp, nkeys, key)) { + debug("Ignorng key %s%s (%p), already included", hex, label, key); sshkey_free(key); + } + else if (index(p->name,'@') && strncmp(hex,p->name,strlen(hex))) { + debug("Ignoring key %s%s (%p), not explicitly listed", hex, label, key); } else { /* expand key array and add key */ *keysp = xrealloc(*keysp, *nkeys + 1, sizeof(struct sshkey *)); (*keysp)[*nkeys] = key; *nkeys = *nkeys + 1; - debug("have %d keys", *nkeys); + + debug("Key %d: %s%s (%p)", *nkeys, hex, label, key); } + free(hex); } else if (rsa) { RSA_free(rsa); } - for (i = 0; i < 3; i++) + for (i = 0; i < 4; i++) free(attribs[i].pValue); } if ((rv = f->C_FindObjectsFinal(session)) != CKR_OK) error("C_FindObjectsFinal failed: %lu", rv); return (0); } /* register a new provider, fails if provider already exists */ @@ -557,96 +585,101 @@ int pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp) { int nkeys, need_finalize = 0; struct pkcs11_provider *p = NULL; void *handle = NULL; CK_RV (*getfunctionlist)(CK_FUNCTION_LIST **); CK_RV rv; CK_FUNCTION_LIST *f = NULL; CK_TOKEN_INFO *token; CK_ULONG i; + char *dll_filename = provider_id; *keyp = NULL; if (pkcs11_provider_lookup(provider_id) != NULL) { error("provider already registered: %s", provider_id); goto fail; } + + if (index(provider_id,'@')) + dll_filename = index(provider_id,'@') + 1; + /* open shared pkcs11-libarary */ - if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) { + if ((handle = dlopen(dll_filename, RTLD_NOW)) == NULL) { error("dlopen %s failed: %s", provider_id, dlerror()); goto fail; } if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL) { error("dlsym(C_GetFunctionList) failed: %s", dlerror()); goto fail; } p = xcalloc(1, sizeof(*p)); p->name = xstrdup(provider_id); p->handle = handle; /* setup the pkcs11 callbacks */ if ((rv = (*getfunctionlist)(&f)) != CKR_OK) { error("C_GetFunctionList failed: %lu", rv); goto fail; } p->function_list = f; if ((rv = f->C_Initialize(NULL)) != CKR_OK) { error("C_Initialize failed: %lu", rv); goto fail; } need_finalize = 1; if ((rv = f->C_GetInfo(&p->info)) != CKR_OK) { error("C_GetInfo failed: %lu", rv); goto fail; } rmspace(p->info.manufacturerID, sizeof(p->info.manufacturerID)); rmspace(p->info.libraryDescription, sizeof(p->info.libraryDescription)); debug("manufacturerID <%s> cryptokiVersion %d.%d" " libraryDescription <%s> libraryVersion %d.%d", p->info.manufacturerID, p->info.cryptokiVersion.major, p->info.cryptokiVersion.minor, p->info.libraryDescription, p->info.libraryVersion.major, p->info.libraryVersion.minor); if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) { error("C_GetSlotList failed: %lu", rv); goto fail; } if (p->nslots == 0) { error("no slots"); goto fail; } p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID)); if ((rv = f->C_GetSlotList(CK_TRUE, p->slotlist, &p->nslots)) != CKR_OK) { error("C_GetSlotList failed: %lu", rv); goto fail; } p->slotinfo = xcalloc(p->nslots, sizeof(struct pkcs11_slotinfo)); p->valid = 1; nkeys = 0; for (i = 0; i < p->nslots; i++) { token = &p->slotinfo[i].token; if ((rv = f->C_GetTokenInfo(p->slotlist[i], token)) != CKR_OK) { error("C_GetTokenInfo failed: %lu", rv); continue; } rmspace(token->label, sizeof(token->label)); rmspace(token->manufacturerID, sizeof(token->manufacturerID)); rmspace(token->model, sizeof(token->model)); rmspace(token->serialNumber, sizeof(token->serialNumber)); debug("label <%s> manufacturerID <%s> model <%s> serial <%s>" " flags 0x%lx", token->label, token->manufacturerID, token->model, token->serialNumber, token->flags); /* open session, login with pin and retrieve public keys */ if (pkcs11_open_session(p, i, pin) == 0) pkcs11_fetch_keys(p, i, keyp, &nkeys); } if (nkeys > 0) { TAILQ_INSERT_TAIL(&pkcs11_providers, p, next); p->refcount++; /* add to provider list */ return (nkeys); } error("no keys"); /* don't add the provider, since it does not have any keys */ diff --git a/sshconnect2.c b/sshconnect2.c index ba56f64..fd664b1 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1319,54 +1319,54 @@ int userauth_pubkey(Authctxt *authctxt) { Identity *id; int sent = 0; while ((id = TAILQ_FIRST(&authctxt->keys))) { if (id->tried++) return (0); /* move key to the end of the queue */ TAILQ_REMOVE(&authctxt->keys, id, next); TAILQ_INSERT_TAIL(&authctxt->keys, id, next); /* * send a test message if we have the public key. for * encrypted keys we cannot do this and have to load the * private key instead */ if (id->key != NULL) { if (key_type_plain(id->key->type) == KEY_RSA && (datafellows & SSH_BUG_RSASIGMD5) != 0) { debug("Skipped %s key %s for RSA/MD5 server", key_type(id->key), id->filename); } else if (id->key->type != KEY_RSA1) { - debug("Offering %s public key: %s", - key_type(id->key), id->filename); + debug("Offering %s public key: %s (%p)", + key_type(id->key), id->filename, id->key); sent = send_pubkey_test(authctxt, id); } } else { debug("Trying private key: %s", id->filename); id->key = load_identity_file(id->filename, id->userprovided); if (id->key != NULL) { id->isprivate = 1; if (key_type_plain(id->key->type) == KEY_RSA && (datafellows & SSH_BUG_RSASIGMD5) != 0) { debug("Skipped %s key %s for RSA/MD5 " "server", key_type(id->key), id->filename); } else { sent = sign_and_send_pubkey( authctxt, id); } key_free(id->key); id->key = NULL; } } if (sent) return (sent); } return (0); } /* * Send userauth request message specifying keyboard-interactive method. */