jonathancammack / rpms / openssh

Forked from rpms/openssh 8 months ago
Clone
3e8b5b
diff --git a/Makefile.in b/Makefile.in
3e8b5b
index 6f001bb3..c9424f1e 100644
3e8b5b
--- a/Makefile.in
3e8b5b
+++ b/Makefile.in
3e8b5b
@@ -93,7 +93,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
3e8b5b
 	atomicio.o dispatch.o mac.o uuencode.o misc.o utf8.o \
3e8b5b
 	monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-rsa.o dh.o \
3e8b5b
 	msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \
3e8b5b
-	ssh-pkcs11.o smult_curve25519_ref.o \
3e8b5b
+	ssh-pkcs11.o ssh-pkcs11-uri.o smult_curve25519_ref.o \
3e8b5b
 	poly1305.o chacha.o cipher-chachapoly.o \
3e8b5b
 	ssh-ed25519.o digest-openssl.o digest-libc.o hmac.o \
3e8b5b
 	sc25519.o ge25519.o fe25519.o ed25519.o verify.o hash.o \
3e8b5b
@@ -250,6 +250,8 @@ clean:	regressclean
3e8b5b
 	rm -f regress/unittests/match/test_match$(EXEEXT)
3e8b5b
 	rm -f regress/unittests/utf8/*.o
3e8b5b
 	rm -f regress/unittests/utf8/test_utf8$(EXEEXT)
3e8b5b
+	rm -f regress/unittests/pkcs11/*.o
3e8b5b
+	rm -f regress/unittests/pkcs11/test_pkcs11$(EXEEXT)
3e8b5b
 	rm -f regress/misc/kexfuzz/*.o
3e8b5b
 	rm -f regress/misc/kexfuzz/kexfuzz$(EXEEXT)
3e8b5b
 	(cd openbsd-compat && $(MAKE) clean)
3e8b5b
@@ -280,6 +282,8 @@ distclean:	regressclean
3e8b5b
 	rm -f regress/unittests/match/test_match
3e8b5b
 	rm -f regress/unittests/utf8/*.o
3e8b5b
 	rm -f regress/unittests/utf8/test_utf8
3e8b5b
+	rm -f regress/unittests/pkcs11/*.o
3e8b5b
+	rm -f regress/unittests/pkcs11/test_pkcs11
3e8b5b
 	rm -f regress/misc/kexfuzz/*.o
3e8b5b
 	rm -f regress/misc/kexfuzz/kexfuzz$(EXEEXT)
3e8b5b
 	(cd openbsd-compat && $(MAKE) distclean)
3e8b5b
@@ -442,6 +446,7 @@ regress-prep:
3e8b5b
 	$(MKDIR_P) `pwd`/regress/unittests/kex
3e8b5b
 	$(MKDIR_P) `pwd`/regress/unittests/match
3e8b5b
 	$(MKDIR_P) `pwd`/regress/unittests/utf8
3e8b5b
+	$(MKDIR_P) `pwd`/regress/unittests/pkcs11
3e8b5b
 	$(MKDIR_P) `pwd`/regress/misc/kexfuzz
3e8b5b
 	[ -f `pwd`/regress/Makefile ] || \
3e8b5b
 	    ln -s `cd $(srcdir) && pwd`/regress/Makefile `pwd`/regress/Makefile
3e8b5b
@@ -565,6 +570,16 @@ regress/unittests/utf8/test_utf8$(EXEEXT): \
3e8b5b
 	    regress/unittests/test_helper/libtest_helper.a \
3e8b5b
 	    -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS)
3e8b5b
 
3e8b5b
+UNITTESTS_TEST_PKCS11_OBJS=\
3e8b5b
+	regress/unittests/pkcs11/tests.o
3e8b5b
+
3e8b5b
+regress/unittests/pkcs11/test_pkcs11$(EXEEXT): \
3e8b5b
+    ${UNITTESTS_TEST_PKCS11_OBJS} \
3e8b5b
+    regress/unittests/test_helper/libtest_helper.a libssh.a
3e8b5b
+	$(LD) -o $@ $(LDFLAGS) $(UNITTESTS_TEST_PKCS11_OBJS) \
3e8b5b
+	    regress/unittests/test_helper/libtest_helper.a \
3e8b5b
+	    -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS)
3e8b5b
+
3e8b5b
 MISC_KEX_FUZZ_OBJS=\
3e8b5b
 	regress/misc/kexfuzz/kexfuzz.o
3e8b5b
 
3e8b5b
@@ -585,6 +600,7 @@ regress-binaries: regress/modpipe$(EXEEXT) \
3e8b5b
 	regress/unittests/kex/test_kex$(EXEEXT) \
3e8b5b
 	regress/unittests/match/test_match$(EXEEXT) \
3e8b5b
 	regress/unittests/utf8/test_utf8$(EXEEXT) \
3e8b5b
+	regress/unittests/pkcs11/test_pkcs11$(EXEEXT) \
3e8b5b
 	regress/misc/kexfuzz/kexfuzz$(EXEEXT)
3e8b5b
 
3e8b5b
 tests interop-tests t-exec unit: regress-prep regress-binaries $(TARGETS)
3e8b5b
diff --git a/authfd.c b/authfd.c
3e8b5b
index 95348abf..5383df92 100644
3e8b5b
--- a/authfd.c
3e8b5b
+++ b/authfd.c
3e8b5b
@@ -312,6 +312,8 @@ ssh_free_identitylist(struct ssh_identitylist *idl)
3e8b5b
 		if (idl->comments != NULL)
3e8b5b
 			free(idl->comments[i]);
3e8b5b
 	}
3e8b5b
+	free(idl->keys);
3e8b5b
+	free(idl->comments);
3e8b5b
 	free(idl);
3e8b5b
 }
3e8b5b
 
3e8b5b
diff --git a/configure.ac b/configure.ac
3e8b5b
index 30be6c18..82459746 100644
3e8b5b
--- a/configure.ac
3e8b5b
+++ b/configure.ac
3e8b5b
@@ -1854,12 +1854,14 @@ AC_LINK_IFELSE(
3e8b5b
 	[AC_DEFINE([HAVE_ISBLANK], [1], [Define if you have isblank(3C).])
3e8b5b
 ])
3e8b5b
 
3e8b5b
+SCARD_MSG="yes"
3e8b5b
 disable_pkcs11=
3e8b5b
 AC_ARG_ENABLE([pkcs11],
3e8b5b
 	[  --disable-pkcs11        disable PKCS#11 support code [no]],
3e8b5b
 	[
3e8b5b
 		if test "x$enableval" = "xno" ; then
3e8b5b
 			disable_pkcs11=1
3e8b5b
+			SCARD_MSG="no"
3e8b5b
 		fi
3e8b5b
 	]
3e8b5b
 )
3e8b5b
@@ -1875,6 +1877,40 @@ if test "x$openssl" = "xyes" && test "x$disable_pkcs11" = "x"; then
3e8b5b
 	)
3e8b5b
 fi
3e8b5b
 
3e8b5b
+# Check whether we have a p11-kit, we got default provider on command line
3e8b5b
+DEFAULT_PKCS11_PROVIDER_MSG="no"
3e8b5b
+AC_ARG_WITH([default-pkcs11-provider],
3e8b5b
+	[  --with-default-pkcs11-provider[[=PATH]]   Use default pkcs11 provider (p11-kit detected by default)],
3e8b5b
+	[ if test "x$withval" != "xno" && test "x$disable_pkcs11" = "x"; then
3e8b5b
+		if test "x$withval" = "xyes" ; then
3e8b5b
+			AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no])
3e8b5b
+			if test "x$PKGCONFIG" != "xno"; then
3e8b5b
+				AC_MSG_CHECKING([if $PKGCONFIG knows about p11-kit])
3e8b5b
+				if "$PKGCONFIG" "p11-kit-1"; then
3e8b5b
+					AC_MSG_RESULT([yes])
3e8b5b
+					use_pkgconfig_for_p11kit=yes
3e8b5b
+				else
3e8b5b
+					AC_MSG_RESULT([no])
3e8b5b
+				fi
3e8b5b
+			fi
3e8b5b
+		else
3e8b5b
+			PKCS11_PATH="${withval}"
3e8b5b
+		fi
3e8b5b
+		if test "x$use_pkgconfig_for_p11kit" = "xyes"; then
3e8b5b
+			PKCS11_PATH=`$PKGCONFIG --variable=proxy_module p11-kit-1`
3e8b5b
+		fi
3e8b5b
+		AC_CHECK_FILE("$PKCS11_PATH",
3e8b5b
+			[ AC_DEFINE_UNQUOTED([PKCS11_DEFAULT_PROVIDER], ["$PKCS11_PATH"], [Path to default PKCS#11 provider (p11-kit proxy)])
3e8b5b
+			  DEFAULT_PKCS11_PROVIDER_MSG="$PKCS11_PATH"
3e8b5b
+			],
3e8b5b
+			[ AC_MSG_ERROR([Requested PKCS11 provided not found]) ]
3e8b5b
+		)
3e8b5b
+	else
3e8b5b
+		AC_MSG_WARN([Needs PKCS11 support to enable default pkcs11 provider])
3e8b5b
+	fi ]
3e8b5b
+)
3e8b5b
+
3e8b5b
+
3e8b5b
 # IRIX has a const char return value for gai_strerror()
3e8b5b
 AC_CHECK_FUNCS([gai_strerror], [
3e8b5b
 	AC_DEFINE([HAVE_GAI_STRERROR])
3e8b5b
@@ -5256,6 +5292,7 @@ echo "           Translate v4 in v6 hack: $IPV4_IN6_HACK_MSG"
3e8b5b
 echo "                  BSD Auth support: $BSD_AUTH_MSG"
3e8b5b
 echo "              Random number source: $RAND_MSG"
3e8b5b
 echo "             Privsep sandbox style: $SANDBOX_STYLE"
3e8b5b
+echo "          Default PKCS#11 provider: $DEFAULT_PKCS11_PROVIDER_MSG"
3e8b5b
 
3e8b5b
 echo ""
3e8b5b
 
3e8b5b
diff --git a/regress/Makefile b/regress/Makefile
3e8b5b
index 925edf71..94bb25e9 100644
3e8b5b
--- a/regress/Makefile
3e8b5b
+++ b/regress/Makefile
3e8b5b
@@ -109,9 +109,11 @@ CLEANFILES=	*.core actual agent-key.* authorized_keys_${USERNAME} \
3e8b5b
 		known_hosts known_hosts-cert known_hosts.* krl-* ls.copy \
3e8b5b
 		modpipe netcat no_identity_config \
3e8b5b
 		pidfile putty.rsa2 ready regress.log \
3e8b5b
-		remote_pid revoked-* rsa rsa-agent rsa-agent.pub rsa.pub \
3e8b5b
+		remote_pid revoked-* rsa rsa-agent rsa-agent.pub \
3e8b5b
+		rsa-agent-cert.pub rsa.pub \
3e8b5b
 		rsa1 rsa1-agent rsa1-agent.pub rsa1.pub rsa_ssh2_cr.prv \
3e8b5b
-		rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \
3e8b5b
+		pkcs11*.crt pkcs11*.key \
3e8b5b
+		pkcs11.info rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \
3e8b5b
 		scp-ssh-wrapper.scp setuid-allowed sftp-server.log \
3e8b5b
 		sftp-server.sh sftp.log ssh-log-wrapper.sh ssh.log \
3e8b5b
 		ssh_config ssh_config.* ssh_proxy ssh_proxy_bak \
3e8b5b
@@ -231,6 +233,7 @@ unit:
3e8b5b
 		V="" ; \
3e8b5b
 		test "x${USE_VALGRIND}" = "x" || \
3e8b5b
 		    V=${.CURDIR}/valgrind-unit.sh ; \
3e8b5b
+		$$V ${.OBJDIR}/unittests/pkcs11/test_pkcs11 ; \
3e8b5b
 		$$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf ; \
3e8b5b
 		$$V ${.OBJDIR}/unittests/sshkey/test_sshkey \
3e8b5b
 			-d ${.CURDIR}/unittests/sshkey/testdata ; \
3e8b5b
diff --git a/regress/agent-pkcs11.sh b/regress/agent-pkcs11.sh
3e8b5b
index 5205d906..5ca49be5 100644
3e8b5b
--- a/regress/agent-pkcs11.sh
3e8b5b
+++ b/regress/agent-pkcs11.sh
3e8b5b
@@ -29,6 +29,13 @@ fi
3e8b5b
 
3e8b5b
 test -f "$TEST_SSH_PKCS11" || fatal "$TEST_SSH_PKCS11 does not exist"
3e8b5b
 
3e8b5b
+# requires ssh-agent built with correct path to ssh-pkcs11-helper
3e8b5b
+# otherwise it fails to start the helper
3e8b5b
+strings ${TEST_SSH_SSHAGENT} | grep "$TEST_SSH_SSHPKCS11HELPER"
3e8b5b
+if [ $? -ne 0 ]; then
3e8b5b
+	fatal "Needs to reconfigure with --libexecdir=\`pwd\` or so"
3e8b5b
+fi
3e8b5b
+
3e8b5b
 # setup environment for softhsm2 token
3e8b5b
 DIR=$OBJ/SOFTHSM
3e8b5b
 rm -rf $DIR
3e8b5b
@@ -113,7 +120,7 @@ else
3e8b5b
 	done
3e8b5b
 
3e8b5b
 	trace "remove pkcs11 keys"
3e8b5b
-	echo ${TEST_SSH_PIN} | notty ${SSHADD} -e ${TEST_SSH_PKCS11} > /dev/null 2>&1
3e8b5b
+	${SSHADD} -e ${TEST_SSH_PKCS11} > /dev/null 2>&1
3e8b5b
 	r=$?
3e8b5b
 	if [ $r -ne 0 ]; then
3e8b5b
 		fail "ssh-add -e failed: exit code $r"
3e8b5b
diff --git a/regress/pkcs11.sh b/regress/pkcs11.sh
3e8b5b
new file mode 100644
3e8b5b
index 00000000..19fc8169
3e8b5b
--- /dev/null
3e8b5b
+++ b/regress/pkcs11.sh
3e8b5b
@@ -0,0 +1,352 @@
3e8b5b
+#
3e8b5b
+#  Copyright (c) 2017 Red Hat
3e8b5b
+#
3e8b5b
+#  Authors: Jakub Jelen <jjelen@redhat.com>
3e8b5b
+#
3e8b5b
+#  Permission to use, copy, modify, and distribute this software for any
3e8b5b
+#  purpose with or without fee is hereby granted, provided that the above
3e8b5b
+#  copyright notice and this permission notice appear in all copies.
3e8b5b
+#
3e8b5b
+#  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
3e8b5b
+#  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
3e8b5b
+#  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
3e8b5b
+#  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
3e8b5b
+#  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
3e8b5b
+#  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
3e8b5b
+#  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
3e8b5b
+
3e8b5b
+tid="pkcs11 tests with soft token"
3e8b5b
+
3e8b5b
+try_token_libs() {
3e8b5b
+	for _lib in "$@" ; do
3e8b5b
+		if test -f "$_lib" ; then
3e8b5b
+			verbose "Using token library $_lib"
3e8b5b
+			TEST_SSH_PKCS11="$_lib"
3e8b5b
+			return
3e8b5b
+		fi
3e8b5b
+	done
3e8b5b
+	echo "skipped: Unable to find PKCS#11 token library"
3e8b5b
+	exit 0
3e8b5b
+}
3e8b5b
+
3e8b5b
+try_token_libs \
3e8b5b
+	/usr/local/lib/softhsm/libsofthsm2.so \
3e8b5b
+	/usr/lib64/pkcs11/libsofthsm2.so \
3e8b5b
+	/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so
3e8b5b
+
3e8b5b
+TEST_SSH_PIN=1234
3e8b5b
+TEST_SSH_SOPIN=12345678
3e8b5b
+if [ "x$TEST_SSH_SSHPKCS11HELPER" != "x" ]; then
3e8b5b
+	SSH_PKCS11_HELPER="${TEST_SSH_SSHPKCS11HELPER}"
3e8b5b
+	export SSH_PKCS11_HELPER
3e8b5b
+fi
3e8b5b
+
3e8b5b
+test -f "$TEST_SSH_PKCS11" || fatal "$TEST_SSH_PKCS11 does not exist"
3e8b5b
+
3e8b5b
+# requires ssh-agent built with correct path to ssh-pkcs11-helper
3e8b5b
+# otherwise it fails to start the helper
3e8b5b
+strings ${TEST_SSH_SSHAGENT} | grep "$TEST_SSH_SSHPKCS11HELPER"
3e8b5b
+if [ $? -ne 0 ]; then
3e8b5b
+	fatal "Needs to reconfigure with --libexecdir=\`pwd\` or so"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+# setup environment for softhsm token
3e8b5b
+DIR=$OBJ/SOFTHSM
3e8b5b
+rm -rf $DIR
3e8b5b
+TOKEN=$DIR/tokendir
3e8b5b
+mkdir -p $TOKEN
3e8b5b
+SOFTHSM2_CONF=$DIR/softhsm2.conf
3e8b5b
+export SOFTHSM2_CONF
3e8b5b
+cat > $SOFTHSM2_CONF << EOF
3e8b5b
+# SoftHSM v2 configuration file
3e8b5b
+directories.tokendir = ${TOKEN}
3e8b5b
+objectstore.backend = file
3e8b5b
+# ERROR, WARNING, INFO, DEBUG
3e8b5b
+log.level = DEBUG
3e8b5b
+# If CKF_REMOVABLE_DEVICE flag should be set
3e8b5b
+slots.removable = false
3e8b5b
+EOF
3e8b5b
+out=$(softhsm2-util --init-token --free --label token-slot-0 --pin "$TEST_SSH_PIN" --so-pin "$TEST_SSH_SOPIN")
3e8b5b
+slot=$(echo -- $out | sed 's/.* //')
3e8b5b
+
3e8b5b
+# prevent ssh-agent from calling ssh-askpass
3e8b5b
+SSH_ASKPASS=/usr/bin/true
3e8b5b
+export SSH_ASKPASS
3e8b5b
+unset DISPLAY
3e8b5b
+# We need interactive access to test PKCS# since it prompts for PIN
3e8b5b
+sed -i 's/.*BatchMode.*//g' $OBJ/ssh_proxy
3e8b5b
+
3e8b5b
+# start command w/o tty, so ssh accepts pin from stdin (from agent-pkcs11.sh)
3e8b5b
+notty() {
3e8b5b
+	perl -e 'use POSIX; POSIX::setsid();
3e8b5b
+	    if (fork) { wait; exit($? >> 8); } else { exec(@ARGV) }' "$@"
3e8b5b
+}
3e8b5b
+
3e8b5b
+trace "generating keys"
3e8b5b
+ID1="02"
3e8b5b
+ID2="04"
3e8b5b
+RSA=${DIR}/RSA
3e8b5b
+EC=${DIR}/EC
3e8b5b
+openssl genpkey -algorithm rsa > $RSA
3e8b5b
+openssl pkcs8 -nocrypt -in $RSA |\
3e8b5b
+    softhsm2-util --slot "$slot" --label "SSH RSA Key $ID1" --id $ID1 \
3e8b5b
+	--pin "$TEST_SSH_PIN" --import /dev/stdin
3e8b5b
+openssl genpkey \
3e8b5b
+    -genparam \
3e8b5b
+    -algorithm ec \
3e8b5b
+    -pkeyopt ec_paramgen_curve:prime256v1 |\
3e8b5b
+    openssl genpkey \
3e8b5b
+    -paramfile /dev/stdin > $EC
3e8b5b
+openssl pkcs8 -nocrypt -in $EC |\
3e8b5b
+    softhsm2-util --slot "$slot" --label "SSH ECDSA Key $ID2" --id $ID2 \
3e8b5b
+	--pin "$TEST_SSH_PIN" --import /dev/stdin
3e8b5b
+
3e8b5b
+trace "List the keys in the ssh-keygen with PKCS#11 URIs"
3e8b5b
+${SSHKEYGEN} -D ${TEST_SSH_PKCS11} > $OBJ/token_keys
3e8b5b
+if [ $? -ne 0 ]; then
3e8b5b
+	fail "keygen fails to enumerate keys on PKCS#11 token"
3e8b5b
+fi
3e8b5b
+grep "pkcs11:" $OBJ/token_keys > /dev/null
3e8b5b
+if [ $? -ne 0 ]; then
3e8b5b
+	fail "The keys from ssh-keygen do not contain PKCS#11 URI as a comment"
3e8b5b
+fi
3e8b5b
+tail -n 1 $OBJ/token_keys > $OBJ/authorized_keys_$USER
3e8b5b
+
3e8b5b
+trace "Simple connect with ssh (without PKCS#11 URI)"
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -I ${TEST_SSH_PKCS11} \
3e8b5b
+    -F $OBJ/ssh_proxy somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -ne 5 ]; then
3e8b5b
+	fail "ssh connect with pkcs11 failed (exit code $r)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+trace "Connect with PKCS#11 URI"
3e8b5b
+trace "  (second key should succeed)"
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
3e8b5b
+    -i "pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -ne 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI failed (exit code $r)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+trace "  (first key should fail)"
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
3e8b5b
+     -i "pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -eq 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI succeeded (should fail)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+trace "Connect with PKCS#11 URI including PIN should not prompt"
3e8b5b
+trace "  (second key should succeed)"
3e8b5b
+${SSH} -F $OBJ/ssh_proxy -i \
3e8b5b
+    "pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -ne 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI failed (exit code $r)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+trace "  (first key should fail)"
3e8b5b
+${SSH} -F $OBJ/ssh_proxy -i \
3e8b5b
+    "pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -eq 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI succeeded (should fail)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+trace "Connect with various filtering options in PKCS#11 URI"
3e8b5b
+trace "  (by object label, second key should succeed)"
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
3e8b5b
+    -i "pkcs11:object=SSH%20RSA%20Key%202?module-path=${TEST_SSH_PKCS11}" somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -ne 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI failed (exit code $r)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+trace "  (by object label, first key should fail)"
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
3e8b5b
+     -i "pkcs11:object=SSH%20RSA%20Key?module-path=${TEST_SSH_PKCS11}" somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -eq 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI succeeded (should fail)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+trace "  (by token label, second key should succeed)"
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
3e8b5b
+    -i "pkcs11:id=${ID2};token=SoftToken%20(token)?module-path=${TEST_SSH_PKCS11}" somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -ne 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI failed (exit code $r)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+trace "  (by wrong token label, should fail)"
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
3e8b5b
+     -i "pkcs11:token=SoftToken?module-path=${TEST_SSH_PKCS11}" somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -eq 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI succeeded (should fail)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+
3e8b5b
+
3e8b5b
+
3e8b5b
+trace "Test PKCS#11 URI specification in configuration files"
3e8b5b
+echo "IdentityFile \"pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}\"" \
3e8b5b
+    >> $OBJ/ssh_proxy
3e8b5b
+trace "  (second key should succeed)"
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -ne 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI in config failed (exit code $r)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+trace "  (first key should fail)"
3e8b5b
+head -n 1 $OBJ/token_keys > $OBJ/authorized_keys_$USER
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -eq 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI in config succeeded (should fail)"
3e8b5b
+fi
3e8b5b
+sed -i -e "/IdentityFile/d" $OBJ/ssh_proxy
3e8b5b
+
3e8b5b
+trace "Test PKCS#11 URI specification in configuration files with bogus spaces"
3e8b5b
+echo "IdentityFile \"    pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11}    \"" \
3e8b5b
+    >> $OBJ/ssh_proxy
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -ne 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI with bogus spaces in config failed" \
3e8b5b
+	    "(exit code $r)"
3e8b5b
+fi
3e8b5b
+sed -i -e "/IdentityFile/d" $OBJ/ssh_proxy
3e8b5b
+
3e8b5b
+
3e8b5b
+trace "Combination of PKCS11Provider and PKCS11URI on commandline"
3e8b5b
+trace "  (first key should succeed)"
3e8b5b
+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
3e8b5b
+    -i "pkcs11:id=${ID1}" -I ${TEST_SSH_PKCS11} somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -ne 5 ]; then
3e8b5b
+	fail "ssh connect with PKCS#11 URI and provider combination" \
3e8b5b
+	    "failed (exit code $r)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+trace "Regress: Missing provider in PKCS11URI option"
3e8b5b
+${SSH} -F $OBJ/ssh_proxy \
3e8b5b
+    -o IdentityFile=\"pkcs11:token=segfault\" somehost exit 5
3e8b5b
+r=$?
3e8b5b
+if [ $r -eq 139 ]; then
3e8b5b
+	fail "ssh connect with missing provider_id from configuration option" \
3e8b5b
+	    "crashed (exit code $r)"
3e8b5b
+fi
3e8b5b
+
3e8b5b
+
3e8b5b
+trace "SSH Agent can work with PKCS#11 URI"
3e8b5b
+trace "start the agent"
3e8b5b
+eval `${SSHAGENT} -s -P "${OBJ}/*"` > /dev/null
3e8b5b
+
3e8b5b
+r=$?
3e8b5b
+if [ $r -ne 0 ]; then
3e8b5b
+	fail "could not start ssh-agent: exit code $r"
3e8b5b
+else
3e8b5b
+	trace "add whole provider to agent"
3e8b5b
+	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
3e8b5b
+	    "pkcs11:?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 0 ]; then
3e8b5b
+		fail "ssh-add failed with whole provider: exit code $r"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace " pkcs11 list via agent (all keys)"
3e8b5b
+	${SSHADD} -l > /dev/null 2>&1
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 0 ]; then
3e8b5b
+		fail "ssh-add -l failed with whole provider: exit code $r"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace " pkcs11 connect via agent (all keys)"
3e8b5b
+	${SSH} -F $OBJ/ssh_proxy somehost exit 5
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 5 ]; then
3e8b5b
+		fail "ssh connect failed with whole provider (exit code $r)"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace " remove pkcs11 keys (all keys)"
3e8b5b
+	${SSHADD} -d "pkcs11:?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 0 ]; then
3e8b5b
+		fail "ssh-add -d failed with whole provider: exit code $r"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace "add only first key to the agent"
3e8b5b
+	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
3e8b5b
+	    "pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 0 ]; then
3e8b5b
+		fail "ssh-add failed with first key: exit code $r"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace " pkcs11 connect via agent (first key)"
3e8b5b
+	${SSH} -F $OBJ/ssh_proxy somehost exit 5
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 5 ]; then
3e8b5b
+		fail "ssh connect failed with first key (exit code $r)"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace " remove first pkcs11 key"
3e8b5b
+	${SSHADD} -d "pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11}" \
3e8b5b
+	    > /dev/null 2>&1
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 0 ]; then
3e8b5b
+		fail "ssh-add -d failed with first key: exit code $r"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace "add only second key to the agent"
3e8b5b
+	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
3e8b5b
+	    "pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 0 ]; then
3e8b5b
+		fail "ssh-add failed with second key: exit code $r"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace " pkcs11 connect via agent (second key should fail)"
3e8b5b
+	${SSH} -F $OBJ/ssh_proxy somehost exit 5
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -eq 5 ]; then
3e8b5b
+		fail "ssh connect passed without key (should fail)"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace "add also the first key to the agent"
3e8b5b
+	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
3e8b5b
+	    "pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 0 ]; then
3e8b5b
+		fail "ssh-add failed with first key: exit code $r"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace " remove second pkcs11 key"
3e8b5b
+	${SSHADD} -d "pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}" \
3e8b5b
+	    > /dev/null 2>&1
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 0 ]; then
3e8b5b
+		fail "ssh-add -d failed with second key: exit code $r"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace " remove already-removed pkcs11 key should fail"
3e8b5b
+	${SSHADD} -d "pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}" \
3e8b5b
+	    > /dev/null 2>&1
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -eq 0 ]; then
3e8b5b
+		fail "ssh-add -d passed with non-existing key (should fail)"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace " pkcs11 connect via agent (the first key should be still usable)"
3e8b5b
+	${SSH} -F $OBJ/ssh_proxy somehost exit 5
3e8b5b
+	r=$?
3e8b5b
+	if [ $r -ne 5 ]; then
3e8b5b
+		fail "ssh connect failed with first key (after removing second): exit code $r"
3e8b5b
+	fi
3e8b5b
+
3e8b5b
+	trace "kill agent"
3e8b5b
+	${SSHAGENT} -k > /dev/null
3e8b5b
+fi
3e8b5b
diff --git a/regress/unittests/Makefile b/regress/unittests/Makefile
3e8b5b
index e464b085..a0e5a37c 100644
3e8b5b
--- a/regress/unittests/Makefile
3e8b5b
+++ b/regress/unittests/Makefile
3e8b5b
@@ -2,6 +2,6 @@
3e8b5b
 
