Locking down a specific Identity on a smartcard
Dirk-Willem van Gulik
dirkx at webweaving.org
Wed Mar 18 03:41:05 AEDT 2015
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.
*/
More information about the openssh-unix-dev
mailing list