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