3e8b5b
 REGRESS_FAIL_EARLY?=	yes
3e8b5b
 SUBDIR=	test_helper sshbuf sshkey bitmap kex hostkeys utf8 match conversion
3e8b5b
-SUBDIR+=authopt
3e8b5b
+SUBDIR+=pkcs11 authopt
3e8b5b
 
3e8b5b
 .include <bsd.subdir.mk>
3e8b5b
diff --git a/regress/unittests/pkcs11/tests.c b/regress/unittests/pkcs11/tests.c
3e8b5b
new file mode 100644
3e8b5b
index 00000000..b637cb13
3e8b5b
--- /dev/null
3e8b5b
+++ b/regress/unittests/pkcs11/tests.c
3e8b5b
@@ -0,0 +1,337 @@
3e8b5b
+/*
3e8b5b
+ * Copyright (c) 2017 Red Hat
3e8b5b
+ *
3e8b5b
+ * Authors: Jakub Jelen <jjelen@redhat.com>
3e8b5b
+ *
3e8b5b
+ * Permission to use, copy, modify, and distribute this software for any
3e8b5b
+ * purpose with or without fee is hereby granted, provided that the above
3e8b5b
+ * copyright notice and this permission notice appear in all copies.
3e8b5b
+ *
3e8b5b
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
3e8b5b
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
3e8b5b
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
3e8b5b
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
3e8b5b
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
3e8b5b
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
3e8b5b
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
3e8b5b
+ */
3e8b5b
+
3e8b5b
+#include "includes.h"
3e8b5b
+
3e8b5b
+#include <locale.h>
3e8b5b
+#include <string.h>
3e8b5b
+
3e8b5b
+#include "../test_helper/test_helper.h"
3e8b5b
+
3e8b5b
+#include "sshbuf.h"
3e8b5b
+#include "ssh-pkcs11-uri.h"
3e8b5b
+
3e8b5b
+#define EMPTY_URI compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL)
3e8b5b
+
3e8b5b
+/* prototypes are not public -- specify them here internally for tests */
3e8b5b
+struct sshbuf *percent_encode(const char *, size_t, char *);
3e8b5b
+int percent_decode(char *, char **);
3e8b5b
+
3e8b5b
+void
3e8b5b
+compare_uri(struct pkcs11_uri *a, struct pkcs11_uri *b)
3e8b5b
+{
3e8b5b
+	ASSERT_PTR_NE(a, NULL);
3e8b5b
+	ASSERT_PTR_NE(b, NULL);
3e8b5b
+	ASSERT_SIZE_T_EQ(a->id_len, b->id_len);
3e8b5b
+	ASSERT_MEM_EQ(a->id, b->id, a->id_len);
3e8b5b
+	if (b->object != NULL)
3e8b5b
+		ASSERT_STRING_EQ(a->object, b->object);
3e8b5b
+	else /* both should be null */
3e8b5b
+		ASSERT_PTR_EQ(a->object, b->object);
3e8b5b
+	if (b->module_path != NULL)
3e8b5b
+		ASSERT_STRING_EQ(a->module_path, b->module_path);
3e8b5b
+	else /* both should be null */
3e8b5b
+		ASSERT_PTR_EQ(a->module_path, b->module_path);
3e8b5b
+	if (b->token != NULL)
3e8b5b
+		ASSERT_STRING_EQ(a->token, b->token);
3e8b5b
+	else /* both should be null */
3e8b5b
+		ASSERT_PTR_EQ(a->token, b->token);
3e8b5b
+	if (b->manuf != NULL)
3e8b5b
+		ASSERT_STRING_EQ(a->manuf, b->manuf);
3e8b5b
+	else /* both should be null */
3e8b5b
+		ASSERT_PTR_EQ(a->manuf, b->manuf);
3e8b5b
+	if (b->lib_manuf != NULL)
3e8b5b
+		ASSERT_STRING_EQ(a->lib_manuf, b->lib_manuf);
3e8b5b
+	else /* both should be null */
3e8b5b
+		ASSERT_PTR_EQ(a->lib_manuf, b->lib_manuf);
3e8b5b
+}
3e8b5b
+
3e8b5b
+void
3e8b5b
+check_parse_rv(char *uri, struct pkcs11_uri *expect, int expect_rv)
3e8b5b
+{
3e8b5b
+	char *buf = NULL, *str;
3e8b5b
+	struct pkcs11_uri *pkcs11uri = NULL;
3e8b5b
+	int rv;
3e8b5b
+
3e8b5b
+	if (expect_rv == 0)
3e8b5b
+		str = "Valid";
3e8b5b
+	else
3e8b5b
+		str = "Invalid";
3e8b5b
+	asprintf(&buf, "%s PKCS#11 URI parsing: %s", str, uri);
3e8b5b
+	TEST_START(buf);
3e8b5b
+	free(buf);
3e8b5b
+	pkcs11uri = pkcs11_uri_init();
3e8b5b
+	rv = pkcs11_uri_parse(uri, pkcs11uri);
3e8b5b
+	ASSERT_INT_EQ(rv, expect_rv);
3e8b5b
+	if (rv == 0) /* in case of failure result is undefined */
3e8b5b
+		compare_uri(pkcs11uri, expect);
3e8b5b
+	pkcs11_uri_cleanup(pkcs11uri);
3e8b5b
+	free(expect);
3e8b5b
+	TEST_DONE();
3e8b5b
+}
3e8b5b
+
3e8b5b
+void
3e8b5b
+check_parse(char *uri, struct pkcs11_uri *expect)
3e8b5b
+{
3e8b5b
+	check_parse_rv(uri, expect, 0);
3e8b5b
+}
3e8b5b
+
3e8b5b
+struct pkcs11_uri *
3e8b5b
+compose_uri(unsigned char *id, size_t id_len, char *token, char *lib_manuf,
3e8b5b
+    char *manuf, char *module_path, char *object, char *pin)
3e8b5b
+{
3e8b5b
+	struct pkcs11_uri *uri = pkcs11_uri_init();
3e8b5b
+	if (id_len > 0) {
3e8b5b
+		uri->id_len = id_len;
3e8b5b
+		uri->id = id;
3e8b5b
+	}
3e8b5b
+	uri->module_path = module_path;
3e8b5b
+	uri->token = token;
3e8b5b
+	uri->lib_manuf = lib_manuf;
3e8b5b
+	uri->manuf = manuf;
3e8b5b
+	uri->object = object;
3e8b5b
+	uri->pin = pin;
3e8b5b
+	return uri;
3e8b5b
+}
3e8b5b
+
3e8b5b
+static void
3e8b5b
+test_parse_valid(void)
3e8b5b
+{
3e8b5b
+	/* path arguments */
3e8b5b
+	check_parse("pkcs11:id=%01",
3e8b5b
+	    compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL, NULL));
3e8b5b
+	check_parse("pkcs11:id=%00%01",
3e8b5b
+	    compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL));
3e8b5b
+	check_parse("pkcs11:token=SSH%20Keys",
3e8b5b
+	    compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL));
3e8b5b
+	check_parse("pkcs11:library-manufacturer=OpenSC",
3e8b5b
+	    compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL));
3e8b5b
+	check_parse("pkcs11:manufacturer=piv_II",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL));
3e8b5b
+	check_parse("pkcs11:object=SIGN%20Key",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "SIGN Key", NULL));
3e8b5b
+	/* query arguments */
3e8b5b
+	check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
3e8b5b
+	check_parse("pkcs11:?pin-value=123456",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, "123456"));
3e8b5b
+
3e8b5b
+	/* combinations */
3e8b5b
+	/* ID SHOULD be percent encoded */
3e8b5b
+	check_parse("pkcs11:token=SSH%20Key;id=0",
3e8b5b
+	    compose_uri("0", 1, "SSH Key", NULL, NULL, NULL, NULL, NULL));
3e8b5b
+	check_parse(
3e8b5b
+	    "pkcs11:manufacturer=CAC?module-path=/usr/lib64/p11-kit-proxy.so",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, "CAC",
3e8b5b
+	    "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
3e8b5b
+	check_parse(
3e8b5b
+	    "pkcs11:object=RSA%20Key?module-path=/usr/lib64/pkcs11/opencryptoki.so",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, NULL,
3e8b5b
+	    "/usr/lib64/pkcs11/opencryptoki.so", "RSA Key", NULL));
3e8b5b
+	check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so&pin-value=123456",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, "123456"));
3e8b5b
+
3e8b5b
+	/* empty path component matches everything */
3e8b5b
+	check_parse("pkcs11:", EMPTY_URI);
3e8b5b
+
3e8b5b
+	/* empty string is a valid to match against (and different from NULL) */
3e8b5b
+	check_parse("pkcs11:token=",
3e8b5b
+	    compose_uri(NULL, 0, "", NULL, NULL, NULL, NULL, NULL));
3e8b5b
+	/* Percent character needs to be percent-encoded */
3e8b5b
+	check_parse("pkcs11:token=%25",
3e8b5b
+	     compose_uri(NULL, 0, "%", NULL, NULL, NULL, NULL, NULL));
3e8b5b
+}
3e8b5b
+
3e8b5b
+static void
3e8b5b
+test_parse_invalid(void)
3e8b5b
+{
3e8b5b
+	/* Invalid percent encoding */
3e8b5b
+	check_parse_rv("pkcs11:id=%0", EMPTY_URI, -1);
3e8b5b
+	/* Invalid percent encoding */
3e8b5b
+	check_parse_rv("pkcs11:id=%ZZ", EMPTY_URI, -1);
3e8b5b
+	/* Space MUST be percent encoded -- XXX not enforced yet */
3e8b5b
+	check_parse("pkcs11:token=SSH Keys",
3e8b5b
+	    compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL));
3e8b5b
+	/* MUST NOT contain duplicate attributes of the same name */
3e8b5b
+	check_parse_rv("pkcs11:id=%01;id=%02", EMPTY_URI, -1);
3e8b5b
+	/* MUST NOT contain duplicate attributes of the same name */
3e8b5b
+	check_parse_rv("pkcs11:?pin-value=111111&pin-value=123456", EMPTY_URI, -1);
3e8b5b
+	/* Unrecognized attribute in path are ignored with log message */
3e8b5b
+	check_parse("pkcs11:key_name=SSH", EMPTY_URI);
3e8b5b
+	/* Unrecognized attribute in query SHOULD be ignored */
3e8b5b
+	check_parse("pkcs11:?key_name=SSH", EMPTY_URI);
3e8b5b
+}
3e8b5b
+
3e8b5b
+void
3e8b5b
+check_gen(char *expect, struct pkcs11_uri *uri)
3e8b5b
+{
3e8b5b
+	char *buf = NULL, *uri_str;
3e8b5b
+
3e8b5b
+	asprintf(&buf, "Valid PKCS#11 URI generation: %s", expect);
3e8b5b
+	TEST_START(buf);
3e8b5b
+	free(buf);
3e8b5b
+	uri_str = pkcs11_uri_get(uri);
3e8b5b
+	ASSERT_PTR_NE(uri_str, NULL);
3e8b5b
+	ASSERT_STRING_EQ(uri_str, expect);
3e8b5b
+	free(uri_str);
3e8b5b
+	TEST_DONE();
3e8b5b
+}
3e8b5b
+
3e8b5b
+static void
3e8b5b
+test_generate_valid(void)
3e8b5b
+{
3e8b5b
+	/* path arguments */
3e8b5b
+	check_gen("pkcs11:id=%01",
3e8b5b
+	    compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL, NULL));
3e8b5b
+	check_gen("pkcs11:id=%00%01",
3e8b5b
+	    compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL));
3e8b5b
+	check_gen("pkcs11:token=SSH%20Keys", /* space must be percent encoded */
3e8b5b
+	    compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL));
3e8b5b
+	/* library-manufacturer is not implmented now */
3e8b5b
+	/*check_gen("pkcs11:library-manufacturer=OpenSC",
3e8b5b
+	    compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL));*/
3e8b5b
+	check_gen("pkcs11:manufacturer=piv_II",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL));
3e8b5b
+	check_gen("pkcs11:object=RSA%20Key",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "RSA Key", NULL));
3e8b5b
+	/* query arguments */
3e8b5b
+	check_gen("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
3e8b5b
+
3e8b5b
+	/* combinations */
3e8b5b
+	check_gen("pkcs11:id=%02;token=SSH%20Keys",
3e8b5b
+	    compose_uri("\x02", 1, "SSH Keys", NULL, NULL, NULL, NULL, NULL));
3e8b5b
+	check_gen("pkcs11:id=%EE%02?module-path=/usr/lib64/p11-kit-proxy.so",
3e8b5b
+	    compose_uri("\xEE\x02", 2, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
3e8b5b
+	check_gen("pkcs11:object=Encryption%20Key;manufacturer=piv_II",
3e8b5b
+	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, "Encryption Key", NULL));
3e8b5b
+
3e8b5b
+	/* empty path component matches everything */
3e8b5b
+	check_gen("pkcs11:", EMPTY_URI);
3e8b5b
+
3e8b5b
+}
3e8b5b
+
3e8b5b
+void
3e8b5b
+check_encode(char *source, size_t len, char *whitelist, char *expect)
3e8b5b
+{
3e8b5b
+	char *buf = NULL;
3e8b5b
+	struct sshbuf *b;
3e8b5b
+
3e8b5b
+	asprintf(&buf, "percent_encode: expected %s", expect);
3e8b5b
+	TEST_START(buf);
3e8b5b
+	free(buf);
3e8b5b
+
3e8b5b
+	b = percent_encode(source, len, whitelist);
3e8b5b
+	ASSERT_STRING_EQ(sshbuf_ptr(b), expect);
3e8b5b
+	sshbuf_free(b);
3e8b5b
+	TEST_DONE();
3e8b5b
+}
3e8b5b
+
3e8b5b
+static void
3e8b5b
+test_percent_encode_multibyte(void)
3e8b5b
+{
3e8b5b
+	/* SHOULD be encoded as octets according to the UTF-8 character encoding */
3e8b5b
+
3e8b5b
+	/* multi-byte characters are "for free" */
3e8b5b
+	check_encode("$", 1, "", "%24");
3e8b5b
+	check_encode("¢", 2, "", "%C2%A2");
3e8b5b
+	check_encode("€", 3, "", "%E2%82%AC");
3e8b5b
+	check_encode("𐍈", 4, "", "%F0%90%8D%88");
3e8b5b
+
3e8b5b
+	/* CK_UTF8CHAR is unsigned char (1 byte) */
3e8b5b
+	/* labels SHOULD be normalized to NFC [UAX15] */
3e8b5b
+
3e8b5b
+}
3e8b5b
+
3e8b5b
+static void
3e8b5b
+test_percent_encode(void)
3e8b5b
+{
3e8b5b
+	/* Without whitelist encodes everything (for CKA_ID) */
3e8b5b
+	check_encode("A*", 2, "", "%41%2A");
3e8b5b
+	check_encode("\x00", 1, "", "%00");
3e8b5b
+	check_encode("\x7F", 1, "", "%7F");
3e8b5b
+	check_encode("\x80", 1, "", "%80");
3e8b5b
+	check_encode("\xff", 1, "", "%FF");
3e8b5b
+
3e8b5b
+	/* Default whitelist encodes anything but safe letters */
3e8b5b
+	check_encode("test" "\x00" "0alpha", 11, PKCS11_URI_WHITELIST,
3e8b5b
+	    "test%000alpha");
3e8b5b
+	check_encode(" ", 1, PKCS11_URI_WHITELIST,
3e8b5b
+	    "%20"); /* Space MUST be percent encoded */
3e8b5b
+	check_encode("/", 1, PKCS11_URI_WHITELIST,
3e8b5b
+	    "%2F"); /* '/' delimiter MUST be percent encoded (in the path) */
3e8b5b
+	check_encode("?", 1, PKCS11_URI_WHITELIST,
3e8b5b
+	    "%3F"); /* delimiter '?' MUST be percent encoded (in the path) */
3e8b5b
+	check_encode("#", 1, PKCS11_URI_WHITELIST,
3e8b5b
+	    "%23"); /* '#' MUST be always percent encoded */
3e8b5b
+	check_encode("key=value;separator?query&#anch", 35, PKCS11_URI_WHITELIST,
3e8b5b
+	    "key%3Dvalue%3Bseparator%3Fquery%26amp%3B%23anch");
3e8b5b
+
3e8b5b
+	/* Components in query can have '/' unencoded (useful for paths) */
3e8b5b
+	check_encode("/path/to.file", 13, PKCS11_URI_WHITELIST "/",
3e8b5b
+	    "/path/to.file");
3e8b5b
+}
3e8b5b
+
3e8b5b
+void
3e8b5b
+check_decode(char *source, char *expect, int expect_len)
3e8b5b
+{
3e8b5b
+	char *buf = NULL, *out = NULL;
3e8b5b
+	int rv;
3e8b5b
+
3e8b5b
+	asprintf(&buf, "percent_decode: %s", source);
3e8b5b
+	TEST_START(buf);
3e8b5b
+	free(buf);
3e8b5b
+
3e8b5b
+	rv = percent_decode(source, &out;;
3e8b5b
+	ASSERT_INT_EQ(rv, expect_len);
3e8b5b
+	if (rv >= 0)
3e8b5b
+		ASSERT_MEM_EQ(out, expect, expect_len);
3e8b5b
+	free(out);
3e8b5b
+	TEST_DONE();
3e8b5b
+}
3e8b5b
+
3e8b5b
+static void
3e8b5b
+test_percent_decode(void)
3e8b5b
+{
3e8b5b
+	/* simple valid cases */
3e8b5b
+	check_decode("%00", "\x00", 1);
3e8b5b
+	check_decode("%FF", "\xFF", 1);
3e8b5b
+
3e8b5b
+	/* normal strings shold be kept intact */
3e8b5b
+	check_decode("strings are left", "strings are left", 16);
3e8b5b
+	check_decode("10%25 of trees", "10% of trees", 12);
3e8b5b
+
3e8b5b
+	/* make sure no more than 2 bytes are parsed */
3e8b5b
+	check_decode("%222", "\x22" "2", 2);
3e8b5b
+
3e8b5b
+	/* invalid expects failure */
3e8b5b
+	check_decode("%0", "", -1);
3e8b5b
+	check_decode("%Z", "", -1);
3e8b5b
+	check_decode("%FG", "", -1);
3e8b5b
+}
3e8b5b
+
3e8b5b
+void
3e8b5b
+tests(void)
3e8b5b
+{
3e8b5b
+	test_percent_encode();
3e8b5b
+	test_percent_encode_multibyte();
3e8b5b
+	test_percent_decode();
3e8b5b
+	test_parse_valid();
3e8b5b
+	test_parse_invalid();
3e8b5b
+	test_generate_valid();
3e8b5b
+}
3e8b5b
diff --git a/ssh-add.c b/ssh-add.c
3e8b5b
index ac9c808d..f039e00e 100644
3e8b5b
--- a/ssh-add.c
3e8b5b
+++ b/ssh-add.c
3e8b5b
@@ -64,6 +64,7 @@
3e8b5b
 #include "misc.h"
3e8b5b
 #include "ssherr.h"
3e8b5b
 #include "digest.h"
3e8b5b
+#include "ssh-pkcs11-uri.h"
3e8b5b
 
3e8b5b
 /* argv0 */
3e8b5b
 extern char *__progname;
3e8b5b
@@ -188,6 +189,24 @@ delete_all(int agent_fd, int qflag)
3e8b5b
 	return ret;
3e8b5b
 }
3e8b5b
 
3e8b5b
+#ifdef ENABLE_PKCS11
3e8b5b
+static int update_card(int, int, const char *, int);
3e8b5b
+
3e8b5b
+int
3e8b5b
+update_pkcs11_uri(int agent_fd, int adding, const char *pkcs11_uri, int qflag)
3e8b5b
+{
3e8b5b
+	struct pkcs11_uri *uri;
3e8b5b
+
3e8b5b
+	/* dry-run parse to make sure the URI is valid and to report errors */
3e8b5b
+	uri = pkcs11_uri_init();
3e8b5b
+	if (pkcs11_uri_parse((char *) pkcs11_uri, uri) != 0)
3e8b5b
+		fatal("Failed to parse PKCS#11 URI");
3e8b5b
+	pkcs11_uri_cleanup(uri);
3e8b5b
+
3e8b5b
+	return update_card(agent_fd, adding, pkcs11_uri, qflag);
3e8b5b
+}
3e8b5b
+#endif
3e8b5b
+
3e8b5b
 static int
3e8b5b
 add_file(int agent_fd, const char *filename, int key_only, int qflag)
3e8b5b
 {
3e8b5b
@@ -529,6 +548,13 @@ lock_agent(int agent_fd, int lock)
3e8b5b
 static int
3e8b5b
 do_file(int agent_fd, int deleting, int key_only, char *file, int qflag)
3e8b5b
 {
3e8b5b
+#ifdef ENABLE_PKCS11
3e8b5b
+	if (strlen(file) >= strlen(PKCS11_URI_SCHEME) &&
3e8b5b
+	    strncmp(file, PKCS11_URI_SCHEME,
3e8b5b
+	    strlen(PKCS11_URI_SCHEME)) == 0) {
3e8b5b
+		return update_pkcs11_uri(agent_fd, !deleting, file, qflag);
3e8b5b
+	}
3e8b5b
+#endif
3e8b5b
 	if (deleting) {
3e8b5b
 		if (delete_file(agent_fd, file, key_only, qflag) == -1)
3e8b5b
 			return -1;
3e8b5b
diff --git a/ssh-agent.c b/ssh-agent.c
3e8b5b
index d06ecfd9..9c1b328f 100644
3e8b5b
--- a/ssh-agent.c
3e8b5b
+++ b/ssh-agent.c
3e8b5b
@@ -548,10 +548,72 @@ no_identities(SocketEntry *e)
3e8b5b
 }
3e8b5b
 
3e8b5b
 #ifdef ENABLE_PKCS11
3e8b5b
+static char *
3e8b5b
+sanitize_pkcs11_provider(const char *provider)
3e8b5b
+{
3e8b5b
+	struct pkcs11_uri *uri = NULL;
3e8b5b
+	char *sane_uri, *module_path = NULL; /* default path */
3e8b5b
+	char canonical_provider[PATH_MAX];
3e8b5b
+
3e8b5b
+	if (provider == NULL)
3e8b5b
+		return NULL;
3e8b5b
+
3e8b5b
+	if (strlen(provider) >= strlen(PKCS11_URI_SCHEME) &&
3e8b5b
+	    strncmp(provider, PKCS11_URI_SCHEME,
3e8b5b
+	    strlen(PKCS11_URI_SCHEME)) == 0) {
3e8b5b
+		/* PKCS#11 URI */
3e8b5b
+		uri = pkcs11_uri_init();
3e8b5b
+		if (uri == NULL) {
3e8b5b
+			error("Failed to init PCKS#11 URI");
3e8b5b
+			return NULL;
3e8b5b
+		}
3e8b5b
+
3e8b5b
+		if (pkcs11_uri_parse(provider, uri) != 0) {
3e8b5b
+			error("Failed to parse PKCS#11 URI");
3e8b5b
+			return NULL;
3e8b5b
+		}
3e8b5b
+		/* validate also provider from URI */
3e8b5b
+		if (uri->module_path)
3e8b5b
+			module_path = strdup(uri->module_path);
3e8b5b
+	} else
3e8b5b
+		module_path = strdup(provider); /* simple path */
3e8b5b
+
3e8b5b
+	if (module_path != NULL) { /* do not validate default NULL path in URI */
3e8b5b
+		if (realpath(module_path, canonical_provider) == NULL) {
3e8b5b
+			verbose("failed PKCS#11 provider \"%.100s\": realpath: %s",
3e8b5b
+			    module_path, strerror(errno));
3e8b5b
+			free(module_path);
3e8b5b
+			pkcs11_uri_cleanup(uri);
3e8b5b
+			return NULL;
3e8b5b
+		}
3e8b5b
+		free(module_path);
3e8b5b
+		if (match_pattern_list(canonical_provider, pkcs11_whitelist, 0) != 1) {
3e8b5b
+			verbose("refusing PKCS#11 provider \"%.100s\": "
3e8b5b
+			    "not whitelisted", canonical_provider);
3e8b5b
+			pkcs11_uri_cleanup(uri);
3e8b5b
+			return NULL;
3e8b5b
+		}
3e8b5b
+
3e8b5b
+		/* copy verified and sanitized provider path back to the uri */
3e8b5b
+		if (uri) {
3e8b5b
+			free(uri->module_path);
3e8b5b
+			uri->module_path = xstrdup(canonical_provider);
3e8b5b
+		}
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	if (uri) {
3e8b5b
+		sane_uri = pkcs11_uri_get(uri);
3e8b5b
+		pkcs11_uri_cleanup(uri);
3e8b5b
+		return sane_uri;
3e8b5b
+	} else {
3e8b5b
+		return xstrdup(canonical_provider); /* simple path */
3e8b5b
+	}
3e8b5b
+}
3e8b5b
+
3e8b5b
 static void
3e8b5b
 process_add_smartcard_key(SocketEntry *e)
3e8b5b
 {
3e8b5b
-	char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
3e8b5b
+	char *provider = NULL, *pin = NULL, *sane_uri = NULL;
3e8b5b
 	int r, i, count = 0, success = 0, confirm = 0;
3e8b5b
 	u_int seconds;
3e8b5b
 	time_t death = 0;
3e8b5b
@@ -587,28 +649,23 @@ process_add_smartcard_key(SocketEntry *e)
3e8b5b
 			goto send;
3e8b5b
 		}
3e8b5b
 	}
3e8b5b
-	if (realpath(provider, canonical_provider) == NULL) {
3e8b5b
-		verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
3e8b5b
-		    provider, strerror(errno));
3e8b5b
-		goto send;
3e8b5b
-	}
3e8b5b
-	if (match_pattern_list(canonical_provider, pkcs11_whitelist, 0) != 1) {
3e8b5b
-		verbose("refusing PKCS#11 add of \"%.100s\": "
3e8b5b
-		    "provider not whitelisted", canonical_provider);
3e8b5b
+
3e8b5b
+	sane_uri = sanitize_pkcs11_provider(provider);
3e8b5b
+	if (sane_uri == NULL)
3e8b5b
 		goto send;
3e8b5b
-	}
3e8b5b
-	debug("%s: add %.100s", __func__, canonical_provider);
3e8b5b
+
3e8b5b
 	if (lifetime && !death)
3e8b5b
 		death = monotime() + lifetime;
3e8b5b
 
3e8b5b
-	count = pkcs11_add_provider(canonical_provider, pin, &keys);
3e8b5b
+	debug("%s: add %.100s", __func__, sane_uri);
3e8b5b
+	count = pkcs11_add_provider(sane_uri, pin, &keys);
3e8b5b
 	for (i = 0; i < count; i++) {
3e8b5b
 		k = keys[i];
3e8b5b
 		if (lookup_identity(k) == NULL) {
3e8b5b
 			id = xcalloc(1, sizeof(Identity));
3e8b5b
 			id->key = k;
3e8b5b
-			id->provider = xstrdup(canonical_provider);
3e8b5b
-			id->comment = xstrdup(canonical_provider); /* XXX */
3e8b5b
+			id->provider = xstrdup(sane_uri);
3e8b5b
+			id->comment = xstrdup(sane_uri);
3e8b5b
 			id->death = death;
3e8b5b
 			id->confirm = confirm;
3e8b5b
 			TAILQ_INSERT_TAIL(&idtab->idlist, id, next);
3e8b5b
@@ -622,6 +679,7 @@ process_add_smartcard_key(SocketEntry *e)
3e8b5b
 send:
3e8b5b
 	free(pin);
3e8b5b
 	free(provider);
3e8b5b
+	free(sane_uri);
3e8b5b
 	free(keys);
3e8b5b
 	send_status(e, success);
3e8b5b
 }
3e8b5b
@@ -629,7 +687,7 @@ send:
3e8b5b
 static void
3e8b5b
 process_remove_smartcard_key(SocketEntry *e)
3e8b5b
 {
3e8b5b
-	char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
3e8b5b
+	char *provider = NULL, *pin = NULL, *sane_uri = NULL;
3e8b5b
 	int r, success = 0;
3e8b5b
 	Identity *id, *nxt;
3e8b5b
 
3e8b5b
@@ -640,30 +698,29 @@ process_remove_smartcard_key(SocketEntry *e)
3e8b5b
 	}
3e8b5b
 	free(pin);
3e8b5b
 
3e8b5b
-	if (realpath(provider, canonical_provider) == NULL) {
3e8b5b
-		verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
3e8b5b
-		    provider, strerror(errno));
3e8b5b
+	sane_uri = sanitize_pkcs11_provider(provider);
3e8b5b
+	if (sane_uri == NULL)
3e8b5b
 		goto send;
3e8b5b
-	}
3e8b5b
 
3e8b5b
-	debug("%s: remove %.100s", __func__, canonical_provider);
3e8b5b
+	debug("%s: remove %.100s", __func__, sane_uri);
3e8b5b
 	for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) {
3e8b5b
 		nxt = TAILQ_NEXT(id, next);
3e8b5b
 		/* Skip file--based keys */
3e8b5b
 		if (id->provider == NULL)
3e8b5b
 			continue;
3e8b5b
-		if (!strcmp(canonical_provider, id->provider)) {
3e8b5b
+		if (!strcmp(sane_uri, id->provider)) {
3e8b5b
 			TAILQ_REMOVE(&idtab->idlist, id, next);
3e8b5b
 			free_identity(id);
3e8b5b
 			idtab->nentries--;
3e8b5b
 		}
3e8b5b
 	}
3e8b5b
-	if (pkcs11_del_provider(canonical_provider) == 0)
3e8b5b
+	if (pkcs11_del_provider(sane_uri) == 0)
3e8b5b
 		success = 1;
3e8b5b
 	else
3e8b5b
 		error("%s: pkcs11_del_provider failed", __func__);
3e8b5b
 send:
3e8b5b
 	free(provider);
3e8b5b
+	free(sane_uri);
3e8b5b
 	send_status(e, success);
3e8b5b
 }
3e8b5b
 #endif /* ENABLE_PKCS11 */
3e8b5b
diff --git a/ssh-keygen.c b/ssh-keygen.c
3e8b5b
index 3898b281..91c43b14 100644
3e8b5b
--- a/ssh-keygen.c
3e8b5b
+++ b/ssh-keygen.c
3e8b5b
@@ -798,6 +798,7 @@ do_download(struct passwd *pw)
3e8b5b
 			free(fp);
3e8b5b
 		} else {
3e8b5b
 			(void) sshkey_write(keys[i], stdout); /* XXX check */
3e8b5b
+			(void) pkcs11_uri_write(keys[i], stdout);
3e8b5b
 			fprintf(stdout, "\n");
3e8b5b
 		}
3e8b5b
 		sshkey_free(keys[i]);
3e8b5b
diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c
3e8b5b
index e7860de8..7b2a9115 100644
3e8b5b
--- a/ssh-pkcs11-client.c
3e8b5b
+++ b/ssh-pkcs11-client.c
3e8b5b
@@ -321,6 +321,8 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp)
3e8b5b
 	u_int nkeys, i;
3e8b5b
 	struct sshbuf *msg;
3e8b5b
 
3e8b5b
+	debug("%s: called, name = %s", __func__, name);
3e8b5b
+
3e8b5b
 	if (fd < 0 && pkcs11_start_helper() < 0)
3e8b5b
 		return (-1);
3e8b5b
 
3e8b5b
@@ -338,6 +340,7 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp)
3e8b5b
 		if ((r = sshbuf_get_u32(msg, &nkeys)) != 0)
3e8b5b
 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
3e8b5b
 		*keysp = xcalloc(nkeys, sizeof(struct sshkey *));
3e8b5b
+		debug("%s: nkeys = %u", __func__, nkeys);
3e8b5b
 		for (i = 0; i < nkeys; i++) {
3e8b5b
 			/* XXX clean up properly instead of fatal() */
3e8b5b
 			if ((r = sshbuf_get_string(msg, &blob, &blen)) != 0 ||
3e8b5b
diff --git a/ssh-pkcs11-uri.c b/ssh-pkcs11-uri.c
3e8b5b
new file mode 100644
3e8b5b
index 00000000..e1a7b4e0
3e8b5b
--- /dev/null
3e8b5b
+++ b/ssh-pkcs11-uri.c
3e8b5b
@@ -0,0 +1,421 @@
3e8b5b
+/*
3e8b5b
+ * Copyright (c) 2017 Red Hat
3e8b5b
+ *
3e8b5b
+ * Authors: Jakub Jelen <jjelen@redhat.com>
3e8b5b
+ *
3e8b5b
+ * Permission to use, copy, modify, and distribute this software for any
3e8b5b
+ * purpose with or without fee is hereby granted, provided that the above
3e8b5b
+ * copyright notice and this permission notice appear in all copies.
3e8b5b
+ *
3e8b5b
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
3e8b5b
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
3e8b5b
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
3e8b5b
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
3e8b5b
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
3e8b5b
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
3e8b5b
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
3e8b5b
+ */
3e8b5b
+
3e8b5b
+#include "includes.h"
3e8b5b
+
3e8b5b
+#ifdef ENABLE_PKCS11
3e8b5b
+
3e8b5b
+#include <stdio.h>
3e8b5b
+#include <string.h>
3e8b5b
+
3e8b5b
+#include "sshkey.h"
3e8b5b
+#include "sshbuf.h"
3e8b5b
+#include "log.h"
3e8b5b
+
3e8b5b
+#define CRYPTOKI_COMPAT
3e8b5b
+#include "pkcs11.h"
3e8b5b
+
3e8b5b
+#include "ssh-pkcs11-uri.h"
3e8b5b
+
3e8b5b
+#define PKCS11_URI_PATH_SEPARATOR ";"
3e8b5b
+#define PKCS11_URI_QUERY_SEPARATOR "&"
3e8b5b
+#define PKCS11_URI_VALUE_SEPARATOR "="
3e8b5b
+#define PKCS11_URI_ID "id"
3e8b5b
+#define PKCS11_URI_TOKEN "token"
3e8b5b
+#define PKCS11_URI_OBJECT "object"
3e8b5b
+#define PKCS11_URI_LIB_MANUF "library-manufacturer"
3e8b5b
+#define PKCS11_URI_MANUF "manufacturer"
3e8b5b
+#define PKCS11_URI_MODULE_PATH "module-path"
3e8b5b
+#define PKCS11_URI_PIN_VALUE "pin-value"
3e8b5b
+
3e8b5b
+/* Keyword tokens. */
3e8b5b
+typedef enum {
3e8b5b
+	pId, pToken, pObject, pLibraryManufacturer, pManufacturer, pModulePath,
3e8b5b
+	pPinValue, pBadOption
3e8b5b
+} pkcs11uriOpCodes;
3e8b5b
+
3e8b5b
+/* Textual representation of the tokens. */
3e8b5b
+static struct {
3e8b5b
+	const char *name;
3e8b5b
+	pkcs11uriOpCodes opcode;
3e8b5b
+} keywords[] = {
3e8b5b
+	{ PKCS11_URI_ID, pId },
3e8b5b
+	{ PKCS11_URI_TOKEN, pToken },
3e8b5b
+	{ PKCS11_URI_OBJECT, pObject },
3e8b5b
+	{ PKCS11_URI_LIB_MANUF, pLibraryManufacturer },
3e8b5b
+	{ PKCS11_URI_MANUF, pManufacturer },
3e8b5b
+	{ PKCS11_URI_MODULE_PATH, pModulePath },
3e8b5b
+	{ PKCS11_URI_PIN_VALUE, pPinValue },
3e8b5b
+	{ NULL, pBadOption }
3e8b5b
+};
3e8b5b
+
3e8b5b
+static pkcs11uriOpCodes
3e8b5b
+parse_token(const char *cp)
3e8b5b
+{
3e8b5b
+	u_int i;
3e8b5b
+
3e8b5b
+	for (i = 0; keywords[i].name; i++)
3e8b5b
+		if (strncasecmp(cp, keywords[i].name,
3e8b5b
+		    strlen(keywords[i].name)) == 0)
3e8b5b
+			return keywords[i].opcode;
3e8b5b
+
3e8b5b
+	return pBadOption;
3e8b5b
+}
3e8b5b
+
3e8b5b
+int
3e8b5b
+percent_decode(char *data, char **outp)
3e8b5b
+{
3e8b5b
+	char tmp[3];
3e8b5b
+	char *out, *tmp_end;
3e8b5b
+	char *p = data;
3e8b5b
+	long value;
3e8b5b
+	size_t outlen = 0;
3e8b5b
+
3e8b5b
+	out = malloc(strlen(data)+1); /* upper bound */
3e8b5b
+	if (out == NULL)
3e8b5b
+		return -1;
3e8b5b
+	while (*p != '\0') {
3e8b5b
+		switch (*p) {
3e8b5b
+		case '%':
3e8b5b
+			p++;
3e8b5b
+			if (*p == '\0')
3e8b5b
+				goto fail;
3e8b5b
+			tmp[0] = *p++;
3e8b5b
+			if (*p == '\0')
3e8b5b
+				goto fail;
3e8b5b
+			tmp[1] = *p++;
3e8b5b
+			tmp[2] = '\0';
3e8b5b
+			tmp_end = NULL;
3e8b5b
+			value = strtol(tmp, &tmp_end, 16);
3e8b5b
+			if (tmp_end != tmp+2)
3e8b5b
+				goto fail;
3e8b5b
+			else
3e8b5b
+				out[outlen++] = (char) value;
3e8b5b
+			break;
3e8b5b
+		default:
3e8b5b
+			out[outlen++] = *p++;
3e8b5b
+			break;
3e8b5b
+		}
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	/* zero terminate */
3e8b5b
+	out[outlen] = '\0';
3e8b5b
+	*outp = out;
3e8b5b
+	return outlen;
3e8b5b
+fail:
3e8b5b
+	free(out);
3e8b5b
+	return -1;
3e8b5b
+}
3e8b5b
+
3e8b5b
+struct sshbuf *
3e8b5b
+percent_encode(const char *data, size_t length, const char *whitelist)
3e8b5b
+{
3e8b5b
+	struct sshbuf *b = NULL;
3e8b5b
+	char tmp[4], *cp;
3e8b5b
+	size_t i;
3e8b5b
+
3e8b5b
+	if ((b = sshbuf_new()) == NULL)
3e8b5b
+		return NULL;
3e8b5b
+	for (i = 0; i < length; i++) {
3e8b5b
+		cp = strchr(whitelist, data[i]);
3e8b5b
+		/* if c is specified as '\0' pointer to terminator is returned !! */
3e8b5b
+		if (cp != NULL && *cp != '\0') {
3e8b5b
+			if (sshbuf_put(b, &data[i], 1) != 0)
3e8b5b
+				goto err;
3e8b5b
+		} else
3e8b5b
+			if (snprintf(tmp, 4, "%%%02X", (unsigned char) data[i]) < 3
3e8b5b
+			    || sshbuf_put(b, tmp, 3) != 0)
3e8b5b
+				goto err;
3e8b5b
+	}
3e8b5b
+	if (sshbuf_put(b, "\0", 1) == 0)
3e8b5b
+		return b;
3e8b5b
+err:
3e8b5b
+	sshbuf_free(b);
3e8b5b
+	return NULL;
3e8b5b
+}
3e8b5b
+
3e8b5b
+char *
3e8b5b
+pkcs11_uri_append(char *part, const char *separator, const char *key,
3e8b5b
+    struct sshbuf *value)
3e8b5b
+{
3e8b5b
+	char *new_part;
3e8b5b
+	size_t size = 0;
3e8b5b
+
3e8b5b
+	if (value == NULL)
3e8b5b
+		return NULL;
3e8b5b
+
3e8b5b
+	size = asprintf(&new_part,
3e8b5b
+	    "%s%s%s"  PKCS11_URI_VALUE_SEPARATOR "%s",
3e8b5b
+	    (part != NULL ? part : ""),
3e8b5b
+	    (part != NULL ? separator : ""),
3e8b5b
+	    key, sshbuf_ptr(value));
3e8b5b
+	sshbuf_free(value);
3e8b5b
+	free(part);
3e8b5b
+
3e8b5b
+	if (size <= 0)
3e8b5b
+		return NULL;
3e8b5b
+	return new_part;
3e8b5b
+}
3e8b5b
+
3e8b5b
+char *
3e8b5b
+pkcs11_uri_get(struct pkcs11_uri *uri)
3e8b5b
+{
3e8b5b
+	size_t size = 0;
3e8b5b
+	char *p = NULL, *path = NULL, *query = NULL;
3e8b5b
+
3e8b5b
+	/* compose a percent-encoded ID */
3e8b5b
+	if (uri->id_len > 0) {
3e8b5b
+		struct sshbuf *key_id = percent_encode(uri->id, uri->id_len, "");
3e8b5b
+		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
3e8b5b
+		    PKCS11_URI_ID, key_id);
3e8b5b
+		if (path == NULL)
3e8b5b
+			goto err;
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	/* Write object label */
3e8b5b
+	if (uri->object) {
3e8b5b
+		struct sshbuf *label = percent_encode(uri->object, strlen(uri->object),
3e8b5b
+		    PKCS11_URI_WHITELIST);
3e8b5b
+		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
3e8b5b
+		    PKCS11_URI_OBJECT, label);
3e8b5b
+		if (path == NULL)
3e8b5b
+			goto err;
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	/* Write token label */
3e8b5b
+	if (uri->token) {
3e8b5b
+		struct sshbuf *label = percent_encode(uri->token, strlen(uri->token),
3e8b5b
+		    PKCS11_URI_WHITELIST);
3e8b5b
+		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
3e8b5b
+		    PKCS11_URI_TOKEN, label);
3e8b5b
+		if (path == NULL)
3e8b5b
+			goto err;
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	/* Write manufacturer */
3e8b5b
+	if (uri->manuf) {
3e8b5b
+		struct sshbuf *manuf = percent_encode(uri->manuf,
3e8b5b
+		    strlen(uri->manuf), PKCS11_URI_WHITELIST);
3e8b5b
+		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
3e8b5b
+		    PKCS11_URI_MANUF, manuf);
3e8b5b
+		if (path == NULL)
3e8b5b
+			goto err;
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	/* Write module_path */
3e8b5b
+	if (uri->module_path) {
3e8b5b
+		struct sshbuf *module = percent_encode(uri->module_path,
3e8b5b
+		    strlen(uri->module_path), PKCS11_URI_WHITELIST "/");
3e8b5b
+		query = pkcs11_uri_append(query, PKCS11_URI_QUERY_SEPARATOR,
3e8b5b
+		    PKCS11_URI_MODULE_PATH, module);
3e8b5b
+		if (query == NULL)
3e8b5b
+			goto err;
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	size = asprintf(&p, PKCS11_URI_SCHEME "%s%s%s",
3e8b5b
+	    path != NULL ? path : "",
3e8b5b
+	    query != NULL ? "?" : "",
3e8b5b
+	    query != NULL ? query : "");
3e8b5b
+err:
3e8b5b
+	free(query);
3e8b5b
+	free(path);
3e8b5b
+	if (size <= 0)
3e8b5b
+		return NULL;
3e8b5b
+	return p;
3e8b5b
+}
3e8b5b
+
3e8b5b
+struct pkcs11_uri *
3e8b5b
+pkcs11_uri_init()
3e8b5b
+{
3e8b5b
+	struct pkcs11_uri *d = calloc(1, sizeof(struct pkcs11_uri));
3e8b5b
+	return d;
3e8b5b
+}
3e8b5b
+
3e8b5b
+void
3e8b5b
+pkcs11_uri_cleanup(struct pkcs11_uri *pkcs11)
3e8b5b
+{
3e8b5b
+	free(pkcs11->id);
3e8b5b
+	free(pkcs11->module_path);
3e8b5b
+	free(pkcs11->token);
3e8b5b
+	free(pkcs11->object);
3e8b5b
+	free(pkcs11->lib_manuf);
3e8b5b
+	free(pkcs11->manuf);
3e8b5b
+	if (pkcs11->pin)
3e8b5b
+		freezero(pkcs11->pin, strlen(pkcs11->pin));
3e8b5b
+	free(pkcs11);
3e8b5b
+}
3e8b5b
+
3e8b5b
+int
3e8b5b
+pkcs11_uri_parse(const char *uri, struct pkcs11_uri *pkcs11)
3e8b5b
+{
3e8b5b
+	char *saveptr1, *saveptr2, *str1, *str2, *tok;
3e8b5b
+	int rv = 0, len;
3e8b5b
+	char *p = NULL;
3e8b5b
+
3e8b5b
+	size_t scheme_len = strlen(PKCS11_URI_SCHEME);
3e8b5b
+	if (strlen(uri) < scheme_len || /* empty URI matches everything */
3e8b5b
+	    strncmp(uri, PKCS11_URI_SCHEME, scheme_len) != 0) {
3e8b5b
+		error("%s: The '%s' does not look like PKCS#11 URI",
3e8b5b
+		    __func__, uri);
3e8b5b
+		return -1;
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	if (pkcs11 == NULL) {
3e8b5b
+		error("%s: Bad arguments. The pkcs11 can't be null", __func__);
3e8b5b
+		return -1;
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	/* skip URI schema name */
3e8b5b
+	p = strdup(uri);
3e8b5b
+	str1 = p;
3e8b5b
+
3e8b5b
+	/* everything before ? */
3e8b5b
+	tok = strtok_r(str1, "?", &saveptr1);
3e8b5b
+	if (tok == NULL) {
3e8b5b
+		error("%s: pk11-path expected, got EOF", __func__);
3e8b5b
+		rv = -1;
3e8b5b
+		goto out;
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	/* skip URI schema name:
3e8b5b
+	 * the scheme ensures that there is at least something before "?"
3e8b5b
+	 * allowing empty pk11-path. Resulting token at worst pointing to
3e8b5b
+	 * \0 byte */
3e8b5b
+	tok = tok + scheme_len;
3e8b5b
+
3e8b5b
+	/* parse pk11-path */
3e8b5b
+	for (str2 = tok; ; str2 = NULL) {
3e8b5b
+		char **charptr, *arg = NULL;
3e8b5b
+		pkcs11uriOpCodes opcode;
3e8b5b
+		tok = strtok_r(str2, PKCS11_URI_PATH_SEPARATOR, &saveptr2);
3e8b5b
+		if (tok == NULL)
3e8b5b
+			break;
3e8b5b
+		opcode = parse_token(tok);
3e8b5b
+		if (opcode != pBadOption)
3e8b5b
+			arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */
3e8b5b
+
3e8b5b
+		switch (opcode) {
3e8b5b
+		case pId:
3e8b5b
+			/* CKA_ID */
3e8b5b
+			if (pkcs11->id != NULL) {
3e8b5b
+				verbose("%s: The id already set in the PKCS#11 URI",
3e8b5b
+					__func__);
3e8b5b
+				rv = -1;
3e8b5b
+				goto out;
3e8b5b
+			}
3e8b5b
+			len = percent_decode(arg, &pkcs11->id);
3e8b5b
+			if (len <= 0) {
3e8b5b
+				verbose("%s: Failed to percent-decode CKA_ID: %s",
3e8b5b
+				    __func__, arg);
3e8b5b
+				rv = -1;
3e8b5b
+				goto out;
3e8b5b
+			} else
3e8b5b
+				pkcs11->id_len = len;
3e8b5b
+			debug3("%s: Setting CKA_ID = %s from PKCS#11 URI",
3e8b5b
+			    __func__, arg);
3e8b5b
+			break;
3e8b5b
+		case pToken:
3e8b5b
+			/* CK_TOKEN_INFO -> label */
3e8b5b
+			charptr = &pkcs11->token;
3e8b5b
+ parse_string:
3e8b5b
+			if (*charptr != NULL) {
3e8b5b
+				verbose("%s: The %s already set in the PKCS#11 URI",
3e8b5b
+				    keywords[opcode].name, __func__);
3e8b5b
+				rv = -1;
3e8b5b
+				goto out;
3e8b5b
+			}
3e8b5b
+			percent_decode(arg, charptr);
3e8b5b
+			debug3("%s: Setting %s = %s from PKCS#11 URI",
3e8b5b
+			    __func__, keywords[opcode].name, *charptr);
3e8b5b
+			break;
3e8b5b
+
3e8b5b
+		case pObject:
3e8b5b
+			/* CK_TOKEN_INFO -> manufacturerID */
3e8b5b
+			charptr = &pkcs11->object;
3e8b5b
+			goto parse_string;
3e8b5b
+
3e8b5b
+		case pManufacturer:
3e8b5b
+			/* CK_TOKEN_INFO -> manufacturerID */
3e8b5b
+			charptr = &pkcs11->manuf;
3e8b5b
+			goto parse_string;
3e8b5b
+
3e8b5b
+		case pLibraryManufacturer:
3e8b5b
+			/* CK_INFO -> manufacturerID */
3e8b5b
+			charptr = &pkcs11->lib_manuf;
3e8b5b
+			goto parse_string;
3e8b5b
+
3e8b5b
+		default:
3e8b5b
+			/* Unrecognized attribute in the URI path SHOULD be error */
3e8b5b
+			verbose("%s: Unknown part of path in PKCS#11 URI: %s",
3e8b5b
+			    __func__, tok);
3e8b5b
+		}
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	tok = strtok_r(NULL, "?", &saveptr1);
3e8b5b
+	if (tok == NULL) {
3e8b5b
+		goto out;
3e8b5b
+	}
3e8b5b
+	/* parse pk11-query (optional) */
3e8b5b
+	for (str2 = tok; ; str2 = NULL) {
3e8b5b
+		char *arg;
3e8b5b
+		pkcs11uriOpCodes opcode;
3e8b5b
+		tok = strtok_r(str2, PKCS11_URI_QUERY_SEPARATOR, &saveptr2);
3e8b5b
+		if (tok == NULL)
3e8b5b
+			break;
3e8b5b
+		opcode = parse_token(tok);
3e8b5b
+		if (opcode != pBadOption)
3e8b5b
+			arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */
3e8b5b
+
3e8b5b
+		switch (opcode) {
3e8b5b
+		case pModulePath:
3e8b5b
+			/* module-path is PKCS11Provider */
3e8b5b
+			if (pkcs11->module_path != NULL) {
3e8b5b
+				verbose("%s: Multiple module-path attributes are"
3e8b5b
+				    "not supported the PKCS#11 URI", __func__);
3e8b5b
+				rv = -1;
3e8b5b
+				goto out;
3e8b5b
+			}
3e8b5b
+			percent_decode(arg, &pkcs11->module_path);
3e8b5b
+			debug3("%s: Setting PKCS11Provider = %s from PKCS#11 URI",
3e8b5b
+			    __func__, pkcs11->module_path);
3e8b5b
+			break;
3e8b5b
+
3e8b5b
+		case pPinValue:
3e8b5b
+			/* pin-value */
3e8b5b
+			if (pkcs11->pin != NULL) {
3e8b5b
+				verbose("%s: Multiple pin-value attributes are"
3e8b5b
+				    "not supported the PKCS#11 URI", __func__);
3e8b5b
+				rv = -1;
3e8b5b
+				goto out;
3e8b5b
+			}
3e8b5b
+			percent_decode(arg, &pkcs11->pin);
3e8b5b
+			debug3("%s: Setting PIN from PKCS#11 URI", __func__);
3e8b5b
+			break;
3e8b5b
+
3e8b5b
+		default:
3e8b5b
+			/* Unrecognized attribute in the URI query SHOULD be ignored */
3e8b5b
+			verbose("%s: Unknown part of query in PKCS#11 URI: %s",
3e8b5b
+			    __func__, tok);
3e8b5b
+		}
3e8b5b
+	}
3e8b5b
+out:
3e8b5b
+	free(p);
3e8b5b
+	return rv;
3e8b5b
+}
3e8b5b
+
3e8b5b
+#endif /* ENABLE_PKCS11 */
3e8b5b
diff --git a/ssh-pkcs11-uri.h b/ssh-pkcs11-uri.h
3e8b5b
new file mode 100644
3e8b5b
index 00000000..942a5a5a
3e8b5b
--- /dev/null
3e8b5b
+++ b/ssh-pkcs11-uri.h
3e8b5b
@@ -0,0 +1,42 @@
3e8b5b
+/*
3e8b5b
+ * Copyright (c) 2017 Red Hat
3e8b5b
+ *
3e8b5b
+ * Authors: Jakub Jelen <jjelen@redhat.com>
3e8b5b
+ *
3e8b5b
+ * Permission to use, copy, modify, and distribute this software for any
3e8b5b
+ * purpose with or without fee is hereby granted, provided that the above
3e8b5b
+ * copyright notice and this permission notice appear in all copies.
3e8b5b
+ *
3e8b5b
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
3e8b5b
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
3e8b5b
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
3e8b5b
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
3e8b5b
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
3e8b5b
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
3e8b5b
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
3e8b5b
+ */
3e8b5b
+
3e8b5b
+#define PKCS11_URI_SCHEME "pkcs11:"
3e8b5b
+#define PKCS11_URI_WHITELIST	"abcdefghijklmnopqrstuvwxyz" \
3e8b5b
+				"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
3e8b5b
+				"0123456789_-.()"
3e8b5b
+
3e8b5b
+struct pkcs11_uri {
3e8b5b
+	/* path */
3e8b5b
+	char *id;
3e8b5b
+	size_t id_len;
3e8b5b
+	char *token;
3e8b5b
+	char *object;
3e8b5b
+	char *lib_manuf;
3e8b5b
+	char *manuf;
3e8b5b
+	/* query */
3e8b5b
+	char *module_path;
3e8b5b
+	char *pin; /* Only parsed, but not printed */
3e8b5b
+};
3e8b5b
+
3e8b5b
+struct	 pkcs11_uri *pkcs11_uri_init();
3e8b5b
+void	 pkcs11_uri_cleanup(struct pkcs11_uri *);
3e8b5b
+int	 pkcs11_uri_parse(const char *, struct pkcs11_uri *);
3e8b5b
+struct	 pkcs11_uri *pkcs11_uri_init();
3e8b5b
+char	*pkcs11_uri_get(struct pkcs11_uri *uri);
3e8b5b
+
3e8b5b
diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c
3e8b5b
index 70f06bff..59332945 100644
3e8b5b
--- a/ssh-pkcs11.c
3e8b5b
+++ b/ssh-pkcs11.c
3e8b5b
@@ -54,8 +54,8 @@ struct pkcs11_slotinfo {
3e8b5b
 	int			logged_in;
3e8b5b
 };
3e8b5b
 
3e8b5b
-struct pkcs11_provider {
3e8b5b
-	char			*name;
3e8b5b
+struct pkcs11_module {
3e8b5b
+	char			*module_path;
3e8b5b
 	void			*handle;
3e8b5b
 	CK_FUNCTION_LIST	*function_list;
3e8b5b
 	CK_INFO			info;
3e8b5b
@@ -64,6 +64,13 @@ struct pkcs11_provider {
3e8b5b
 	struct pkcs11_slotinfo	*slotinfo;
3e8b5b
 	int			valid;
3e8b5b
 	int			refcount;
3e8b5b
+};
3e8b5b
+
3e8b5b
+struct pkcs11_provider {
3e8b5b
+	char			*name;
3e8b5b
+	struct pkcs11_module	*module; /* can be shared between various providers */
3e8b5b
+	int			refcount;
3e8b5b
+	int			valid;
3e8b5b
 	TAILQ_ENTRY(pkcs11_provider) next;
3e8b5b
 };
3e8b5b
 
3e8b5b
@@ -74,6 +81,7 @@ struct pkcs11_key {
3e8b5b
 	CK_ULONG		slotidx;
3e8b5b
 	char			*keyid;
3e8b5b
 	int			keyid_len;
3e8b5b
+	char			*label;
3e8b5b
 };
3e8b5b
 
3e8b5b
 int pkcs11_interactive = 0;
3e8b5b
@@ -106,26 +114,63 @@ pkcs11_init(int interactive)
3e8b5b
  * this is called when a provider gets unregistered.
3e8b5b
  */
3e8b5b
 static void
3e8b5b
-pkcs11_provider_finalize(struct pkcs11_provider *p)
3e8b5b
+pkcs11_module_finalize(struct pkcs11_module *m)
3e8b5b
 {
3e8b5b
 	CK_RV rv;
3e8b5b
 	CK_ULONG i;
3e8b5b
 
3e8b5b
-	debug("pkcs11_provider_finalize: %p refcount %d valid %d",
3e8b5b
-	    p, p->refcount, p->valid);
3e8b5b
-	if (!p->valid)
3e8b5b
+	debug("%s: %p refcount %d valid %d", __func__,
3e8b5b
+	    m, m->refcount, m->valid);
3e8b5b
+	if (!m->valid)
3e8b5b
 		return;
3e8b5b
-	for (i = 0; i < p->nslots; i++) {
3e8b5b
-		if (p->slotinfo[i].session &&
3e8b5b
-		    (rv = p->function_list->C_CloseSession(
3e8b5b
-		    p->slotinfo[i].session)) != CKR_OK)
3e8b5b
+	for (i = 0; i < m->nslots; i++) {
3e8b5b
+		if (m->slotinfo[i].session &&
3e8b5b
+		    (rv = m->function_list->C_CloseSession(
3e8b5b
+		    m->slotinfo[i].session)) != CKR_OK)
3e8b5b
 			error("C_CloseSession failed: %lu", rv);
3e8b5b
 	}
3e8b5b
-	if ((rv = p->function_list->C_Finalize(NULL)) != CKR_OK)
3e8b5b
+	if ((rv = m->function_list->C_Finalize(NULL)) != CKR_OK)
3e8b5b
 		error("C_Finalize failed: %lu", rv);
3e8b5b
+	m->valid = 0;
3e8b5b
+	m->function_list = NULL;
3e8b5b
+	dlclose(m->handle);
3e8b5b
+}
3e8b5b
+
3e8b5b
+/*
3e8b5b
+ * remove a reference to the pkcs11 module.
3e8b5b
+ * called when a provider is unregistered.
3e8b5b
+ */
3e8b5b
+static void
3e8b5b
+pkcs11_module_unref(struct pkcs11_module *m)
3e8b5b
+{
3e8b5b
+	debug("%s: %p refcount %d", __func__, m, m->refcount);
3e8b5b
+	if (--m->refcount <= 0) {
3e8b5b
+		pkcs11_module_finalize(m);
3e8b5b
+		if (m->valid)
3e8b5b
+			error("%s: %p still valid", __func__, m);
3e8b5b
+		free(m->slotlist);
3e8b5b
+		free(m->slotinfo);
3e8b5b
+		free(m->module_path);
3e8b5b
+		free(m);
3e8b5b
+	}
3e8b5b
+}
3e8b5b
+
3e8b5b
+/*
3e8b5b
+ * finalize a provider shared libarary, it's no longer usable.
3e8b5b
+ * however, there might still be keys referencing this provider,
3e8b5b
+ * so the actuall freeing of memory is handled by pkcs11_provider_unref().
3e8b5b
+ * this is called when a provider gets unregistered.
3e8b5b
+ */
3e8b5b
+static void
3e8b5b
+pkcs11_provider_finalize(struct pkcs11_provider *p)
3e8b5b
+{
3e8b5b
+	debug("%s: %p refcount %d valid %d", __func__,
3e8b5b
+	    p, p->refcount, p->valid);
3e8b5b
+	if (!p->valid)
3e8b5b
+		return;
3e8b5b
+	pkcs11_module_unref(p->module);
3e8b5b
+	p->module = NULL;
3e8b5b
 	p->valid = 0;
3e8b5b
-	p->function_list = NULL;
3e8b5b
-	dlclose(p->handle);
3e8b5b
 }
3e8b5b
 
3e8b5b
 /*
3e8b5b
@@ -135,13 +180,11 @@ pkcs11_provider_finalize(struct pkcs11_provider *p)
3e8b5b
 static void
3e8b5b
 pkcs11_provider_unref(struct pkcs11_provider *p)
3e8b5b
 {
3e8b5b
-	debug("pkcs11_provider_unref: %p refcount %d", p, p->refcount);
3e8b5b
+	debug("%s: %p refcount %d", __func__, p, p->refcount);
3e8b5b
 	if (--p->refcount <= 0) {
3e8b5b
-		if (p->valid)
3e8b5b
-			error("pkcs11_provider_unref: %p still valid", p);
3e8b5b
 		free(p->name);
3e8b5b
-		free(p->slotlist);
3e8b5b
-		free(p->slotinfo);
3e8b5b
+		if (p->module)
3e8b5b
+			pkcs11_module_unref(p->module);
3e8b5b
 		free(p);
3e8b5b
 	}
3e8b5b
 }
3e8b5b
@@ -159,6 +202,20 @@ pkcs11_terminate(void)
3e8b5b
 	}
3e8b5b
 }
3e8b5b
 
3e8b5b
+/* lookup provider by module path */
3e8b5b
+static struct pkcs11_module *
3e8b5b
+pkcs11_provider_lookup_module(char *module_path)
3e8b5b
+{
3e8b5b
+	struct pkcs11_provider *p;
3e8b5b
+
3e8b5b
+	TAILQ_FOREACH(p, &pkcs11_providers, next) {
3e8b5b
+		debug("check %p %s (%s)", p, p->name, p->module->module_path);
3e8b5b
+		if (!strcmp(module_path, p->module->module_path))
3e8b5b
+			return (p->module);
3e8b5b
+	}
3e8b5b
+	return (NULL);
3e8b5b
+}
3e8b5b
+
3e8b5b
 /* lookup provider by name */
3e8b5b
 static struct pkcs11_provider *
3e8b5b
 pkcs11_provider_lookup(char *provider_id)
3e8b5b
@@ -173,19 +230,52 @@ pkcs11_provider_lookup(char *provider_id)
3e8b5b
 	return (NULL);
3e8b5b
 }
3e8b5b
 
3e8b5b
+int pkcs11_del_provider_by_uri(struct pkcs11_uri *);
3e8b5b
+
3e8b5b
 /* unregister provider by name */
3e8b5b
 int
3e8b5b
 pkcs11_del_provider(char *provider_id)
3e8b5b
+{
3e8b5b
+	int rv;
3e8b5b
+	struct pkcs11_uri *uri;
3e8b5b
+
3e8b5b
+	debug("%s: called, provider_id = %s", __func__, provider_id);
3e8b5b
+
3e8b5b
+	uri = pkcs11_uri_init();
3e8b5b
+	if (uri == NULL)
3e8b5b
+		fatal("Failed to init PCKS#11 URI");
3e8b5b
+
3e8b5b
+	if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
3e8b5b
+	    strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
3e8b5b
+		if (pkcs11_uri_parse(provider_id, uri) != 0)
3e8b5b
+			fatal("Failed to parse PKCS#11 URI");
3e8b5b
+	} else {
3e8b5b
+		uri->module_path = strdup(provider_id);
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	rv = pkcs11_del_provider_by_uri(uri);
3e8b5b
+	pkcs11_uri_cleanup(uri);
3e8b5b
+	return rv;
3e8b5b
+}
3e8b5b
+
3e8b5b
+/* unregister provider by PKCS#11 URI */
3e8b5b
+int
3e8b5b
+pkcs11_del_provider_by_uri(struct pkcs11_uri *uri)
3e8b5b
 {
3e8b5b
 	struct pkcs11_provider *p;
3e8b5b
+	int rv = -1;
3e8b5b
+	char *provider_uri = pkcs11_uri_get(uri);
3e8b5b
 
3e8b5b
-	if ((p = pkcs11_provider_lookup(provider_id)) != NULL) {
3e8b5b
+	debug3("%s(%s): called", __func__, provider_uri);
3e8b5b
+
3e8b5b
+	if ((p = pkcs11_provider_lookup(provider_uri)) != NULL) {
3e8b5b
 		TAILQ_REMOVE(&pkcs11_providers, p, next);
3e8b5b
 		pkcs11_provider_finalize(p);
3e8b5b
 		pkcs11_provider_unref(p);
3e8b5b
-		return (0);
3e8b5b
+		rv = 0;
3e8b5b
 	}
3e8b5b
-	return (-1);
3e8b5b
+	free(provider_uri);
3e8b5b
+	return rv;
3e8b5b
 }
3e8b5b
 
3e8b5b
 static RSA_METHOD *rsa_method;
3e8b5b
@@ -195,6 +285,55 @@ static EC_KEY_METHOD *ec_key_method;
3e8b5b
 static int ec_key_idx = 0;
3e8b5b
 #endif
3e8b5b
 
3e8b5b
+/*
3e8b5b
+ * This can't be in the ssh-pkcs11-uri, becase we can not depend on
3e8b5b
+ * PKCS#11 structures in ssh-agent (using client-helper communication)
3e8b5b
+ */
3e8b5b
+int
3e8b5b
+pkcs11_uri_write(const struct sshkey *key, FILE *f)
3e8b5b
+{
3e8b5b
+	char *p = NULL;
3e8b5b
+	struct pkcs11_uri uri;
3e8b5b
+	struct pkcs11_key *k11;
3e8b5b
+
3e8b5b
+	/* sanity - is it a RSA key with associated app_data? */
3e8b5b
+	switch (key->type) {
3e8b5b
+	case KEY_RSA:
3e8b5b
+		k11 = RSA_get_ex_data(key->rsa, rsa_idx);
3e8b5b
+		break;
3e8b5b
+#ifdef HAVE_EC_KEY_METHOD_NEW
3e8b5b
+	case KEY_ECDSA:
3e8b5b
+		k11 = EC_KEY_get_ex_data(key->ecdsa, ec_key_idx);
3e8b5b
+		break;
3e8b5b
+#endif
3e8b5b
+	default:
3e8b5b
+		error("Unknown key type %d", key->type);
3e8b5b
+		return -1;
3e8b5b
+	}
3e8b5b
+	if (k11 == NULL) {
3e8b5b
+		error("Failed to get ex_data for key type %d", key->type);
3e8b5b
+		return (-1);
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	/* omit type -- we are looking for private-public or private-certificate pairs */
3e8b5b
+	uri.id = k11->keyid;
3e8b5b
+	uri.id_len = k11->keyid_len;
3e8b5b
+	uri.token = k11->provider->module->slotinfo[k11->slotidx].token.label;
3e8b5b
+	uri.object = k11->label;
3e8b5b
+	uri.module_path = k11->provider->module->module_path;
3e8b5b
+	uri.lib_manuf = k11->provider->module->info.manufacturerID;
3e8b5b
+	uri.manuf = k11->provider->module->slotinfo[k11->slotidx].token.manufacturerID;
3e8b5b
+
3e8b5b
+	p = pkcs11_uri_get(&uri);
3e8b5b
+	/* do not cleanup -- we do not allocate here, only reference */
3e8b5b
+	if (p == NULL)
3e8b5b
+		return -1;
3e8b5b
+
3e8b5b
+	fprintf(f, " %s", p);
3e8b5b
+	free(p);
3e8b5b
+	return 0;
3e8b5b
+}
3e8b5b
+
3e8b5b
 /* release a wrapped object */
3e8b5b
 static void
3e8b5b
 pkcs11_k11_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx,
3e8b5b
@@ -208,6 +347,7 @@ pkcs11_k11_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx,
3e8b5b
         if (k11->provider)
3e8b5b
                 pkcs11_provider_unref(k11->provider);
3e8b5b
         free(k11->keyid);
3e8b5b
+	free(k11->label);
3e8b5b
         free(k11);
3e8b5b
 }
3e8b5b
 
3e8b5b
@@ -222,8 +362,8 @@ pkcs11_find(struct pkcs11_provider *p, CK_ULONG slotidx, CK_ATTRIBUTE *attr,
3e8b5b
 	CK_RV			rv;
3e8b5b
 	int			ret = -1;
3e8b5b
 
3e8b5b
-	f = p->function_list;
3e8b5b
-	session = p->slotinfo[slotidx].session;
3e8b5b
+	f = p->module->function_list;
3e8b5b
+	session = p->module->slotinfo[slotidx].session;
3e8b5b
 	if ((rv = f->C_FindObjectsInit(session, attr, nattr)) != CKR_OK) {
3e8b5b
 		error("C_FindObjectsInit failed (nattr %lu): %lu", nattr, rv);
3e8b5b
 		return (-1);
3e8b5b
@@ -252,8 +392,8 @@ pkcs11_login(struct pkcs11_key *k11, CK_USER_TYPE type)
3e8b5b
 		return (-1);
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	f = k11->provider->function_list;
3e8b5b
-	si = &k11->provider->slotinfo[k11->slotidx];
3e8b5b
+	f = k11->provider->module->function_list;
3e8b5b
+	si = &k11->provider->module->slotinfo[k11->slotidx];
3e8b5b
 
3e8b5b
 	if (!pkcs11_interactive) {
3e8b5b
 		error("need pin entry%s",
3e8b5b
@@ -300,8 +440,8 @@ pkcs11_check_obj_bool_attrib(struct pkcs11_key *k11, CK_OBJECT_HANDLE obj,
3e8b5b
 		return (-1);
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	f = k11->provider->function_list;
3e8b5b
-	si = &k11->provider->slotinfo[k11->slotidx];
3e8b5b
+	f = k11->provider->module->function_list;
3e8b5b
+	si = &k11->provider->module->slotinfo[k11->slotidx];
3e8b5b
 
3e8b5b
 	attr.type = type;
3e8b5b
 	attr.pValue = &flag;
3e8b5b
@@ -332,13 +472,14 @@ pkcs11_get_key(struct pkcs11_key *k11, CK_MECHANISM_TYPE mech_type)
3e8b5b
 	int			 always_auth = 0;
3e8b5b
 	int			 did_login = 0;
3e8b5b
 
3e8b5b
-	if (!k11->provider || !k11->provider->valid) {
3e8b5b
+	if (!k11->provider || !k11->provider->valid || !k11->provider->module
3e8b5b
+	    || !k11->provider->module->valid) {
3e8b5b
 		error("no pkcs11 (valid) provider found");
3e8b5b
 		return (-1);
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	f = k11->provider->function_list;
3e8b5b
-	si = &k11->provider->slotinfo[k11->slotidx];
3e8b5b
+	f = k11->provider->module->function_list;
3e8b5b
+	si = &k11->provider->module->slotinfo[k11->slotidx];
3e8b5b
 
3e8b5b
 	if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) {
3e8b5b
 		if (pkcs11_login(k11, CKU_USER) < 0) {
3e8b5b
@@ -415,8 +556,8 @@ pkcs11_rsa_private_encrypt(int flen, const u_char *from, u_char *to, RSA *rsa,
3e8b5b
 		return (-1);
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	f = k11->provider->function_list;
3e8b5b
-	si = &k11->provider->slotinfo[k11->slotidx];
3e8b5b
+	f = k11->provider->module->function_list;
3e8b5b
+	si = &k11->provider->module->slotinfo[k11->slotidx];
3e8b5b
 	tlen = RSA_size(rsa);
3e8b5b
 
3e8b5b
 	/* XXX handle CKR_BUFFER_TOO_SMALL */
3e8b5b
@@ -460,7 +601,7 @@ pkcs11_rsa_start_wrapper(void)
3e8b5b
 /* redirect private key operations for rsa key to pkcs11 token */
3e8b5b
 static int
3e8b5b
 pkcs11_rsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx,
3e8b5b
-    CK_ATTRIBUTE *keyid_attrib, RSA *rsa)
3e8b5b
+    CK_ATTRIBUTE *keyid_attrib, CK_ATTRIBUTE *label_attrib, RSA *rsa)
3e8b5b
 {
3e8b5b
 	struct pkcs11_key	*k11;
3e8b5b
 
3e8b5b
@@ -478,6 +619,12 @@ pkcs11_rsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx,
3e8b5b
 		memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len);
3e8b5b
 	}
3e8b5b
 
3e8b5b
+	if (label_attrib->ulValueLen > 0 ) {
3e8b5b
+		k11->label = xmalloc(label_attrib->ulValueLen+1);
3e8b5b
+		memcpy(k11->label, label_attrib->pValue, label_attrib->ulValueLen);
3e8b5b
+		k11->label[label_attrib->ulValueLen] = 0;
3e8b5b
+	}
3e8b5b
+
3e8b5b
 	RSA_set_method(rsa, rsa_method);
3e8b5b
 	RSA_set_ex_data(rsa, rsa_idx, k11);
3e8b5b
 	return (0);
3e8b5b
@@ -508,8 +655,8 @@ ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv,
3e8b5b
 		return (NULL);
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	f = k11->provider->function_list;
3e8b5b
-	si = &k11->provider->slotinfo[k11->slotidx];
3e8b5b
+	f = k11->provider->module->function_list;
3e8b5b
+	si = &k11->provider->module->slotinfo[k11->slotidx];
3e8b5b
 
3e8b5b
 	siglen = ECDSA_size(ec);
3e8b5b
 	sig = xmalloc(siglen);
3e8b5b
@@ -574,7 +721,7 @@ pkcs11_ecdsa_start_wrapper(void)
3e8b5b
 
3e8b5b
 static int
3e8b5b
 pkcs11_ecdsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx,
3e8b5b
-    CK_ATTRIBUTE *keyid_attrib, EC_KEY *ec)
3e8b5b
+    CK_ATTRIBUTE *keyid_attrib, CK_ATTRIBUTE *label_attrib, EC_KEY *ec)
3e8b5b
 {
3e8b5b
 	struct pkcs11_key	*k11;
3e8b5b
 
3e8b5b
@@ -590,6 +737,12 @@ pkcs11_ecdsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx,
3e8b5b
 	k11->keyid = xmalloc(k11->keyid_len);
3e8b5b
 	memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len);
3e8b5b
 
3e8b5b
+	if (label_attrib->ulValueLen > 0 ) {
3e8b5b
+		k11->label = xmalloc(label_attrib->ulValueLen+1);
3e8b5b
+		memcpy(k11->label, label_attrib->pValue, label_attrib->ulValueLen);
3e8b5b
+		k11->label[label_attrib->ulValueLen] = 0;
3e8b5b
+	}
3e8b5b
+
3e8b5b
 	EC_KEY_set_method(ec, ec_key_method);
3e8b5b
 	EC_KEY_set_ex_data(ec, ec_key_idx, k11);
3e8b5b
 
3e8b5b
@@ -624,47 +777,26 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin,
3e8b5b
 	CK_FUNCTION_LIST	*f;
3e8b5b
 	CK_RV			rv;
3e8b5b
 	CK_SESSION_HANDLE	session;
3e8b5b
-	int			login_required, have_pinpad, ret;
3e8b5b
-	char			prompt[1024], *xpin = NULL;
3e8b5b
+	int			login_required, ret;
3e8b5b
 
3e8b5b
-	f = p->function_list;
3e8b5b
-	si = &p->slotinfo[slotidx];
3e8b5b
+	f = p->module->function_list;
3e8b5b
+	si = &p->module->slotinfo[slotidx];
3e8b5b
 
3e8b5b
-	have_pinpad = si->token.flags & CKF_PROTECTED_AUTHENTICATION_PATH;
3e8b5b
 	login_required = si->token.flags & CKF_LOGIN_REQUIRED;
3e8b5b
 
3e8b5b
 	/* fail early before opening session */
3e8b5b
-	if (login_required && !have_pinpad && !pkcs11_interactive &&
3e8b5b
+	if (login_required && !pkcs11_interactive &&
3e8b5b
 	    (pin == NULL || strlen(pin) == 0)) {
3e8b5b
 		error("pin required");
3e8b5b
 		return (-SSH_PKCS11_ERR_PIN_REQUIRED);
3e8b5b
 	}
3e8b5b
-	if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION|
3e8b5b
+	if ((rv = f->C_OpenSession(p->module->slotlist[slotidx], CKF_RW_SESSION|
3e8b5b
 	    CKF_SERIAL_SESSION, NULL, NULL, &session)) != CKR_OK) {
3e8b5b
-		error("C_OpenSession failed: %lu", rv);
3e8b5b
+		error("C_OpenSession failed for slot %lu: %lu", slotidx, rv);
3e8b5b
 		return (-1);
3e8b5b
 	}
3e8b5b
-	if (login_required) {
3e8b5b
-		if (have_pinpad && (pin == NULL || strlen(pin) == 0)) {
3e8b5b
-			/* defer PIN entry to the reader keypad */
3e8b5b
-			rv = f->C_Login(session, CKU_USER, NULL_PTR, 0);
3e8b5b
-		} else {
3e8b5b
-			if (pkcs11_interactive) {
3e8b5b
-				snprintf(prompt, sizeof(prompt),
3e8b5b
-				    "Enter PIN for '%s': ", si->token.label);
3e8b5b
-				if ((xpin = read_passphrase(prompt,
3e8b5b
-				    RP_ALLOW_EOF)) == NULL) {
3e8b5b
-					debug("%s: no pin specified",
3e8b5b
-					    __func__);
3e8b5b
-					return (-SSH_PKCS11_ERR_PIN_REQUIRED);
3e8b5b
-				}
3e8b5b
-				pin = xpin;
3e8b5b
-			}
3e8b5b
-			rv = f->C_Login(session, CKU_USER,
3e8b5b
-			    (u_char *)pin, strlen(pin));
3e8b5b
-			if (xpin != NULL)
3e8b5b
-				freezero(xpin, strlen(xpin));
3e8b5b
-		}
3e8b5b
+	if (login_required && pin != NULL && strlen(pin) != 0) {
3e8b5b
+		rv = f->C_Login(session, user, (u_char *)pin, strlen(pin));
3e8b5b
 		if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN) {
3e8b5b
 			error("C_Login failed: %lu", rv);
3e8b5b
 			ret = (rv == CKR_PIN_LOCKED) ?
3e8b5b
@@ -696,7 +828,8 @@ static struct sshkey *
3e8b5b
 pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
     CK_OBJECT_HANDLE *obj)
3e8b5b
 {
3e8b5b
-	CK_ATTRIBUTE		 key_attr[3];
3e8b5b
+	CK_ATTRIBUTE		 key_attr[4];
3e8b5b
+	int			 nattr = 4;
3e8b5b
 	CK_SESSION_HANDLE	 session;
3e8b5b
 	CK_FUNCTION_LIST	*f = NULL;
3e8b5b
 	CK_RV			 rv;
3e8b5b
@@ -710,14 +843,15 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 
3e8b5b
 	memset(&key_attr, 0, sizeof(key_attr));
3e8b5b
 	key_attr[0].type = CKA_ID;
3e8b5b
-	key_attr[1].type = CKA_EC_POINT;
3e8b5b
-	key_attr[2].type = CKA_EC_PARAMS;
3e8b5b
+	key_attr[1].type = CKA_LABEL;
3e8b5b
+	key_attr[2].type = CKA_EC_POINT;
3e8b5b
+	key_attr[3].type = CKA_EC_PARAMS;
3e8b5b
 
3e8b5b
-	session = p->slotinfo[slotidx].session;
3e8b5b
-	f = p->function_list;
3e8b5b
+	session = p->module->slotinfo[slotidx].session;
3e8b5b
+	f = p->module->function_list;
3e8b5b
 
3e8b5b
 	/* figure out size of the attributes */
3e8b5b
-	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
3e8b5b
+	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
3e8b5b
 	if (rv != CKR_OK) {
3e8b5b
 		error("C_GetAttributeValue failed: %lu", rv);
3e8b5b
 		return (NULL);
3e8b5b
@@ -730,19 +863,19 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 	 * ensure that none of the others are zero length.
3e8b5b
 	 * XXX assumes CKA_ID is always first.
3e8b5b
 	 */
3e8b5b
-	if (key_attr[1].ulValueLen == 0 ||
3e8b5b
-	    key_attr[2].ulValueLen == 0) {
3e8b5b
+	if (key_attr[2].ulValueLen == 0 ||
3e8b5b
+	    key_attr[3].ulValueLen == 0) {
3e8b5b
 		error("invalid attribute length");
3e8b5b
 		return (NULL);
3e8b5b
 	}
3e8b5b
 
3e8b5b
 	/* allocate buffers for attributes */
3e8b5b
-	for (i = 0; i < 3; i++)
3e8b5b
+	for (i = 0; i < nattr; i++)
3e8b5b
 		if (key_attr[i].ulValueLen > 0)
3e8b5b
 			key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
3e8b5b
 
3e8b5b
 	/* retrieve ID, public point and curve parameters of EC key */
3e8b5b
-	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
3e8b5b
+	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
3e8b5b
 	if (rv != CKR_OK) {
3e8b5b
 		error("C_GetAttributeValue failed: %lu", rv);
3e8b5b
 		goto fail;
3e8b5b
@@ -752,8 +887,8 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	attrp = key_attr[2].pValue;
3e8b5b
-	group = d2i_ECPKParameters(NULL, &attrp, key_attr[2].ulValueLen);
3e8b5b
+	attrp = key_attr[3].pValue;
3e8b5b
+	group = d2i_ECPKParameters(NULL, &attrp, key_attr[3].ulValueLen);
3e8b5b
 	if (group == NULL) {
3e8b5b
 		ossl_error("d2i_ECPKParameters failed");
3e8b5b
 		goto fail;
3e8b5b
@@ -764,13 +899,13 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	if (key_attr[1].ulValueLen <= 2) {
3e8b5b
+	if (key_attr[2].ulValueLen <= 2) {
3e8b5b
 		error("CKA_EC_POINT too small");
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	attrp = key_attr[1].pValue;
3e8b5b
-	octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[1].ulValueLen);
3e8b5b
+	attrp = key_attr[2].pValue;
3e8b5b
+	octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[2].ulValueLen);
3e8b5b
 	if (octet == NULL) {
3e8b5b
 		ossl_error("d2i_ASN1_OCTET_STRING failed");
3e8b5b
 		goto fail;
3e8b5b
@@ -787,7 +922,7 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	if (pkcs11_ecdsa_wrap(p, slotidx, &key_attr[0], ec))
3e8b5b
+	if (pkcs11_ecdsa_wrap(p, slotidx, &key_attr[0], &key_attr[1], ec))
3e8b5b
 		goto fail;
3e8b5b
 
3e8b5b
 	key = sshkey_new(KEY_UNSPEC);
3e8b5b
@@ -803,7 +938,7 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 	ec = NULL;	/* now owned by key */
3e8b5b
 
3e8b5b
 fail:
3e8b5b
-	for (i = 0; i < 3; i++)
3e8b5b
+	for (i = 0; i < nattr; i++)
3e8b5b
 		free(key_attr[i].pValue);
3e8b5b
 	if (ec)
3e8b5b
 		EC_KEY_free(ec);
3e8b5b
@@ -820,7 +955,8 @@ static struct sshkey *
3e8b5b
 pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
     CK_OBJECT_HANDLE *obj)
3e8b5b
 {
3e8b5b
-	CK_ATTRIBUTE		 key_attr[3];
3e8b5b
+	CK_ATTRIBUTE		 key_attr[4];
3e8b5b
+	int			 nattr = 4;
3e8b5b
 	CK_SESSION_HANDLE	 session;
3e8b5b
 	CK_FUNCTION_LIST	*f = NULL;
3e8b5b
 	CK_RV			 rv;
3e8b5b
@@ -831,14 +967,15 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 
3e8b5b
 	memset(&key_attr, 0, sizeof(key_attr));
3e8b5b
 	key_attr[0].type = CKA_ID;
3e8b5b
-	key_attr[1].type = CKA_MODULUS;
3e8b5b
-	key_attr[2].type = CKA_PUBLIC_EXPONENT;
3e8b5b
+	key_attr[1].type = CKA_LABEL;
3e8b5b
+	key_attr[2].type = CKA_MODULUS;
3e8b5b
+	key_attr[3].type = CKA_PUBLIC_EXPONENT;
3e8b5b
 
3e8b5b
-	session = p->slotinfo[slotidx].session;
3e8b5b
-	f = p->function_list;
3e8b5b
+	session = p->module->slotinfo[slotidx].session;
3e8b5b
+	f = p->module->function_list;
3e8b5b
 
3e8b5b
 	/* figure out size of the attributes */
3e8b5b
-	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
3e8b5b
+	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
3e8b5b
 	if (rv != CKR_OK) {
3e8b5b
 		error("C_GetAttributeValue failed: %lu", rv);
3e8b5b
 		return (NULL);
3e8b5b
@@ -850,19 +987,19 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 	 * ensure that none of the others are zero length.
3e8b5b
 	 * XXX assumes CKA_ID is always first.
3e8b5b
 	 */
3e8b5b
-	if (key_attr[1].ulValueLen == 0 ||
3e8b5b
-	    key_attr[2].ulValueLen == 0) {
3e8b5b
+	if (key_attr[2].ulValueLen == 0 ||
3e8b5b
+	    key_attr[3].ulValueLen == 0) {
3e8b5b
 		error("invalid attribute length");
3e8b5b
 		return (NULL);
3e8b5b
 	}
3e8b5b
 
3e8b5b
 	/* allocate buffers for attributes */
3e8b5b
-	for (i = 0; i < 3; i++)
3e8b5b
+	for (i = 0; i < nattr; i++)
3e8b5b
 		if (key_attr[i].ulValueLen > 0)
3e8b5b
 			key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
3e8b5b
 
3e8b5b
 	/* retrieve ID, modulus and public exponent of RSA key */
3e8b5b
-	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
3e8b5b
+	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
3e8b5b
 	if (rv != CKR_OK) {
3e8b5b
 		error("C_GetAttributeValue failed: %lu", rv);
3e8b5b
 		goto fail;
3e8b5b
@@ -873,8 +1011,8 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	rsa_n = BN_bin2bn(key_attr[1].pValue, key_attr[1].ulValueLen, NULL);
3e8b5b
-	rsa_e = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL);
3e8b5b
+	rsa_n = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL);
3e8b5b
+	rsa_e = BN_bin2bn(key_attr[3].pValue, key_attr[3].ulValueLen, NULL);
3e8b5b
 	if (rsa_n == NULL || rsa_e == NULL) {
3e8b5b
 		error("BN_bin2bn failed");
3e8b5b
 		goto fail;
3e8b5b
@@ -883,7 +1021,7 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 		fatal("%s: set key", __func__);
3e8b5b
 	rsa_n = rsa_e = NULL; /* transferred */
3e8b5b
 
3e8b5b
-	if (pkcs11_rsa_wrap(p, slotidx, &key_attr[0], rsa))
3e8b5b
+	if (pkcs11_rsa_wrap(p, slotidx, &key_attr[0], &key_attr[1], rsa))
3e8b5b
 		goto fail;
3e8b5b
 
3e8b5b
 	key = sshkey_new(KEY_UNSPEC);
3e8b5b
@@ -898,7 +1036,7 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 	rsa = NULL;	/* now owned by key */
3e8b5b
 
3e8b5b
 fail:
3e8b5b
-	for (i = 0; i < 3; i++)
3e8b5b
+	for (i = 0; i < nattr; i++)
3e8b5b
 		free(key_attr[i].pValue);
3e8b5b
 	RSA_free(rsa);
3e8b5b
 
3e8b5b
@@ -909,7 +1047,8 @@ static struct sshkey *
3e8b5b
 pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
     CK_OBJECT_HANDLE *obj)
3e8b5b
 {
3e8b5b
-	CK_ATTRIBUTE		 cert_attr[3];
3e8b5b
+	CK_ATTRIBUTE		 cert_attr[4];
3e8b5b
+	int			 nattr = 4;
3e8b5b
 	CK_SESSION_HANDLE	 session;
3e8b5b
 	CK_FUNCTION_LIST	*f = NULL;
3e8b5b
 	CK_RV			 rv;
3e8b5b
@@ -926,14 +1065,15 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 
3e8b5b
 	memset(&cert_attr, 0, sizeof(cert_attr));
3e8b5b
 	cert_attr[0].type = CKA_ID;
3e8b5b
-	cert_attr[1].type = CKA_SUBJECT;
3e8b5b
-	cert_attr[2].type = CKA_VALUE;
3e8b5b
+	cert_attr[1].type = CKA_LABEL;
3e8b5b
+	cert_attr[2].type = CKA_SUBJECT;
3e8b5b
+	cert_attr[3].type = CKA_VALUE;
3e8b5b
 
3e8b5b
-	session = p->slotinfo[slotidx].session;
3e8b5b
-	f = p->function_list;
3e8b5b
+	session = p->module->slotinfo[slotidx].session;
3e8b5b
+	f = p->module->function_list;
3e8b5b
 
3e8b5b
 	/* figure out size of the attributes */
3e8b5b
-	rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3);
3e8b5b
+	rv = f->C_GetAttributeValue(session, *obj, cert_attr, nattr);
3e8b5b
 	if (rv != CKR_OK) {
3e8b5b
 		error("C_GetAttributeValue failed: %lu", rv);
3e8b5b
 		return (NULL);
3e8b5b
@@ -945,18 +1085,19 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 	 * XXX assumes CKA_ID is always first.
3e8b5b
 	 */
3e8b5b
 	if (cert_attr[1].ulValueLen == 0 ||
3e8b5b
-	    cert_attr[2].ulValueLen == 0) {
3e8b5b
+	    cert_attr[2].ulValueLen == 0 ||
3e8b5b
+	    cert_attr[3].ulValueLen == 0) {
3e8b5b
 		error("invalid attribute length");
3e8b5b
 		return (NULL);
3e8b5b
 	}
3e8b5b
 
3e8b5b
 	/* allocate buffers for attributes */
3e8b5b
-	for (i = 0; i < 3; i++)
3e8b5b
+	for (i = 0; i < nattr; i++)
3e8b5b
 		if (cert_attr[i].ulValueLen > 0)
3e8b5b
 			cert_attr[i].pValue = xcalloc(1, cert_attr[i].ulValueLen);
3e8b5b
 
3e8b5b
 	/* retrieve ID, subject and value of certificate */
3e8b5b
-	rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3);
3e8b5b
+	rv = f->C_GetAttributeValue(session, *obj, cert_attr, nattr);
3e8b5b
 	if (rv != CKR_OK) {
3e8b5b
 		error("C_GetAttributeValue failed: %lu", rv);
3e8b5b
 		goto fail;
3e8b5b
@@ -968,8 +1109,8 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
 
3e8b5b
-	cp = cert_attr[2].pValue;
3e8b5b
-	if (d2i_X509(&x509, &cp, cert_attr[2].ulValueLen) == NULL) {
3e8b5b
+	cp = cert_attr[3].pValue;
3e8b5b
+	if (d2i_X509(&x509, &cp, cert_attr[3].ulValueLen) == NULL) {
3e8b5b
 		error("d2i_x509 failed");
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
@@ -990,7 +1131,7 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 			goto fail;
3e8b5b
 		}
3e8b5b
 
3e8b5b
-		if (pkcs11_rsa_wrap(p, slotidx, &cert_attr[0], rsa))
3e8b5b
+		if (pkcs11_rsa_wrap(p, slotidx, &cert_attr[0], &cert_attr[1], rsa))
3e8b5b
 			goto fail;
3e8b5b
 
3e8b5b
 		key = sshkey_new(KEY_UNSPEC);
3e8b5b
@@ -1020,7 +1161,7 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 			goto fail;
3e8b5b
 		}
3e8b5b
 
3e8b5b
-		if (pkcs11_ecdsa_wrap(p, slotidx, &cert_attr[0], ec))
3e8b5b
+		if (pkcs11_ecdsa_wrap(p, slotidx, &cert_attr[0], &cert_attr[1], ec))
3e8b5b
 			goto fail;
3e8b5b
 
3e8b5b
 		key = sshkey_new(KEY_UNSPEC);
3e8b5b
@@ -1039,7 +1180,7 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 		error("unknown certificate key type");
3e8b5b
 
3e8b5b
 fail:
3e8b5b
-	for (i = 0; i < 3; i++)
3e8b5b
+	for (i = 0; i < nattr; i++)
3e8b5b
 		free(cert_attr[i].pValue);
3e8b5b
 	X509_free(x509);
3e8b5b
 	RSA_free(rsa);
3e8b5b
@@ -1066,11 +1207,12 @@ have_rsa_key(const RSA *rsa)
3e8b5b
  */
3e8b5b
 static int
3e8b5b
 pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
-    struct sshkey ***keysp, int *nkeys)
3e8b5b
+    struct sshkey ***keysp, int *nkeys, struct pkcs11_uri *uri)
3e8b5b
 {
3e8b5b
 	struct sshkey		*key = NULL;
3e8b5b
 	CK_OBJECT_CLASS		 key_class;
3e8b5b
-	CK_ATTRIBUTE		 key_attr[1];
3e8b5b
+	CK_ATTRIBUTE		 key_attr[3];
3e8b5b
+	int			 nattr = 1;
3e8b5b
 	CK_SESSION_HANDLE	 session;
3e8b5b
 	CK_FUNCTION_LIST	*f = NULL;
3e8b5b
 	CK_RV			 rv;
3e8b5b
@@ -1086,10 +1228,23 @@ pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 	key_attr[0].pValue = &key_class;
3e8b5b
 	key_attr[0].ulValueLen = sizeof(key_class);
3e8b5b
 
3e8b5b
-	session = p->slotinfo[slotidx].session;
3e8b5b
-	f = p->function_list;
3e8b5b
+	if (uri->id != NULL) {
3e8b5b
+		key_attr[nattr].type = CKA_ID;
3e8b5b
+		key_attr[nattr].pValue = uri->id;
3e8b5b
+		key_attr[nattr].ulValueLen = uri->id_len;
3e8b5b
+		nattr++;
3e8b5b
+	}
3e8b5b
+	if (uri->object != NULL) {
3e8b5b
+		key_attr[nattr].type = CKA_LABEL;
3e8b5b
+		key_attr[nattr].pValue = uri->object;
3e8b5b
+		key_attr[nattr].ulValueLen = strlen(uri->object);
3e8b5b
+		nattr++;
3e8b5b
+	}
3e8b5b
 
3e8b5b
-	rv = f->C_FindObjectsInit(session, key_attr, 1);
3e8b5b
+	session = p->module->slotinfo[slotidx].session;
3e8b5b
+	f = p->module->function_list;
3e8b5b
+
3e8b5b
+	rv = f->C_FindObjectsInit(session, key_attr, nattr);
3e8b5b
 	if (rv != CKR_OK) {
3e8b5b
 		error("C_FindObjectsInit failed: %lu", rv);
3e8b5b
 		goto fail;
3e8b5b
@@ -1163,11 +1318,12 @@ fail:
3e8b5b
  */
3e8b5b
 static int
3e8b5b
 pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
-    struct sshkey ***keysp, int *nkeys)
3e8b5b
+    struct sshkey ***keysp, int *nkeys, struct pkcs11_uri *uri)
3e8b5b
 {
3e8b5b
 	struct sshkey		*key = NULL;
3e8b5b
 	CK_OBJECT_CLASS		 key_class;
3e8b5b
-	CK_ATTRIBUTE		 key_attr[1];
3e8b5b
+	CK_ATTRIBUTE		 key_attr[3];
3e8b5b
+	int			 nattr = 1;
3e8b5b
 	CK_SESSION_HANDLE	 session;
3e8b5b
 	CK_FUNCTION_LIST	*f = NULL;
3e8b5b
 	CK_RV			 rv;
3e8b5b
@@ -1183,10 +1339,23 @@ pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 	key_attr[0].pValue = &key_class;
3e8b5b
 	key_attr[0].ulValueLen = sizeof(key_class);
3e8b5b
 
3e8b5b
-	session = p->slotinfo[slotidx].session;
3e8b5b
-	f = p->function_list;
3e8b5b
+	if (uri->id != NULL) {
3e8b5b
+		key_attr[nattr].type = CKA_ID;
3e8b5b
+		key_attr[nattr].pValue = uri->id;
3e8b5b
+		key_attr[nattr].ulValueLen = uri->id_len;
3e8b5b
+		nattr++;
3e8b5b
+	}
3e8b5b
+	if (uri->object != NULL) {
3e8b5b
+		key_attr[nattr].type = CKA_LABEL;
3e8b5b
+		key_attr[nattr].pValue = uri->object;
3e8b5b
+		key_attr[nattr].ulValueLen = strlen(uri->object);
3e8b5b
+		nattr++;
3e8b5b
+	}
3e8b5b
 
3e8b5b
-	rv = f->C_FindObjectsInit(session, key_attr, 1);
3e8b5b
+	session = p->module->slotinfo[slotidx].session;
3e8b5b
+	f = p->module->function_list;
3e8b5b
+
3e8b5b
+	rv = f->C_FindObjectsInit(session, key_attr, nattr);
3e8b5b
 	if (rv != CKR_OK) {
3e8b5b
 		error("C_FindObjectsInit failed: %lu", rv);
3e8b5b
 		goto fail;
3e8b5b
@@ -1443,15 +1612,10 @@ pkcs11_ecdsa_generate_private_key(struct pkcs11_provider *p, CK_ULONG slotidx,
3e8b5b
 }
3e8b5b
 #endif /* WITH_PKCS11_KEYGEN */
3e8b5b
 
3e8b5b
-/*
3e8b5b
- * register a new provider, fails if provider already exists. if
3e8b5b
- * keyp is provided, fetch keys.
3e8b5b
- */
3e8b5b
 static int
3e8b5b
-pkcs11_register_provider(char *provider_id, char *pin, struct sshkey ***keyp,
3e8b5b
-    struct pkcs11_provider **providerp, CK_ULONG user)
3e8b5b
+pkcs11_initialize_provider(struct pkcs11_uri *uri, struct pkcs11_provider **providerp)
3e8b5b
 {
3e8b5b
-	int nkeys, need_finalize = 0;
3e8b5b
+	int need_finalize = 0;
3e8b5b
 	int ret = -1;
3e8b5b
 	struct pkcs11_provider *p = NULL;
3e8b5b
 	void *handle = NULL;
3e8b5b
@@ -1460,148 +1624,285 @@ pkcs11_register_provider(char *provider_id, char *pin, struct sshkey ***keyp,
3e8b5b
 	CK_FUNCTION_LIST *f = NULL;
3e8b5b
 	CK_TOKEN_INFO *token;
3e8b5b
 	CK_ULONG i;
3e8b5b
-
3e8b5b
-	if (providerp == NULL)
3e8b5b
+	char *provider_module = NULL;
3e8b5b
+	struct pkcs11_module *m = NULL;
3e8b5b
+
3e8b5b
+	/* if no provider specified, fallback to p11-kit */
3e8b5b
+	if (uri->module_path == NULL) {
3e8b5b
+#ifdef PKCS11_DEFAULT_PROVIDER
3e8b5b
+		provider_module = strdup(PKCS11_DEFAULT_PROVIDER);
3e8b5b
+#else
3e8b5b
+		error("%s: No module path provided", __func__);
3e8b5b
 		goto fail;
3e8b5b
-	*providerp = NULL;
3e8b5b
-
3e8b5b
-	if (keyp != NULL)
3e8b5b
-		*keyp = NULL;
3e8b5b
+#endif
3e8b5b
+	} else {
3e8b5b
+		provider_module = strdup(uri->module_path);
3e8b5b
+	}
3e8b5b
 
3e8b5b
-	if (pkcs11_provider_lookup(provider_id) != NULL) {
3e8b5b
-		debug("%s: provider already registered: %s",
3e8b5b
-		    __func__, provider_id);
3e8b5b
-		goto fail;
3e8b5b
+	p = xcalloc(1, sizeof(*p));
3e8b5b
+	p->name = pkcs11_uri_get(uri);
3e8b5b
+
3e8b5b
+	if ((m = pkcs11_provider_lookup_module(provider_module)) != NULL
3e8b5b
+	   && m->valid) {
3e8b5b
+		debug("%s: provider module already initialized: %s",
3e8b5b
+		    __func__, provider_module);
3e8b5b
+		free(provider_module);
3e8b5b
+		/* Skip the initialization of PKCS#11 module */
3e8b5b
+		m->refcount++;
3e8b5b
+		p->module = m;
3e8b5b
+		p->valid = 1;
3e8b5b
+		TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
3e8b5b
+		p->refcount++;	/* add to provider list */
3e8b5b
+		*providerp = p;
3e8b5b
+		return 0;
3e8b5b
+	} else {
3e8b5b
+		m = xcalloc(1, sizeof(*m));
3e8b5b
+		p->module = m;
3e8b5b
+		m->refcount++;
3e8b5b
 	}
3e8b5b
+
3e8b5b
 	/* open shared pkcs11-library */
3e8b5b
-	if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) {
3e8b5b
-		error("dlopen %s failed: %s", provider_id, dlerror());
3e8b5b
+	if ((handle = dlopen(provider_module, RTLD_NOW)) == NULL) {
3e8b5b
+		error("dlopen %s failed: %s", provider_module, dlerror());
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
 	if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL) {
3e8b5b
 		error("dlsym(C_GetFunctionList) failed: %s", dlerror());
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
-	p = xcalloc(1, sizeof(*p));
3e8b5b
-	p->name = xstrdup(provider_id);
3e8b5b
-	p->handle = handle;
3e8b5b
+
3e8b5b
+	p->module->handle = handle;
3e8b5b
 	/* setup the pkcs11 callbacks */
3e8b5b
 	if ((rv = (*getfunctionlist)(&f)) != CKR_OK) {
3e8b5b
 		error("C_GetFunctionList for provider %s failed: %lu",
3e8b5b
-		    provider_id, rv);
3e8b5b
+		    provider_module, rv);
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
-	p->function_list = f;
3e8b5b
+	m->function_list = f;
3e8b5b
 	if ((rv = f->C_Initialize(NULL)) != CKR_OK) {
3e8b5b
 		error("C_Initialize for provider %s failed: %lu",
3e8b5b
-		    provider_id, rv);
3e8b5b
+		    provider_module, rv);
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
 	need_finalize = 1;
3e8b5b
-	if ((rv = f->C_GetInfo(&p->info)) != CKR_OK) {
3e8b5b
+	if ((rv = f->C_GetInfo(&m->info)) != CKR_OK) {
3e8b5b
 		error("C_GetInfo for provider %s failed: %lu",
3e8b5b
-		    provider_id, rv);
3e8b5b
+		    provider_module, rv);
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
-	rmspace(p->info.manufacturerID, sizeof(p->info.manufacturerID));
3e8b5b
-	rmspace(p->info.libraryDescription, sizeof(p->info.libraryDescription));
3e8b5b
+	rmspace(m->info.manufacturerID, sizeof(m->info.manufacturerID));
3e8b5b
+	if (uri->lib_manuf != NULL &&
3e8b5b
+	    strcmp(uri->lib_manuf, m->info.manufacturerID)) {
3e8b5b
+		debug("%s: Skipping provider %s not matching library_manufacturer",
3e8b5b
+		    __func__, m->info.manufacturerID);
3e8b5b
+		goto fail;
3e8b5b
+	}
3e8b5b
+	rmspace(m->info.libraryDescription, sizeof(m->info.libraryDescription));
3e8b5b
 	debug("provider %s: manufacturerID <%s> cryptokiVersion %d.%d"
3e8b5b
 	    " libraryDescription <%s> libraryVersion %d.%d",
3e8b5b
-	    provider_id,
3e8b5b
-	    p->info.manufacturerID,
3e8b5b
-	    p->info.cryptokiVersion.major,
3e8b5b
-	    p->info.cryptokiVersion.minor,
3e8b5b
-	    p->info.libraryDescription,
3e8b5b
-	    p->info.libraryVersion.major,
3e8b5b
-	    p->info.libraryVersion.minor);
3e8b5b
-	if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) {
3e8b5b
+	    provider_module,
3e8b5b
+	    m->info.manufacturerID,
3e8b5b
+	    m->info.cryptokiVersion.major,
3e8b5b
+	    m->info.cryptokiVersion.minor,
3e8b5b
+	    m->info.libraryDescription,
3e8b5b
+	    m->info.libraryVersion.major,
3e8b5b
+	    m->info.libraryVersion.minor);
3e8b5b
+
3e8b5b
+	if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &m->nslots)) != CKR_OK) {
3e8b5b
 		error("C_GetSlotList failed: %lu", rv);
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
-	if (p->nslots == 0) {
3e8b5b
+	if (m->nslots == 0) {
3e8b5b
 		error("%s: provider %s returned no slots", __func__,
3e8b5b
-		    provider_id);
3e8b5b
+		    provider_module);
3e8b5b
 		ret = -SSH_PKCS11_ERR_NO_SLOTS;
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
-	p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID));
3e8b5b
-	if ((rv = f->C_GetSlotList(CK_TRUE, p->slotlist, &p->nslots))
3e8b5b
+	m->slotlist = xcalloc(m->nslots, sizeof(CK_SLOT_ID));
3e8b5b
+	if ((rv = f->C_GetSlotList(CK_TRUE, m->slotlist, &m->nslots))
3e8b5b
 	    != CKR_OK) {
3e8b5b
 		error("C_GetSlotList for provider %s failed: %lu",
3e8b5b
-		    provider_id, rv);
3e8b5b
+		    provider_module, rv);
3e8b5b
 		goto fail;
3e8b5b
 	}
3e8b5b
-	p->slotinfo = xcalloc(p->nslots, sizeof(struct pkcs11_slotinfo));
3e8b5b
 	p->valid = 1;
3e8b5b
-	nkeys = 0;
3e8b5b
-	for (i = 0; i < p->nslots; i++) {
3e8b5b
-		token = &p->slotinfo[i].token;
3e8b5b
-		if ((rv = f->C_GetTokenInfo(p->slotlist[i], token))
3e8b5b
+	m->slotinfo = xcalloc(m->nslots, sizeof(struct pkcs11_slotinfo));
3e8b5b
+	m->valid = 1;
3e8b5b
+	for (i = 0; i < m->nslots; i++) {
3e8b5b
+		token = &m->slotinfo[i].token;
3e8b5b
+		if ((rv = f->C_GetTokenInfo(m->slotlist[i], token))
3e8b5b
 		    != CKR_OK) {
3e8b5b
 			error("C_GetTokenInfo for provider %s slot %lu "
3e8b5b
-			    "failed: %lu", provider_id, (unsigned long)i, rv);
3e8b5b
+			    "failed: %lu", provider_module, (unsigned long)i, rv);
3e8b5b
+			token->flags = 0;
3e8b5b
 			continue;
3e8b5b
 		}
3e8b5b
+		rmspace(token->label, sizeof(token->label));
3e8b5b
+		rmspace(token->manufacturerID, sizeof(token->manufacturerID));
3e8b5b
+		rmspace(token->model, sizeof(token->model));
3e8b5b
+		rmspace(token->serialNumber, sizeof(token->serialNumber));
3e8b5b
+	}
3e8b5b
+	m->module_path = provider_module;
3e8b5b
+	provider_module = NULL;
3e8b5b
+
3e8b5b
+	/* insert unconditionally -- remove if there will be no keys later */
3e8b5b
+	TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
3e8b5b
+	p->refcount++;	/* add to provider list */
3e8b5b
+	*providerp = p;
3e8b5b
+	return 0;
3e8b5b
+
3e8b5b
+fail:
3e8b5b
+	if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK)
3e8b5b
+		error("C_Finalize for provider %s failed: %lu",
3e8b5b
+		    provider_module, rv);
3e8b5b
+	free(provider_module);
3e8b5b
+	if (m) {
3e8b5b
+		free(m->slotlist);
3e8b5b
+		free(m);
3e8b5b
+	}
3e8b5b
+	if (p) {
3e8b5b
+		free(p->name);
3e8b5b
+		free(p);
3e8b5b
+	}
3e8b5b
+	if (handle)
3e8b5b
+		dlclose(handle);
3e8b5b
+	return ret;
3e8b5b
+}
3e8b5b
+
3e8b5b
+/*
3e8b5b
+ * register a new provider, fails if provider already exists. if
3e8b5b
+ * keyp is provided, fetch keys.
3e8b5b
+ */
3e8b5b
+static int
3e8b5b
+pkcs11_register_provider_by_uri(struct pkcs11_uri *uri, char *pin,
3e8b5b
+    struct sshkey ***keyp, struct pkcs11_provider **providerp, CK_ULONG user)
3e8b5b
+{
3e8b5b
+	int nkeys;
3e8b5b
+	int ret = -1;
3e8b5b
+	struct pkcs11_provider *p = NULL;
3e8b5b
+	CK_ULONG i;
3e8b5b
+	CK_TOKEN_INFO *token;
3e8b5b
+	char *provider_uri = NULL;
3e8b5b
+
3e8b5b
+	if (providerp == NULL)
3e8b5b
+		goto fail;
3e8b5b
+	*providerp = NULL;
3e8b5b
+
3e8b5b
+	if (keyp != NULL)
3e8b5b
+		*keyp = NULL;
3e8b5b
+
3e8b5b
+	if ((ret = pkcs11_initialize_provider(uri, &p)) != 0) {
3e8b5b
+		goto fail;
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	provider_uri = pkcs11_uri_get(uri);
3e8b5b
+	nkeys = 0;
3e8b5b
+	for (i = 0; i < p->module->nslots; i++) {
3e8b5b
+		token = &p->module->slotinfo[i].token;
3e8b5b
 		if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) {
3e8b5b
 			debug2("%s: ignoring uninitialised token in "
3e8b5b
 			    "provider %s slot %lu", __func__,
3e8b5b
-			    provider_id, (unsigned long)i);
3e8b5b
+			    provider_uri, (unsigned long)i);
3e8b5b
+			continue;
3e8b5b
+		}
3e8b5b
+		if (uri->token != NULL &&
3e8b5b
+		    strcmp(token->label, uri->token) != 0) {
3e8b5b
+			debug2("%s: ignoring token not matching label (%s) "
3e8b5b
+			    "specified by PKCS#11 URI in slot %lu", __func__,
3e8b5b
+			    token->label, (unsigned long)i);
3e8b5b
+			continue;
3e8b5b
+		}
3e8b5b
+		if (uri->manuf != NULL &&
3e8b5b
+		    strcmp(token->manufacturerID, uri->manuf) != 0) {
3e8b5b
+			debug2("%s: ignoring token not matching requrested "
3e8b5b
+			    "manufacturerID (%s) specified by PKCS#11 URI in "
3e8b5b
+			    "slot %lu", __func__,
3e8b5b
+			    token->manufacturerID, (unsigned long)i);
3e8b5b
 			continue;
3e8b5b
 		}
3e8b5b
-		rmspace(token->label, sizeof(token->label));
3e8b5b
-		rmspace(token->manufacturerID, sizeof(token->manufacturerID));
3e8b5b
-		rmspace(token->model, sizeof(token->model));
3e8b5b
-		rmspace(token->serialNumber, sizeof(token->serialNumber));
3e8b5b
 		debug("provider %s slot %lu: label <%s> manufacturerID <%s> "
3e8b5b
 		    "model <%s> serial <%s> flags 0x%lx",
3e8b5b
-		    provider_id, (unsigned long)i,
3e8b5b
+		    provider_uri, (unsigned long)i,
3e8b5b
 		    token->label, token->manufacturerID, token->model,
3e8b5b
 		    token->serialNumber, token->flags);
3e8b5b
+		if (pin == NULL && uri->pin != NULL) {
3e8b5b
+			pin = uri->pin;
3e8b5b
+		}
3e8b5b
 		/*
3e8b5b
-		 * open session, login with pin and retrieve public
3e8b5b
-		 * keys (if keyp is provided)
3e8b5b
+		 * open session if not yet openend, login with pin and
3e8b5b
+		 * retrieve public keys (if keyp is provided)
3e8b5b
 		 */
3e8b5b
-		if ((ret = pkcs11_open_session(p, i, pin, user)) == 0) {
3e8b5b
+		if (p->module->slotinfo[i].session != 0 ||
3e8b5b
+		    (ret = pkcs11_open_session(p, i, pin, user)) == 0) {
3e8b5b
 			if (keyp == NULL)
3e8b5b
 				continue;
3e8b5b
-			pkcs11_fetch_keys(p, i, keyp, &nkeys);
3e8b5b
-			pkcs11_fetch_certs(p, i, keyp, &nkeys);
3e8b5b
+			pkcs11_fetch_keys(p, i, keyp, &nkeys, uri);
3e8b5b
+			pkcs11_fetch_certs(p, i, keyp, &nkeys, uri);
3e8b5b
+			if (nkeys == 0 && uri->object != NULL) {
3e8b5b
+				debug3("%s: No keys found. Retrying without label (%s) ",
3e8b5b
+				    __func__, token->label);
3e8b5b
+				/* Try once more without the label filter */
3e8b5b
+				char *label = uri->object;
3e8b5b
+				uri->object = NULL; /* XXX clone uri? */
3e8b5b
+				pkcs11_fetch_keys(p, i, keyp, &nkeys, uri);
3e8b5b
+				pkcs11_fetch_certs(p, i, keyp, &nkeys, uri);
3e8b5b
+				uri->object = label;
3e8b5b
+			}
3e8b5b
 		}
3e8b5b
+		pin = NULL; /* Will be cleaned up with URI */
3e8b5b
 	}
3e8b5b
 
3e8b5b
 	/* now owned by caller */
3e8b5b
 	*providerp = p;
3e8b5b
 
3e8b5b
-	TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
3e8b5b
-	p->refcount++;	/* add to provider list */
3e8b5b
-
3e8b5b
+	free(provider_uri);
3e8b5b
 	return (nkeys);
3e8b5b
 fail:
3e8b5b
-	if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK)
3e8b5b
-		error("C_Finalize for provider %s failed: %lu",
3e8b5b
-		    provider_id, rv);
3e8b5b
 	if (p) {
3e8b5b
-		free(p->name);
3e8b5b
-		free(p->slotlist);
3e8b5b
-		free(p->slotinfo);
3e8b5b
-		free(p);
3e8b5b
+ 		TAILQ_REMOVE(&pkcs11_providers, p, next);
3e8b5b
+		pkcs11_provider_unref(p);
3e8b5b
 	}
3e8b5b
-	if (handle)
3e8b5b
-		dlclose(handle);
3e8b5b
 	return (ret);
3e8b5b
 }
3e8b5b
 
3e8b5b
-/*
3e8b5b
- * register a new provider and get number of keys hold by the token,
3e8b5b
- * fails if provider already exists
3e8b5b
- */
3e8b5b
+static int
3e8b5b
+pkcs11_register_provider(char *provider_id, char *pin, struct sshkey ***keyp,
3e8b5b
+    struct pkcs11_provider **providerp, CK_ULONG user)
3e8b5b
+{
3e8b5b
+	struct pkcs11_uri *uri = NULL;
3e8b5b
+	int r;
3e8b5b
+
3e8b5b
+	debug("%s: called, provider_id = %s", __func__, provider_id);
3e8b5b
+
3e8b5b
+	uri = pkcs11_uri_init();
3e8b5b
+	if (uri == NULL)
3e8b5b
+		fatal("failed to init PKCS#11 URI");
3e8b5b
+
3e8b5b
+	if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
3e8b5b
+	    strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
3e8b5b
+		if (pkcs11_uri_parse(provider_id, uri) != 0)
3e8b5b
+			fatal("Failed to parse PKCS#11 URI");
3e8b5b
+	} else {
3e8b5b
+		uri->module_path = strdup(provider_id);
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	r = pkcs11_register_provider_by_uri(uri, pin, keyp, providerp, user);
3e8b5b
+	pkcs11_uri_cleanup(uri);
3e8b5b
+
3e8b5b
+	return r;
3e8b5b
+}
3e8b5b
+
3e8b5b
 int
3e8b5b
-pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp)
3e8b5b
+pkcs11_add_provider_by_uri(struct pkcs11_uri *uri, char *pin,
3e8b5b
+    struct sshkey ***keyp)
3e8b5b
 {
3e8b5b
-	struct pkcs11_provider *p = NULL;
3e8b5b
 	int nkeys;
3e8b5b
+	struct pkcs11_provider *p = NULL;
3e8b5b
+	char *provider_uri = pkcs11_uri_get(uri);
3e8b5b
 
3e8b5b
-	nkeys = pkcs11_register_provider(provider_id, pin, keyp, &p, CKU_USER);
3e8b5b
+	debug("%s: called, provider_uri = %s", __func__, provider_uri);
3e8b5b
+
3e8b5b
+	nkeys = pkcs11_register_provider_by_uri(uri, pin, keyp, &p, CKU_USER);
3e8b5b
 
3e8b5b
 	/* no keys found or some other error, de-register provider */
3e8b5b
 	if (nkeys <= 0 && p != NULL) {
3e8b5b
@@ -1611,7 +1912,36 @@ pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp)
3e8b5b
 	}
3e8b5b
 	if (nkeys == 0)
3e8b5b
 		debug("%s: provider %s returned no keys", __func__,
3e8b5b
-		    provider_id);
3e8b5b
+		    provider_uri);
3e8b5b
+
3e8b5b
+	free(provider_uri);
3e8b5b
+	return nkeys;
3e8b5b
+}
3e8b5b
+
3e8b5b
+/*
3e8b5b
+ * register a new provider and get number of keys hold by the token,
3e8b5b
+ * fails if provider already exists
3e8b5b
+ */
3e8b5b
+int
3e8b5b
+pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp)
3e8b5b
+{
3e8b5b
+	struct pkcs11_uri *uri;
3e8b5b
+	int nkeys;
3e8b5b
+
3e8b5b
+	uri = pkcs11_uri_init();
3e8b5b
+	if (uri == NULL)
3e8b5b
+		fatal("Failed to init PCKS#11 URI");
3e8b5b
+
3e8b5b
+	if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
3e8b5b
+	    strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
3e8b5b
+		if (pkcs11_uri_parse(provider_id, uri) != 0)
3e8b5b
+			fatal("Failed to parse PKCS#11 URI");
3e8b5b
+	} else {
3e8b5b
+		uri->module_path = strdup(provider_id);
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	nkeys = pkcs11_add_provider_by_uri(uri, pin, keyp);
3e8b5b
+	pkcs11_uri_cleanup(uri);
3e8b5b
 
3e8b5b
 	return (nkeys);
3e8b5b
 }
3e8b5b
@@ -1633,8 +1963,7 @@ pkcs11_gakp(char *provider_id, char *pin, unsigned int slotidx, char *label,
3e8b5b
 
3e8b5b
 	if ((p = pkcs11_provider_lookup(provider_id)) != NULL)
3e8b5b
 		debug("%s: provider \"%s\" available", __func__, provider_id);
3e8b5b
-	else if ((ret = pkcs11_register_provider(provider_id, pin, NULL, &p,
3e8b5b
-	    CKU_SO)) < 0) {
3e8b5b
+	else if ((rv = pkcs11_register_provider(provider_id, pin, NULL, &p, CKU_SO)) != 0) {
3e8b5b
 		debug("%s: could not register provider %s", __func__,
3e8b5b
 		    provider_id);
3e8b5b
 		goto out;
3e8b5b
@@ -1705,8 +2034,7 @@ pkcs11_destroy_keypair(char *provider_id, char *pin, unsigned long slotidx,
3e8b5b
 
3e8b5b
 	if ((p = pkcs11_provider_lookup(provider_id)) != NULL) {
3e8b5b
 		debug("%s: using provider \"%s\"", __func__, provider_id);
3e8b5b
-	} else if (pkcs11_register_provider(provider_id, pin, NULL, &p,
3e8b5b
-	    CKU_SO) < 0) {
3e8b5b
+	} else if ((rv = pkcs11_register_provider(provider_id, pin, NULL, &p, CKU_SO)) != 0) {
3e8b5b
 		debug("%s: could not register provider %s", __func__,
3e8b5b
 		    provider_id);
3e8b5b
 		goto out;
3e8b5b
diff --git a/ssh-pkcs11.h b/ssh-pkcs11.h
3e8b5b
index b9038450..5a855338 100644
3e8b5b
--- a/ssh-pkcs11.h
3e8b5b
+++ b/ssh-pkcs11.h
3e8b5b
@@ -22,10 +22,14 @@
3e8b5b
 #define	SSH_PKCS11_ERR_PIN_REQUIRED		4
3e8b5b
 #define	SSH_PKCS11_ERR_PIN_LOCKED		5
3e8b5b
 
3e8b5b
+#include "ssh-pkcs11-uri.h"
3e8b5b
+
3e8b5b
 int	pkcs11_init(int);
3e8b5b
 void	pkcs11_terminate(void);
3e8b5b
 int	pkcs11_add_provider(char *, char *, struct sshkey ***);
3e8b5b
+int	pkcs11_add_provider_by_uri(struct pkcs11_uri *, char *, struct sshkey ***);
3e8b5b
 int	pkcs11_del_provider(char *);
3e8b5b
+int	pkcs11_uri_write(const struct sshkey *, FILE *);
3e8b5b
 #ifdef WITH_PKCS11_KEYGEN
3e8b5b
 struct sshkey *
3e8b5b
 	pkcs11_gakp(char *, char *, unsigned int, char *, unsigned int,
3e8b5b
diff --git a/ssh.c b/ssh.c
3e8b5b
index 91e7c351..47f4f299 100644
3e8b5b
--- a/ssh.c
3e8b5b
+++ b/ssh.c
3e8b5b
@@ -772,6 +772,14 @@ main(int ac, char **av)
3e8b5b
 			options.gss_deleg_creds = 1;
3e8b5b
 			break;
3e8b5b
 		case 'i':
3e8b5b
+#ifdef ENABLE_PKCS11
3e8b5b
+			if (strlen(optarg) >= strlen(PKCS11_URI_SCHEME) &&
3e8b5b
+			    strncmp(optarg, PKCS11_URI_SCHEME,
3e8b5b
+			    strlen(PKCS11_URI_SCHEME)) == 0) {
3e8b5b
+				add_identity_file(&options, NULL, optarg, 1);
3e8b5b
+				break;
3e8b5b
+			}
3e8b5b
+#endif
3e8b5b
 			p = tilde_expand_filename(optarg, getuid());
3e8b5b
 			if (stat(p, &st) < 0)
3e8b5b
 				fprintf(stderr, "Warning: Identity file %s "
3e8b5b
@@ -1521,6 +1529,7 @@ main(int ac, char **av)
3e8b5b
 		free(options.certificate_files[i]);
3e8b5b
 		options.certificate_files[i] = NULL;
3e8b5b
 	}
3e8b5b
+	pkcs11_terminate();
3e8b5b
 
3e8b5b
  skip_connect:
3e8b5b
 	exit_status = ssh_session2(ssh, pw);
3e8b5b
@@ -1994,6 +2003,45 @@ ssh_session2(struct ssh *ssh, struct passwd *pw)
3e8b5b
 	    options.escape_char : SSH_ESCAPECHAR_NONE, id);
3e8b5b
 }
3e8b5b
 
3e8b5b
+#ifdef ENABLE_PKCS11
3e8b5b
+static void
3e8b5b
+load_pkcs11_identity(char *pkcs11_uri, char *identity_files[],
3e8b5b
+    struct sshkey *identity_keys[], int *n_ids)
3e8b5b
+{
3e8b5b
+	int nkeys, i;
3e8b5b
+	struct sshkey **keys;
3e8b5b
+	struct pkcs11_uri *uri;
3e8b5b
+
3e8b5b
+	debug("identity file '%s' from pkcs#11", pkcs11_uri);
3e8b5b
+	uri = pkcs11_uri_init();
3e8b5b
+	if (uri == NULL)
3e8b5b
+		fatal("Failed to init PCKS#11 URI");
3e8b5b
+
3e8b5b
+	if (pkcs11_uri_parse(pkcs11_uri, uri) != 0)
3e8b5b
+	fatal("Failed to parse PKCS#11 URI %s", pkcs11_uri);
3e8b5b
+
3e8b5b
+	/* we need to merge URI and provider together */
3e8b5b
+	if (options.pkcs11_provider != NULL && uri->module_path == NULL)
3e8b5b
+		uri->module_path = strdup(options.pkcs11_provider);
3e8b5b
+
3e8b5b
+	if (options.num_identity_files < SSH_MAX_IDENTITY_FILES &&
3e8b5b
+	    (nkeys = pkcs11_add_provider_by_uri(uri, NULL, &keys)) > 0) {
3e8b5b
+		for (i = 0; i < nkeys; i++) {
3e8b5b
+			if (*n_ids >= SSH_MAX_IDENTITY_FILES) {
3e8b5b
+				sshkey_free(keys[i]);
3e8b5b
+				continue;
3e8b5b
+			}
3e8b5b
+			identity_keys[*n_ids] = keys[i];
3e8b5b
+			identity_files[*n_ids] = pkcs11_uri_get(uri);
3e8b5b
+			(*n_ids)++;
3e8b5b
+		}
3e8b5b
+		free(keys);
3e8b5b
+	}
3e8b5b
+
3e8b5b
+	pkcs11_uri_cleanup(uri);
3e8b5b
+}
3e8b5b
+#endif /* ENABLE_PKCS11 */
3e8b5b
+
3e8b5b
 /* Loads all IdentityFile and CertificateFile keys */
3e8b5b
 static void
3e8b5b
 load_public_identity_files(struct passwd *pw)
3e8b5b
@@ -2008,10 +2056,6 @@ load_public_identity_files(struct passwd *pw)
3e8b5b
 	char *certificate_files[SSH_MAX_CERTIFICATE_FILES];
3e8b5b
 	struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES];
3e8b5b
 	int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES];
3e8b5b
-#ifdef ENABLE_PKCS11
3e8b5b
-	struct sshkey **keys;
3e8b5b
-	int nkeys;
3e8b5b
-#endif /* PKCS11 */
3e8b5b
 
3e8b5b
 	n_ids = n_certs = 0;
3e8b5b
 	memset(identity_files, 0, sizeof(identity_files));
3e8b5b
@@ -2024,32 +2068,46 @@ load_public_identity_files(struct passwd *pw)
3e8b5b
 	    sizeof(certificate_file_userprovided));
3e8b5b
 
3e8b5b
 #ifdef ENABLE_PKCS11
3e8b5b
-	if (options.pkcs11_provider != NULL &&
3e8b5b
-	    options.num_identity_files < SSH_MAX_IDENTITY_FILES &&
3e8b5b
-	    (pkcs11_init(!options.batch_mode) == 0) &&
3e8b5b
-	    (nkeys = pkcs11_add_provider(options.pkcs11_provider, NULL,
3e8b5b
-	    &keys)) > 0) {
3e8b5b
-		for (i = 0; i < nkeys; i++) {
3e8b5b
-			if (n_ids >= SSH_MAX_IDENTITY_FILES) {
3e8b5b
-				sshkey_free(keys[i]);
3e8b5b
-				continue;
3e8b5b
-			}
3e8b5b
-			identity_keys[n_ids] = keys[i];
3e8b5b
-			identity_files[n_ids] =
3e8b5b
-			    xstrdup(options.pkcs11_provider); /* XXX */
3e8b5b
-			n_ids++;
3e8b5b
-		}
3e8b5b
-		free(keys);
3e8b5b
+	/* handle fallback from PKCS11Provider option */
3e8b5b
+	pkcs11_init(!options.batch_mode);
3e8b5b
+
3e8b5b
+	if (options.pkcs11_provider != NULL) {
3e8b5b
+		struct pkcs11_uri *uri;
3e8b5b
+
3e8b5b
+		uri = pkcs11_uri_init();
3e8b5b
+		if (uri == NULL)
3e8b5b
+			fatal("Failed to init PCKS#11 URI");
3e8b5b
+
3e8b5b
+		/* Construct simple PKCS#11 URI to simplify access */
3e8b5b
+		uri->module_path = strdup(options.pkcs11_provider);
3e8b5b
+
3e8b5b
+		/* Add it as any other IdentityFile */
3e8b5b
+		cp = pkcs11_uri_get(uri);
3e8b5b
+		add_identity_file(&options, NULL, cp, 1);
3e8b5b
+		free(cp);
3e8b5b
+
3e8b5b
+		pkcs11_uri_cleanup(uri);
3e8b5b
 	}
3e8b5b
 #endif /* ENABLE_PKCS11 */
3e8b5b
 	for (i = 0; i < options.num_identity_files; i++) {
3e8b5b
+		char *name = options.identity_files[i];
3e8b5b
 		if (n_ids >= SSH_MAX_IDENTITY_FILES ||
3e8b5b
-		    strcasecmp(options.identity_files[i], "none") == 0) {
3e8b5b
+		    strcasecmp(name, "none") == 0) {
3e8b5b
 			free(options.identity_files[i]);
3e8b5b
 			options.identity_files[i] = NULL;
3e8b5b
 			continue;
3e8b5b
 		}
3e8b5b
-		cp = tilde_expand_filename(options.identity_files[i], getuid());
3e8b5b
+#ifdef ENABLE_PKCS11
3e8b5b
+		if (strlen(name) >= strlen(PKCS11_URI_SCHEME) &&
3e8b5b
+		    strncmp(name, PKCS11_URI_SCHEME,
3e8b5b
+		    strlen(PKCS11_URI_SCHEME)) == 0) {
3e8b5b
+			load_pkcs11_identity(name, identity_files,
3e8b5b
+			    identity_keys, &n_ids);
3e8b5b
+			free(options.identity_files[i]);
3e8b5b
+			continue;
3e8b5b
+		}
3e8b5b
+#endif /* ENABLE_PKCS11 */
3e8b5b
+		cp = tilde_expand_filename(name, getuid());
3e8b5b
 		filename = percent_expand(cp, "d", pw->pw_dir,
3e8b5b
 		    "u", pw->pw_name, "l", thishost, "h", host,
3e8b5b
 		    "r", options.user, (char *)NULL);
3e8b5b
diff --git a/ssh_config.5 b/ssh_config.5
3e8b5b
index 41262963..a211034e 100644
3e8b5b
--- a/ssh_config.5
3e8b5b
+++ b/ssh_config.5
3e8b5b
@@ -952,6 +952,21 @@ may also be used in conjunction with
3e8b5b
 .Cm CertificateFile
3e8b5b
 in order to provide any certificate also needed for authentication with
3e8b5b
 the identity.
3e8b5b
+.Pp
3e8b5b
+The authentication identity can be also specified in a form of PKCS#11 URI
3e8b5b
+starting with a string
3e8b5b
+.Cm pkcs11: .
3e8b5b
+There is supported a subset of the PKCS#11 URI as defined
3e8b5b
+in RFC 7512 (implemented path arguments
3e8b5b
+.Cm id ,
3e8b5b
+.Cm manufacturer ,
3e8b5b
+.Cm object ,
3e8b5b
+.Cm token
3e8b5b
+and query arguments
3e8b5b
+.Cm module-path
3e8b5b
+and
3e8b5b
+.Cm pin-value
3e8b5b
+). The URI can not be in quotes.
3e8b5b
 .It Cm IgnoreUnknown
3e8b5b
 Specifies a pattern-list of unknown options to be ignored if they are
3e8b5b
 encountered in configuration parsing.
3e8b5b
commit 1efe98998408593861fdcd4da392dd10820f0fde
3e8b5b
Author: Jakub Jelen <jjelen@redhat.com>
3e8b5b
Date:   Wed Jun 12 14:30:30 2019 +0200
3e8b5b
3e8b5b
    Allow to specify the pin also for the ssh-add
3e8b5b
3e8b5b
diff --git a/ssh-add.c b/ssh-add.c
3e8b5b
index f039e00e..adc4e5c9 100644
3e8b5b
--- a/ssh-add.c
3e8b5b
+++ b/ssh-add.c
3e8b5b
@@ -190,20 +190,28 @@ delete_all(int agent_fd, int qflag)
3e8b5b
 }
3e8b5b
 
3e8b5b
 #ifdef ENABLE_PKCS11
3e8b5b
-static int update_card(int, int, const char *, int);
3e8b5b
+static int update_card(int, int, const char *, int, char *);
3e8b5b
 
3e8b5b
 int
3e8b5b
 update_pkcs11_uri(int agent_fd, int adding, const char *pkcs11_uri, int qflag)
3e8b5b
 {
3e8b5b
+	char *pin = NULL;
3e8b5b
 	struct pkcs11_uri *uri;
3e8b5b
 
3e8b5b
 	/* dry-run parse to make sure the URI is valid and to report errors */
3e8b5b
 	uri = pkcs11_uri_init();
3e8b5b
 	if (pkcs11_uri_parse((char *) pkcs11_uri, uri) != 0)
3e8b5b
 		fatal("Failed to parse PKCS#11 URI");
3e8b5b
+	if (uri->pin != NULL) {
3e8b5b
+		pin = strdup(uri->pin);
3e8b5b
+		if (pin == NULL) {
3e8b5b
+			fatal("Failed to dupplicate string");
3e8b5b
+		}
3e8b5b
+		/* pin is freed in the update_card() */
3e8b5b
+	}
3e8b5b
 	pkcs11_uri_cleanup(uri);
3e8b5b
 
3e8b5b
-	return update_card(agent_fd, adding, pkcs11_uri, qflag);
3e8b5b
+	return update_card(agent_fd, adding, pkcs11_uri, qflag, pin);
3e8b5b
 }
3e8b5b
 #endif
3e8b5b
 
3e8b5b
@@ -409,12 +417,11 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag)
3e8b5b
 }
3e8b5b
 
3e8b5b
 static int
3e8b5b
-update_card(int agent_fd, int add, const char *id, int qflag)
3e8b5b
+update_card(int agent_fd, int add, const char *id, int qflag, char *pin)
3e8b5b
 {
3e8b5b
-	char *pin = NULL;
3e8b5b
 	int r, ret = -1;
3e8b5b
 
3e8b5b
-	if (add) {
3e8b5b
+	if (add && pin == NULL) {
3e8b5b
 		if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
3e8b5b
 		    RP_ALLOW_STDIN)) == NULL)
3e8b5b
 			return -1;
3e8b5b
@@ -734,7 +741,7 @@ main(int argc, char **argv)
3e8b5b
 	}
3e8b5b
 	if (pkcs11provider != NULL) {
3e8b5b
 		if (update_card(agent_fd, !deleting, pkcs11provider,
3e8b5b
-		    qflag) == -1)
3e8b5b
+		    qflag, NULL) == -1)
3e8b5b
 			ret = 1;
3e8b5b
 		goto done;
3e8b5b
 	}