diff --git a/Makefile.in b/Makefile.in index ac959c1f..f8ed1781 100644 --- a/Makefile.in +++ b/Makefile.in @@ -93,7 +93,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-rsa.o dh.o \ kexgssc.o \ msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \ - ssh-pkcs11.o smult_curve25519_ref.o \ + ssh-pkcs11.o ssh-pkcs11-uri.o smult_curve25519_ref.o \ poly1305.o chacha.o cipher-chachapoly.o \ ssh-ed25519.o digest-openssl.o digest-libc.o hmac.o \ sc25519.o ge25519.o fe25519.o ed25519.o verify.o hash.o \ @@ -248,6 +248,8 @@ clean: regressclean rm -f regress/unittests/match/test_match$(EXEEXT) rm -f regress/unittests/utf8/*.o rm -f regress/unittests/utf8/test_utf8$(EXEEXT) + rm -f regress/unittests/pkcs11/*.o + rm -f regress/unittests/pkcs11/test_pkcs11$(EXEEXT) rm -f regress/misc/kexfuzz/*.o rm -f regress/misc/kexfuzz/kexfuzz$(EXEEXT) (cd openbsd-compat && $(MAKE) clean) @@ -276,6 +278,8 @@ distclean: regressclean rm -f regress/unittests/match/test_match rm -f regress/unittests/utf8/*.o rm -f regress/unittests/utf8/test_utf8 + rm -f regress/unittests/pkcs11/*.o + rm -f regress/unittests/pkcs11/test_pkcs11 rm -f regress/unittests/misc/kexfuzz (cd openbsd-compat && $(MAKE) distclean) if test -d pkg ; then \ @@ -437,6 +441,7 @@ regress-prep: $(MKDIR_P) `pwd`/regress/unittests/kex $(MKDIR_P) `pwd`/regress/unittests/match $(MKDIR_P) `pwd`/regress/unittests/utf8 + $(MKDIR_P) `pwd`/regress/unittests/pkcs11 $(MKDIR_P) `pwd`/regress/misc/kexfuzz [ -f `pwd`/regress/Makefile ] || \ ln -s `cd $(srcdir) && pwd`/regress/Makefile `pwd`/regress/Makefile @@ -455,6 +460,11 @@ regress/netcat$(EXEEXT): $(srcdir)/regress/netcat.c $(REGRESSLIBS) $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(srcdir)/regress/netcat.c \ $(LDFLAGS) -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS) +regress/soft-pkcs11.so: $(srcdir)/regress/soft-pkcs11.c $(REGRESSLIBS) + $(CC) $(CFLAGS) $(CPPFLAGS) -fpic -c $(srcdir)/regress/soft-pkcs11.c \ + -o $(srcdir)/regress/soft-pkcs11.o + $(CC) -shared -o $@ $(srcdir)/regress/soft-pkcs11.o + regress/check-perm$(EXEEXT): $(srcdir)/regress/check-perm.c $(REGRESSLIBS) $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(srcdir)/regress/check-perm.c \ $(LDFLAGS) -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS) @@ -556,6 +566,16 @@ regress/unittests/utf8/test_utf8$(EXEEXT): \ regress/unittests/test_helper/libtest_helper.a \ -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS) +UNITTESTS_TEST_PKCS11_OBJS=\ + regress/unittests/pkcs11/tests.o + +regress/unittests/pkcs11/test_pkcs11$(EXEEXT): \ + ${UNITTESTS_TEST_PKCS11_OBJS} \ + regress/unittests/test_helper/libtest_helper.a libssh.a + $(LD) -o $@ $(LDFLAGS) $(UNITTESTS_TEST_PKCS11_OBJS) \ + regress/unittests/test_helper/libtest_helper.a \ + -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS) + MISC_KEX_FUZZ_OBJS=\ regress/misc/kexfuzz/kexfuzz.o @@ -566,6 +586,7 @@ regress/misc/kexfuzz/kexfuzz$(EXEEXT): ${MISC_KEX_FUZZ_OBJS} libssh.a regress-binaries: regress/modpipe$(EXEEXT) \ regress/setuid-allowed$(EXEEXT) \ regress/netcat$(EXEEXT) \ + regress/soft-pkcs11.so \ regress/check-perm$(EXEEXT) \ regress/unittests/sshbuf/test_sshbuf$(EXEEXT) \ regress/unittests/sshkey/test_sshkey$(EXEEXT) \ @@ -575,6 +596,7 @@ regress-binaries: regress/modpipe$(EXEEXT) \ regress/unittests/kex/test_kex$(EXEEXT) \ regress/unittests/match/test_match$(EXEEXT) \ regress/unittests/utf8/test_utf8$(EXEEXT) \ + regress/unittests/pkcs11/test_pkcs11$(EXEEXT) \ regress/misc/kexfuzz/kexfuzz$(EXEEXT) REGRESSTMP = "$(PWD)/regress" diff --git a/authfd.c b/authfd.c index 1eff7ba9..35153f47 100644 --- a/authfd.c +++ b/authfd.c @@ -312,6 +312,8 @@ ssh_free_identitylist(struct ssh_identitylist *idl) if (idl->comments != NULL) free(idl->comments[i]); } + free(idl->keys); + free(idl->comments); free(idl); } diff --git a/configure.ac b/configure.ac index d7bcaf01..171a8597 100644 --- a/configure.ac +++ b/configure.ac @@ -1895,12 +1895,14 @@ AC_LINK_IFELSE( [AC_DEFINE([HAVE_ISBLANK], [1], [Define if you have isblank(3C).]) ]) +SCARD_MSG="yes" disable_pkcs11= AC_ARG_ENABLE([pkcs11], [ --disable-pkcs11 disable PKCS#11 support code [no]], [ if test "x$enableval" = "xno" ; then disable_pkcs11=1 + SCARD_MSG="no" fi ] ) @@ -1916,6 +1918,40 @@ if test "x$openssl" = "xyes" && test "x$disable_pkcs11" = "x"; then ) fi +# Check whether we have a p11-kit, we got default provider on command line +DEFAULT_PKCS11_PROVIDER_MSG="no" +AC_ARG_WITH([default-pkcs11-provider], + [ --with-default-pkcs11-provider[[=PATH]] Use default pkcs11 provider (p11-kit detected by default)], + [ if test "x$withval" != "xno" && test "x$disable_pkcs11" = "x"; then + if test "x$withval" = "xyes" ; then + AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no]) + if test "x$PKGCONFIG" != "xno"; then + AC_MSG_CHECKING([if $PKGCONFIG knows about p11-kit]) + if "$PKGCONFIG" "p11-kit-1"; then + AC_MSG_RESULT([yes]) + use_pkgconfig_for_p11kit=yes + else + AC_MSG_RESULT([no]) + fi + fi + else + PKCS11_PATH="${withval}" + fi + if test "x$use_pkgconfig_for_p11kit" = "xyes"; then + PKCS11_PATH=`$PKGCONFIG --variable=proxy_module p11-kit-1` + fi + AC_CHECK_FILE("$PKCS11_PATH", + [ AC_DEFINE_UNQUOTED([PKCS11_DEFAULT_PROVIDER], ["$PKCS11_PATH"], [Path to default PKCS#11 provider (p11-kit proxy)]) + DEFAULT_PKCS11_PROVIDER_MSG="$PKCS11_PATH" + ], + [ AC_MSG_ERROR([Requested PKCS11 provided not found]) ] + ) + else + AC_MSG_WARN([Needs PKCS11 support to enable default pkcs11 provider]) + fi ] +) + + # IRIX has a const char return value for gai_strerror() AC_CHECK_FUNCS([gai_strerror], [ AC_DEFINE([HAVE_GAI_STRERROR]) @@ -5226,6 +5262,7 @@ echo " Translate v4 in v6 hack: $IPV4_IN6_HACK_MSG" echo " Random number source: $RAND_MSG" echo " Privsep sandbox style: $SANDBOX_STYLE" echo " Vendor patch level: $SSH_VENDOR_PATCHLEVEL" +echo " Default PKCS#11 provider: $DEFAULT_PKCS11_PROVIDER_MSG" echo "" diff --git a/regress/Makefile b/regress/Makefile index d15898ad..9c15afa4 100644 --- a/regress/Makefile +++ b/regress/Makefile @@ -108,9 +110,11 @@ CLEANFILES= *.core actual agent-key.* authorized_keys_${USERNAME} \ known_hosts known_hosts-cert known_hosts.* krl-* ls.copy \ modpipe netcat no_identity_config \ pidfile putty.rsa2 ready regress.log \ - remote_pid revoked-* rsa rsa-agent rsa-agent.pub rsa.pub \ + remote_pid revoked-* rsa rsa-agent rsa-agent.pub \ + rsa-agent-cert.pub rsa.pub \ rsa1 rsa1-agent rsa1-agent.pub rsa1.pub rsa_ssh2_cr.prv \ - rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \ + soft-pkcs11.so soft-pkcs11.o pkcs11*.crt pkcs11*.key \ + pkcs11.info rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \ scp-ssh-wrapper.scp setuid-allowed sftp-server.log \ sftp-server.sh sftp.log ssh-log-wrapper.sh ssh.log \ ssh_config ssh_config.* ssh_proxy ssh_proxy_bak \ @@ -225,6 +229,7 @@ unit: V="" ; \ test "x${USE_VALGRIND}" = "x" || \ V=${.CURDIR}/valgrind-unit.sh ; \ + $$V ${.OBJDIR}/unittests/pkcs11/test_pkcs11 ; \ $$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf ; \ $$V ${.OBJDIR}/unittests/sshkey/test_sshkey \ -d ${.CURDIR}/unittests/sshkey/testdata ; \ diff --git a/regress/agent-pkcs11.sh b/regress/agent-pkcs11.sh index db3018b8..4991d0bc 100644 --- a/regress/agent-pkcs11.sh +++ b/regress/agent-pkcs11.sh @@ -4,10 +4,17 @@ tid="pkcs11 agent test" TEST_SSH_PIN="" -TEST_SSH_PKCS11=/usr/local/lib/soft-pkcs11.so.0.0 +TEST_SSH_PKCS11=$OBJ/soft-pkcs11.so test -f "$TEST_SSH_PKCS11" || fatal "$TEST_SSH_PKCS11 does not exist" +# requires ssh-agent built with correct path to ssh-pkcs11-helper +# otherwise it fails to start the helper +strings ${TEST_SSH_SSHAGENT} | grep "$TEST_SSH_SSHPKCS11HELPER" +if [ $? -ne 0 ]; then + fatal "Needs to reconfigure with --libexecdir=\`pwd\` or so" +fi + # setup environment for soft-pkcs11 token SOFTPKCS11RC=$OBJ/pkcs11.info export SOFTPKCS11RC @@ -23,7 +30,7 @@ notty() { } trace "start agent" -eval `${SSHAGENT} -s` > /dev/null +eval `${SSHAGENT} -s -P "${OBJ}/*"` > /dev/null r=$? if [ $r -ne 0 ]; then fail "could not start ssh-agent: exit code $r" @@ -60,7 +67,7 @@ else fi trace "remove pkcs11 keys" - echo ${TEST_SSH_PIN} | notty ${SSHADD} -e ${TEST_SSH_PKCS11} > /dev/null 2>&1 + ${SSHADD} -e ${TEST_SSH_PKCS11} > /dev/null 2>&1 r=$? if [ $r -ne 0 ]; then fail "ssh-add -e failed: exit code $r" diff --git a/regress/locl.h b/regress/locl.h new file mode 100644 index 00000000..afefe27d --- /dev/null +++ b/regress/locl.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2004, Stockholms universitet + * (Stockholm University, Stockholm Sweden) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the university nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* $Id: locl.h,v 1.5 2005/08/28 15:30:31 lha Exp $ */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include "../libcrypto-compat.h" + +#include +#include +#include +#include +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "../pkcs11.h" + +#define OPENSSL_ASN1_MALLOC_ENCODE(T, B, BL, S, R) \ +{ \ + unsigned char *p; \ + (BL) = i2d_##T((S), NULL); \ + if ((BL) <= 0) { \ + (R) = EINVAL; \ + } else { \ + (B) = malloc((BL)); \ + if ((B) == NULL) { \ + (R) = ENOMEM; \ + } else { \ + p = (B); \ + (R) = 0; \ + (BL) = i2d_##T((S), &p); \ + if ((BL) <= 0) { \ + free((B)); \ + (R) = EINVAL; \ + } \ + } \ + } \ +} diff --git a/regress/pkcs11.sh b/regress/pkcs11.sh new file mode 100644 index 00000000..cf98e379 --- /dev/null +++ b/regress/pkcs11.sh @@ -0,0 +1,300 @@ +# +# Copyright (c) 2017 Red Hat +# +# Authors: Jakub Jelen +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +tid="pkcs11 tests with soft token" + +TEST_SSH_PIN="" +TEST_SSH_PKCS11=$OBJ/soft-pkcs11.so + +test -f "$TEST_SSH_PKCS11" || fatal "$TEST_SSH_PKCS11 does not exist" + +# requires ssh-agent built with correct path to ssh-pkcs11-helper +# otherwise it fails to start the helper +strings ${TEST_SSH_SSHAGENT} | grep "$TEST_SSH_SSHPKCS11HELPER" +if [ $? -ne 0 ]; then + fatal "Needs to reconfigure with --libexecdir=\`pwd\` or so" +fi + +# setup environment for soft-pkcs11 token +SOFTPKCS11RC=$OBJ/pkcs11.info +rm -f $SOFTPKCS11RC +export SOFTPKCS11RC +# prevent ssh-agent from calling ssh-askpass +SSH_ASKPASS=/usr/bin/true +export SSH_ASKPASS +unset DISPLAY + +# start command w/o tty, so ssh accepts pin from stdin (from agent-pkcs11.sh) +notty() { + perl -e 'use POSIX; POSIX::setsid(); + if (fork) { wait; exit($? >> 8); } else { exec(@ARGV) }' "$@" +} + +create_key() { + ID=$1 + LABEL=$2 + rm -f $OBJ/pkcs11-${ID}.key $OBJ/pkcs11-${ID}.crt + openssl genrsa -out $OBJ/pkcs11-${ID}.key 2048 > /dev/null 2>&1 + chmod 600 $OBJ/pkcs11-${ID}.key + openssl req -key $OBJ/pkcs11-${ID}.key -new -x509 \ + -out $OBJ/pkcs11-${ID}.crt -text -subj '/CN=pkcs11 test' >/dev/null + printf "${ID}\t${LABEL}\t$OBJ/pkcs11-${ID}.crt\t$OBJ/pkcs11-${ID}.key\n" \ + >> $SOFTPKCS11RC +} + +trace "Create a key pairs on soft token" +ID1="02" +ID2="04" +create_key "$ID1" "SSH RSA Key" +create_key "$ID2" "SSH RSA Key 2" + +trace "List the keys in the ssh-keygen with PKCS#11 URIs" +${SSHKEYGEN} -D ${TEST_SSH_PKCS11} > $OBJ/token_keys +if [ $? -ne 0 ]; then + fail "keygen fails to enumerate keys on PKCS#11 token" +fi +grep "pkcs11:" $OBJ/token_keys > /dev/null +if [ $? -ne 0 ]; then + fail "The keys from ssh-keygen do not contain PKCS#11 URI as a comment" +fi +tail -n 1 $OBJ/token_keys > $OBJ/authorized_keys_$USER + + +trace "Simple connect with ssh (without PKCS#11 URI)" +echo ${TEST_SSH_PIN} | notty ${SSH} -I ${TEST_SSH_PKCS11} \ + -F $OBJ/ssh_proxy somehost exit 5 +r=$? +if [ $r -ne 5 ]; then + fail "ssh connect with pkcs11 failed (exit code $r)" +fi + + +trace "Connect with PKCS#11 URI" +trace " (second key should succeed)" +echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \ + -i "pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}" somehost exit 5 +r=$? +if [ $r -ne 5 ]; then + fail "ssh connect with PKCS#11 URI failed (exit code $r)" +fi + +trace " (first key should fail)" +echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \ + -i "pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11}" somehost exit 5 +r=$? +if [ $r -eq 5 ]; then + fail "ssh connect with PKCS#11 URI succeeded (should fail)" +fi + +trace "Connect with various filtering options in PKCS#11 URI" +trace " (by object label, second key should succeed)" +echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \ + -i "pkcs11:object=SSH%20RSA%20Key%202?module-path=${TEST_SSH_PKCS11}" somehost exit 5 +r=$? +if [ $r -ne 5 ]; then + fail "ssh connect with PKCS#11 URI failed (exit code $r)" +fi + +trace " (by object label, first key should fail)" +echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \ + -i "pkcs11:object=SSH%20RSA%20Key?module-path=${TEST_SSH_PKCS11}" somehost exit 5 +r=$? +if [ $r -eq 5 ]; then + fail "ssh connect with PKCS#11 URI succeeded (should fail)" +fi + +trace " (by token label, second key should succeed)" +echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \ + -i "pkcs11:id=${ID2};token=SoftToken%20(token)?module-path=${TEST_SSH_PKCS11}" somehost exit 5 +r=$? +if [ $r -ne 5 ]; then + fail "ssh connect with PKCS#11 URI failed (exit code $r)" +fi + +trace " (by wrong token label, should fail)" +echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \ + -i "pkcs11:token=SoftToken?module-path=${TEST_SSH_PKCS11}" somehost exit 5 +r=$? +if [ $r -eq 5 ]; then + fail "ssh connect with PKCS#11 URI succeeded (should fail)" +fi + + + + +trace "Test PKCS#11 URI specification in configuration files" +echo "IdentityFile \"pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}\"" \ + >> $OBJ/ssh_proxy +trace " (second key should succeed)" +echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5 +r=$? +if [ $r -ne 5 ]; then + fail "ssh connect with PKCS#11 URI in config failed (exit code $r)" +fi + +trace " (first key should fail)" +head -n 1 $OBJ/token_keys > $OBJ/authorized_keys_$USER +echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5 +r=$? +if [ $r -eq 5 ]; then + fail "ssh connect with PKCS#11 URI in config succeeded (should fail)" +fi +sed -i -e "/IdentityFile/d" $OBJ/ssh_proxy + +trace "Test PKCS#11 URI specification in configuration files with bogus spaces" +echo "IdentityFile \" pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11} \"" \ + >> $OBJ/ssh_proxy +echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5 +r=$? +if [ $r -ne 5 ]; then + fail "ssh connect with PKCS#11 URI with bogus spaces in config failed" \ + "(exit code $r)" +fi +sed -i -e "/IdentityFile/d" $OBJ/ssh_proxy + + +trace "Combination of PKCS11Provider and PKCS11URI on commandline" +trace " (first key should succeed)" +echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \ + -i "pkcs11:id=${ID1}" -I ${TEST_SSH_PKCS11} somehost exit 5 +r=$? +if [ $r -ne 5 ]; then + fail "ssh connect with PKCS#11 URI and provider combination" \ + "failed (exit code $r)" +fi + +trace "Regress: Missing provider in PKCS11URI option" +${SSH} -F $OBJ/ssh_proxy \ + -o IdentityFile=\"pkcs11:token=segfault\" somehost exit 5 +r=$? +if [ $r -eq 139 ]; then + fail "ssh connect with missing provider_id from configuration option" \ + "crashed (exit code $r)" +fi + + +trace "SSH Agent can work with PKCS#11 URI" +trace "start the agent" +eval `${SSHAGENT} -s -P "${OBJ}/*"` > /dev/null + +r=$? +if [ $r -ne 0 ]; then + fail "could not start ssh-agent: exit code $r" +else + trace "add whole provider to agent" + echo ${TEST_SSH_PIN} | notty ${SSHADD} \ + "pkcs11:?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1 + r=$? + if [ $r -ne 0 ]; then + fail "ssh-add failed with whole provider: exit code $r" + fi + + trace " pkcs11 list via agent (all keys)" + ${SSHADD} -l > /dev/null 2>&1 + r=$? + if [ $r -ne 0 ]; then + fail "ssh-add -l failed with whole provider: exit code $r" + fi + + trace " pkcs11 connect via agent (all keys)" + ${SSH} -F $OBJ/ssh_proxy somehost exit 5 + r=$? + if [ $r -ne 5 ]; then + fail "ssh connect failed with whole provider (exit code $r)" + fi + + trace " remove pkcs11 keys (all keys)" + ${SSHADD} -d "pkcs11:?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1 + r=$? + if [ $r -ne 0 ]; then + fail "ssh-add -d failed with whole provider: exit code $r" + fi + + trace "add only first key to the agent" + echo ${TEST_SSH_PIN} | notty ${SSHADD} \ + "pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1 + r=$? + if [ $r -ne 0 ]; then + fail "ssh-add failed with first key: exit code $r" + fi + + trace " pkcs11 connect via agent (first key)" + ${SSH} -F $OBJ/ssh_proxy somehost exit 5 + r=$? + if [ $r -ne 5 ]; then + fail "ssh connect failed with first key (exit code $r)" + fi + + trace " remove first pkcs11 key" + ${SSHADD} -d "pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11}" \ + > /dev/null 2>&1 + r=$? + if [ $r -ne 0 ]; then + fail "ssh-add -d failed with first key: exit code $r" + fi + + trace "add only second key to the agent" + echo ${TEST_SSH_PIN} | notty ${SSHADD} \ + "pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1 + r=$? + if [ $r -ne 0 ]; then + fail "ssh-add failed with second key: exit code $r" + fi + + trace " pkcs11 connect via agent (second key should fail)" + ${SSH} -F $OBJ/ssh_proxy somehost exit 5 + r=$? + if [ $r -eq 5 ]; then + fail "ssh connect passed without key (should fail)" + fi + + trace "add also the first key to the agent" + echo ${TEST_SSH_PIN} | notty ${SSHADD} \ + "pkcs11:id=${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1 + r=$? + if [ $r -ne 0 ]; then + fail "ssh-add failed with first key: exit code $r" + fi + + trace " remove second pkcs11 key" + ${SSHADD} -d "pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}" \ + > /dev/null 2>&1 + r=$? + if [ $r -ne 0 ]; then + fail "ssh-add -d failed with second key: exit code $r" + fi + + trace " remove already-removed pkcs11 key should fail" + ${SSHADD} -d "pkcs11:id=${ID2}?module-path=${TEST_SSH_PKCS11}" \ + > /dev/null 2>&1 + r=$? + if [ $r -eq 0 ]; then + fail "ssh-add -d passed with non-existing key (should fail)" + fi + + trace " pkcs11 connect via agent (the first key should be still usable)" + ${SSH} -F $OBJ/ssh_proxy somehost exit 5 + r=$? + if [ $r -ne 5 ]; then + fail "ssh connect failed with first key (after removing second): exit code $r" + fi + + trace "kill agent" + ${SSHAGENT} -k > /dev/null +fi + +rm -rf $OBJ/.tokens $OBJ/token_keys diff --git a/regress/soft-pkcs11.c b/regress/soft-pkcs11.c new file mode 100644 index 00000000..8b4981bd --- /dev/null +++ b/regress/soft-pkcs11.c @@ -0,0 +1,2058 @@ +/* + * Copyright (c) 2004-2006, Stockholms universitet + * (Stockholm University, Stockholm Sweden) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the university nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "locl.h" + +/* RCSID("$Id: main.c,v 1.24 2006/01/11 12:42:53 lha Exp $"); */ + +#define OBJECT_ID_MASK 0xfff +#define HANDLE_OBJECT_ID(h) ((h) & OBJECT_ID_MASK) +#define OBJECT_ID(obj) HANDLE_OBJECT_ID((obj)->object_handle) + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + #define RSA_PKCS1_SSLeay RSA_PKCS1_OpenSSL +#endif + +struct st_attr { + CK_ATTRIBUTE attribute; + int secret; +}; + +struct st_object { + CK_OBJECT_HANDLE object_handle; + struct st_attr *attrs; + int num_attributes; + enum { + STO_T_CERTIFICATE, + STO_T_PRIVATE_KEY, + STO_T_PUBLIC_KEY + } type; + union { + X509 *cert; + EVP_PKEY *public_key; + struct { + const char *file; + EVP_PKEY *key; + X509 *cert; + } private_key; + } u; +}; + +static struct soft_token { + CK_VOID_PTR application; + CK_NOTIFY notify; + struct { + struct st_object **objs; + int num_objs; + } object; + struct { + int hardware_slot; + int app_error_fatal; + int login_done; + } flags; + int open_sessions; + struct session_state { + CK_SESSION_HANDLE session_handle; + + struct { + CK_ATTRIBUTE *attributes; + CK_ULONG num_attributes; + int next_object; + } find; + + int encrypt_object; + CK_MECHANISM_PTR encrypt_mechanism; + int decrypt_object; + CK_MECHANISM_PTR decrypt_mechanism; + int sign_object; + CK_MECHANISM_PTR sign_mechanism; + int verify_object; + CK_MECHANISM_PTR verify_mechanism; + int digest_object; + } state[10]; +#define MAX_NUM_SESSION (sizeof(soft_token.state)/sizeof(soft_token.state[0])) + FILE *logfile; +} soft_token; + +static void +application_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + if (soft_token.flags.app_error_fatal) + abort(); +} + +static void +st_logf(const char *fmt, ...) +{ + va_list ap; + if (soft_token.logfile == NULL) + return; + va_start(ap, fmt); + vfprintf(soft_token.logfile, fmt, ap); + va_end(ap); + fflush(soft_token.logfile); +} + +static void +snprintf_fill(char *str, size_t size, char fillchar, const char *fmt, ...) +{ + int len; + va_list ap; + len = vsnprintf(str, size, fmt, ap); + va_end(ap); + if (len < 0 || len > (int) size) + return; + while(len < (int) size) + str[len++] = fillchar; +} + +#ifndef TEST_APP +#define printf error_use_st_logf +#endif + +#define VERIFY_SESSION_HANDLE(s, state) \ +{ \ + CK_RV ret; \ + ret = verify_session_handle(s, state); \ + if (ret != CKR_OK) { \ + /* return CKR_OK */; \ + } \ +} + +static CK_RV +verify_session_handle(CK_SESSION_HANDLE hSession, + struct session_state **state) +{ + size_t i; + + for (i = 0; i < MAX_NUM_SESSION; i++){ + if (soft_token.state[i].session_handle == hSession) + break; + } + if (i == MAX_NUM_SESSION) { + application_error("use of invalid handle: 0x%08lx\n", + (unsigned long)hSession); + return CKR_SESSION_HANDLE_INVALID; + } + if (state) + *state = &soft_token.state[i]; + return CKR_OK; +} + +static CK_RV +object_handle_to_object(CK_OBJECT_HANDLE handle, + struct st_object **object) +{ + int i = HANDLE_OBJECT_ID(handle); + + *object = NULL; + if (i >= soft_token.object.num_objs) + return CKR_ARGUMENTS_BAD; + if (soft_token.object.objs[i] == NULL) + return CKR_ARGUMENTS_BAD; + if (soft_token.object.objs[i]->object_handle != handle) + return CKR_ARGUMENTS_BAD; + *object = soft_token.object.objs[i]; + return CKR_OK; +} + +static int +attributes_match(const struct st_object *obj, + const CK_ATTRIBUTE *attributes, + CK_ULONG num_attributes) +{ + CK_ULONG i; + int j; + st_logf("attributes_match: %ld\n", (unsigned long)OBJECT_ID(obj)); + + for (i = 0; i < num_attributes; i++) { + int match = 0; + for (j = 0; j < obj->num_attributes; j++) { + if (attributes[i].type == obj->attrs[j].attribute.type && + attributes[i].ulValueLen == obj->attrs[j].attribute.ulValueLen && + memcmp(attributes[i].pValue, obj->attrs[j].attribute.pValue, + attributes[i].ulValueLen) == 0) { + match = 1; + break; + } + } + if (match == 0) { + st_logf("type %d attribute have no match\n", attributes[i].type); + return 0; + } + } + st_logf("attribute matches\n"); + return 1; +} + +static void +print_attributes(const CK_ATTRIBUTE *attributes, + CK_ULONG num_attributes) +{ + CK_ULONG i; + + st_logf("find objects: attrs: %lu\n", (unsigned long)num_attributes); + + for (i = 0; i < num_attributes; i++) { + st_logf(" type: "); + switch (attributes[i].type) { + case CKA_TOKEN: { + CK_BBOOL *ck_true; + if (attributes[i].ulValueLen != sizeof(CK_BBOOL)) { + application_error("token attribute wrong length\n"); + break; + } + ck_true = attributes[i].pValue; + st_logf("token: %s", *ck_true ? "TRUE" : "FALSE"); + break; + } + case CKA_CLASS: { + CK_OBJECT_CLASS *class; + if (attributes[i].ulValueLen != sizeof(CK_ULONG)) { + application_error("class attribute wrong length\n"); + break; + } + class = attributes[i].pValue; + st_logf("class "); + switch (*class) { + case CKO_CERTIFICATE: + st_logf("certificate"); + break; + case CKO_PUBLIC_KEY: + st_logf("public key"); + break; + case CKO_PRIVATE_KEY: + st_logf("private key"); + break; + case CKO_SECRET_KEY: + st_logf("secret key"); + break; + case CKO_DOMAIN_PARAMETERS: + st_logf("domain parameters"); + break; + default: + st_logf("[class %lx]", (long unsigned)*class); + break; + } + break; + } + case CKA_PRIVATE: + st_logf("private"); + break; + case CKA_LABEL: + st_logf("label"); + break; + case CKA_APPLICATION: + st_logf("application"); + break; + case CKA_VALUE: + st_logf("value"); + break; + case CKA_ID: + st_logf("id"); + break; + default: + st_logf("[unknown 0x%08lx]", (unsigned long)attributes[i].type); + break; + } + st_logf("\n"); + } +} + +static struct st_object * +add_st_object(void) +{ + struct st_object *o, **objs; + int i; + + o = malloc(sizeof(*o)); + if (o == NULL) + return NULL; + memset(o, 0, sizeof(*o)); + o->attrs = NULL; + o->num_attributes = 0; + + for (i = 0; i < soft_token.object.num_objs; i++) { + if (soft_token.object.objs == NULL) { + soft_token.object.objs[i] = o; + break; + } + } + if (i == soft_token.object.num_objs) { + objs = realloc(soft_token.object.objs, + (soft_token.object.num_objs + 1) * sizeof(soft_token.object.objs[0])); + if (objs == NULL) { + free(o); + return NULL; + } + soft_token.object.objs = objs; + soft_token.object.objs[soft_token.object.num_objs++] = o; + } + soft_token.object.objs[i]->object_handle = + (random() & (~OBJECT_ID_MASK)) | i; + + return o; +} + +static CK_RV +add_object_attribute(struct st_object *o, + int secret, + CK_ATTRIBUTE_TYPE type, + CK_VOID_PTR pValue, + CK_ULONG ulValueLen) +{ + struct st_attr *a; + int i; + + i = o->num_attributes; + a = realloc(o->attrs, (i + 1) * sizeof(o->attrs[0])); + if (a == NULL) + return CKR_DEVICE_MEMORY; + o->attrs = a; + o->attrs[i].secret = secret; + o->attrs[i].attribute.type = type; + o->attrs[i].attribute.pValue = malloc(ulValueLen); + if (o->attrs[i].attribute.pValue == NULL && ulValueLen != 0) + return CKR_DEVICE_MEMORY; + memcpy(o->attrs[i].attribute.pValue, pValue, ulValueLen); + o->attrs[i].attribute.ulValueLen = ulValueLen; + o->num_attributes++; + + return CKR_OK; +} + +static CK_RV +add_pubkey_info(struct st_object *o, CK_KEY_TYPE key_type, EVP_PKEY *key) +{ + switch (key_type) { + case CKK_RSA: { + CK_BYTE *modulus = NULL; + size_t modulus_len = 0; + CK_ULONG modulus_bits = 0; + CK_BYTE *exponent = NULL; + size_t exponent_len = 0; + RSA* rsa = NULL; + const BIGNUM *n = NULL, *e = NULL; + + rsa = EVP_PKEY_get0_RSA(key); + RSA_get0_key(rsa, &n, &e, NULL); + + modulus_bits = BN_num_bits(n); + + modulus_len = BN_num_bytes(n); + modulus = malloc(modulus_len); + BN_bn2bin(n, modulus); + + exponent_len = BN_num_bytes(e); + exponent = malloc(exponent_len); + BN_bn2bin(e, exponent); + + add_object_attribute(o, 0, CKA_MODULUS, modulus, modulus_len); + add_object_attribute(o, 0, CKA_MODULUS_BITS, + &modulus_bits, sizeof(modulus_bits)); + add_object_attribute(o, 0, CKA_PUBLIC_EXPONENT, + exponent, exponent_len); + + RSA_set_method(rsa, RSA_PKCS1_OpenSSL()); + + free(modulus); + free(exponent); + } + default: + /* XXX */ + break; + } + return CKR_OK; +} + + +static int +pem_callback(char *buf, int num, int w, void *key) +{ + return -1; +} + + +static CK_RV +add_certificate(char *label, + const char *cert_file, + const char *private_key_file, + char *id, + int anchor) +{ + struct st_object *o = NULL; + CK_BBOOL bool_true = CK_TRUE; + CK_BBOOL bool_false = CK_FALSE; + CK_OBJECT_CLASS c; + CK_CERTIFICATE_TYPE cert_type = CKC_X_509; + CK_KEY_TYPE key_type; + CK_MECHANISM_TYPE mech_type; + void *cert_data = NULL; + size_t cert_length; + void *subject_data = NULL; + size_t subject_length; + void *issuer_data = NULL; + size_t issuer_length; + void *serial_data = NULL; + size_t serial_length; + CK_RV ret = CKR_GENERAL_ERROR; + X509 *cert; + EVP_PKEY *public_key; + + size_t id_len = strlen(id); + + { + FILE *f; + + f = fopen(cert_file, "r"); + if (f == NULL) { + st_logf("failed to open file %s\n", cert_file); + return CKR_GENERAL_ERROR; + } + + cert = PEM_read_X509(f, NULL, NULL, NULL); + fclose(f); + if (cert == NULL) { + st_logf("failed reading PEM cert\n"); + return CKR_GENERAL_ERROR; + } + + OPENSSL_ASN1_MALLOC_ENCODE(X509, cert_data, cert_length, cert, ret); + if (ret) + goto out; + + OPENSSL_ASN1_MALLOC_ENCODE(X509_NAME, issuer_data, issuer_length, + X509_get_issuer_name(cert), ret); + if (ret) + goto out; + + OPENSSL_ASN1_MALLOC_ENCODE(X509_NAME, subject_data, subject_length, + X509_get_subject_name(cert), ret); + if (ret) + goto out; + + OPENSSL_ASN1_MALLOC_ENCODE(ASN1_INTEGER, serial_data, serial_length, + X509_get_serialNumber(cert), ret); + if (ret) + goto out; + + } + + st_logf("done parsing, adding to internal structure\n"); + + o = add_st_object(); + if (o == NULL) { + ret = CKR_DEVICE_MEMORY; + goto out; + } + o->type = STO_T_CERTIFICATE; + o->u.cert = cert; + public_key = X509_get_pubkey(o->u.cert); + + switch (EVP_PKEY_base_id(public_key)) { + case EVP_PKEY_RSA: + key_type = CKK_RSA; + break; + case EVP_PKEY_DSA: + key_type = CKK_DSA; + break; + default: + /* XXX */ + break; + } + + c = CKO_CERTIFICATE; + add_object_attribute(o, 0, CKA_CLASS, &c, sizeof(c)); + add_object_attribute(o, 0, CKA_TOKEN, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_PRIVATE, &bool_false, sizeof(bool_false)); + add_object_attribute(o, 0, CKA_MODIFIABLE, &bool_false, sizeof(bool_false)); + add_object_attribute(o, 0, CKA_LABEL, label, strlen(label)); + + add_object_attribute(o, 0, CKA_CERTIFICATE_TYPE, &cert_type, sizeof(cert_type)); + add_object_attribute(o, 0, CKA_ID, id, id_len); + + add_object_attribute(o, 0, CKA_SUBJECT, subject_data, subject_length); + add_object_attribute(o, 0, CKA_ISSUER, issuer_data, issuer_length); + add_object_attribute(o, 0, CKA_SERIAL_NUMBER, serial_data, serial_length); + add_object_attribute(o, 0, CKA_VALUE, cert_data, cert_length); + if (anchor) + add_object_attribute(o, 0, CKA_TRUSTED, &bool_true, sizeof(bool_true)); + else + add_object_attribute(o, 0, CKA_TRUSTED, &bool_false, sizeof(bool_false)); + + st_logf("add cert ok: %lx\n", (unsigned long)OBJECT_ID(o)); + + o = add_st_object(); + if (o == NULL) { + ret = CKR_DEVICE_MEMORY; + goto out; + } + o->type = STO_T_PUBLIC_KEY; + o->u.public_key = public_key; + + c = CKO_PUBLIC_KEY; + add_object_attribute(o, 0, CKA_CLASS, &c, sizeof(c)); + add_object_attribute(o, 0, CKA_TOKEN, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_PRIVATE, &bool_false, sizeof(bool_false)); + add_object_attribute(o, 0, CKA_MODIFIABLE, &bool_false, sizeof(bool_false)); + add_object_attribute(o, 0, CKA_LABEL, label, strlen(label)); + + add_object_attribute(o, 0, CKA_KEY_TYPE, &key_type, sizeof(key_type)); + add_object_attribute(o, 0, CKA_ID, id, id_len); + add_object_attribute(o, 0, CKA_START_DATE, "", 1); /* XXX */ + add_object_attribute(o, 0, CKA_END_DATE, "", 1); /* XXX */ + add_object_attribute(o, 0, CKA_DERIVE, &bool_false, sizeof(bool_false)); + add_object_attribute(o, 0, CKA_LOCAL, &bool_false, sizeof(bool_false)); + mech_type = CKM_RSA_X_509; + add_object_attribute(o, 0, CKA_KEY_GEN_MECHANISM, &mech_type, sizeof(mech_type)); + + add_object_attribute(o, 0, CKA_SUBJECT, subject_data, subject_length); + add_object_attribute(o, 0, CKA_ENCRYPT, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_VERIFY, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_VERIFY_RECOVER, &bool_false, sizeof(bool_false)); + add_object_attribute(o, 0, CKA_WRAP, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_TRUSTED, &bool_true, sizeof(bool_true)); + + add_pubkey_info(o, key_type, public_key); + + st_logf("add key ok: %lx\n", (unsigned long)OBJECT_ID(o)); + + if (private_key_file) { + CK_FLAGS flags; + FILE *f; + + o = add_st_object(); + if (o == NULL) { + ret = CKR_DEVICE_MEMORY; + goto out; + } + o->type = STO_T_PRIVATE_KEY; + o->u.private_key.file = strdup(private_key_file); + o->u.private_key.key = NULL; + + o->u.private_key.cert = cert; + + c = CKO_PRIVATE_KEY; + add_object_attribute(o, 0, CKA_CLASS, &c, sizeof(c)); + add_object_attribute(o, 0, CKA_TOKEN, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_PRIVATE, &bool_true, sizeof(bool_false)); + add_object_attribute(o, 0, CKA_MODIFIABLE, &bool_false, sizeof(bool_false)); + add_object_attribute(o, 0, CKA_LABEL, label, strlen(label)); + + add_object_attribute(o, 0, CKA_KEY_TYPE, &key_type, sizeof(key_type)); + add_object_attribute(o, 0, CKA_ID, id, id_len); + add_object_attribute(o, 0, CKA_START_DATE, "", 1); /* XXX */ + add_object_attribute(o, 0, CKA_END_DATE, "", 1); /* XXX */ + add_object_attribute(o, 0, CKA_DERIVE, &bool_false, sizeof(bool_false)); + add_object_attribute(o, 0, CKA_LOCAL, &bool_false, sizeof(bool_false)); + mech_type = CKM_RSA_X_509; + add_object_attribute(o, 0, CKA_KEY_GEN_MECHANISM, &mech_type, sizeof(mech_type)); + + add_object_attribute(o, 0, CKA_SUBJECT, subject_data, subject_length); + add_object_attribute(o, 0, CKA_SENSITIVE, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_SECONDARY_AUTH, &bool_false, sizeof(bool_true)); + flags = 0; + add_object_attribute(o, 0, CKA_AUTH_PIN_FLAGS, &flags, sizeof(flags)); + + add_object_attribute(o, 0, CKA_DECRYPT, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_SIGN, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_SIGN_RECOVER, &bool_false, sizeof(bool_false)); + add_object_attribute(o, 0, CKA_UNWRAP, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_EXTRACTABLE, &bool_true, sizeof(bool_true)); + add_object_attribute(o, 0, CKA_NEVER_EXTRACTABLE, &bool_false, sizeof(bool_false)); + + add_pubkey_info(o, key_type, public_key); + + f = fopen(private_key_file, "r"); + if (f == NULL) { + st_logf("failed to open private key\n"); + return CKR_GENERAL_ERROR; + } + + o->u.private_key.key = PEM_read_PrivateKey(f, NULL, pem_callback, NULL); + fclose(f); + if (o->u.private_key.key == NULL) { + st_logf("failed to read private key a startup\n"); + /* don't bother with this failure for now, + fix it at C_Login time */; + } else { + /* XXX verify keytype */ + + if (key_type == CKK_RSA) { + RSA *rsa = EVP_PKEY_get0_RSA(o->u.private_key.key); + RSA_set_method(rsa, RSA_PKCS1_OpenSSL()); + } + + if (X509_check_private_key(cert, o->u.private_key.key) != 1) { + EVP_PKEY_free(o->u.private_key.key); + o->u.private_key.key = NULL; + st_logf("private key doesn't verify\n"); + } else { + st_logf("private key usable\n"); + soft_token.flags.login_done = 1; + } + } + } + + ret = CKR_OK; + out: + if (ret != CKR_OK) { + st_logf("something went wrong when adding cert!\n"); + + /* XXX wack o */; + } + free(cert_data); + free(serial_data); + free(issuer_data); + free(subject_data); + + return ret; +} + +static void +find_object_final(struct session_state *state) +{ + if (state->find.attributes) { + CK_ULONG i; + + for (i = 0; i < state->find.num_attributes; i++) { + if (state->find.attributes[i].pValue) + free(state->find.attributes[i].pValue); + } + free(state->find.attributes); + state->find.attributes = NULL; + state->find.num_attributes = 0; + state->find.next_object = -1; + } +} + +static void +reset_crypto_state(struct session_state *state) +{ + state->encrypt_object = -1; + if (state->encrypt_mechanism) + free(state->encrypt_mechanism); + state->encrypt_mechanism = NULL_PTR; + state->decrypt_object = -1; + if (state->decrypt_mechanism) + free(state->decrypt_mechanism); + state->decrypt_mechanism = NULL_PTR; + state->sign_object = -1; + if (state->sign_mechanism) + free(state->sign_mechanism); + state->sign_mechanism = NULL_PTR; + state->verify_object = -1; + if (state->verify_mechanism) + free(state->verify_mechanism); + state->verify_mechanism = NULL_PTR; + state->digest_object = -1; +} + +static void +close_session(struct session_state *state) +{ + if (state->find.attributes) { + application_error("application didn't do C_FindObjectsFinal\n"); + find_object_final(state); + } + + state->session_handle = CK_INVALID_HANDLE; + soft_token.application = NULL_PTR; + soft_token.notify = NULL_PTR; + reset_crypto_state(state); +} + +static const char * +has_session(void) +{ + return soft_token.open_sessions > 0 ? "yes" : "no"; +} + +static void +read_conf_file(const char *fn) +{ + char buf[1024], *cert, *key, *id, *label, *s, *p; + int anchor; + FILE *f; + + f = fopen(fn, "r"); + if (f == NULL) { + st_logf("can't open configuration file %s\n", fn); + return; + } + + while(fgets(buf, sizeof(buf), f) != NULL) { + buf[strcspn(buf, "\n")] = '\0'; + + anchor = 0; + + st_logf("line: %s\n", buf); + + p = buf; + while (isspace(*p)) + p++; + if (*p == '#') + continue; + while (isspace(*p)) + p++; + + s = NULL; + id = strtok_r(p, "\t", &s); + if (id == NULL) + continue; + label = strtok_r(NULL, "\t", &s); + if (label == NULL) + continue; + cert = strtok_r(NULL, "\t", &s); + if (cert == NULL) + continue; + key = strtok_r(NULL, "\t", &s); + + /* XXX */ + if (strcmp(id, "anchor") == 0) { + id = "\x00\x00"; + anchor = 1; + } + + st_logf("adding: %s\n", label); + + add_certificate(label, cert, key, id, anchor); + } +} + +static CK_RV +func_not_supported(void) +{ + st_logf("function not supported\n"); + return CKR_FUNCTION_NOT_SUPPORTED; +} + +CK_RV +C_Initialize(CK_VOID_PTR a) +{ + CK_C_INITIALIZE_ARGS_PTR args = a; + st_logf("Initialize\n"); + size_t i; + + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + + srandom(getpid() ^ time(NULL)); + + for (i = 0; i < MAX_NUM_SESSION; i++) { + soft_token.state[i].session_handle = CK_INVALID_HANDLE; + soft_token.state[i].find.attributes = NULL; + soft_token.state[i].find.num_attributes = 0; + soft_token.state[i].find.next_object = -1; + reset_crypto_state(&soft_token.state[i]); + } + + soft_token.flags.hardware_slot = 1; + soft_token.flags.app_error_fatal = 0; + soft_token.flags.login_done = 0; + + soft_token.object.objs = NULL; + soft_token.object.num_objs = 0; + + soft_token.logfile = NULL; +#if 1 +// soft_token.logfile = stdout; +#endif +#if 0 + soft_token.logfile = fopen("/tmp/log-pkcs11.txt", "a"); +#endif + + if (a != NULL_PTR) { + st_logf("\tCreateMutex:\t%p\n", args->CreateMutex); + st_logf("\tDestroyMutext\t%p\n", args->DestroyMutex); + st_logf("\tLockMutext\t%p\n", args->LockMutex); + st_logf("\tUnlockMutext\t%p\n", args->UnlockMutex); + st_logf("\tFlags\t%04x\n", (unsigned int)args->flags); + } + + { + char *fn = NULL, *home = NULL; + + if (getuid() == geteuid()) { + fn = getenv("SOFTPKCS11RC"); + if (fn) + fn = strdup(fn); + home = getenv("HOME"); + } + if (fn == NULL && home == NULL) { + struct passwd *pw = getpwuid(getuid()); + if(pw != NULL) + home = pw->pw_dir; + } + if (fn == NULL) { + if (home) + asprintf(&fn, "%s/.soft-token.rc", home); + else + fn = strdup("/etc/soft-token.rc"); + } + + read_conf_file(fn); + free(fn); + } + + return CKR_OK; +} + +CK_RV +C_Finalize(CK_VOID_PTR args) +{ + size_t i; + + st_logf("Finalize\n"); + + for (i = 0; i < MAX_NUM_SESSION; i++) { + if (soft_token.state[i].session_handle != CK_INVALID_HANDLE) { + application_error("application finalized without " + "closing session\n"); + close_session(&soft_token.state[i]); + } + } + + return CKR_OK; +} + +CK_RV +C_GetInfo(CK_INFO_PTR args) +{ + st_logf("GetInfo\n"); + + memset(args, 17, sizeof(*args)); + args->cryptokiVersion.major = 2; + args->cryptokiVersion.minor = 10; + snprintf_fill((char *)args->manufacturerID, + sizeof(args->manufacturerID), + ' ', + "SoftToken"); + snprintf_fill((char *)args->libraryDescription, + sizeof(args->libraryDescription), ' ', + "SoftToken"); + args->libraryVersion.major = 1; + args->libraryVersion.minor = 8; + + return CKR_OK; +} + +extern CK_FUNCTION_LIST funcs; + +CK_RV +C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList) +{ + *ppFunctionList = &funcs; + return CKR_OK; +} + +CK_RV +C_GetSlotList(CK_BBOOL tokenPresent, + CK_SLOT_ID_PTR pSlotList, + CK_ULONG_PTR pulCount) +{ + st_logf("GetSlotList: %s\n", + tokenPresent ? "tokenPresent" : "token not Present"); + if (pSlotList) + pSlotList[0] = 1; + *pulCount = 1; + return CKR_OK; +} + +CK_RV +C_GetSlotInfo(CK_SLOT_ID slotID, + CK_SLOT_INFO_PTR pInfo) +{ + st_logf("GetSlotInfo: slot: %d : %s\n", (int)slotID, has_session()); + + memset(pInfo, 18, sizeof(*pInfo)); + + if (slotID != 1) + return CKR_ARGUMENTS_BAD; + + snprintf_fill((char *)pInfo->slotDescription, + sizeof(pInfo->slotDescription), + ' ', + "SoftToken (slot)"); + snprintf_fill((char *)pInfo->manufacturerID, + sizeof(pInfo->manufacturerID), + ' ', + "SoftToken (slot)"); + pInfo->flags = CKF_TOKEN_PRESENT; + if (soft_token.flags.hardware_slot) + pInfo->flags |= CKF_HW_SLOT; + pInfo->hardwareVersion.major = 1; + pInfo->hardwareVersion.minor = 0; + pInfo->firmwareVersion.major = 1; + pInfo->firmwareVersion.minor = 0; + + return CKR_OK; +} + +CK_RV +C_GetTokenInfo(CK_SLOT_ID slotID, + CK_TOKEN_INFO_PTR pInfo) +{ + st_logf("GetTokenInfo: %s\n", has_session()); + + memset(pInfo, 19, sizeof(*pInfo)); + + snprintf_fill((char *)pInfo->label, + sizeof(pInfo->label), + ' ', + "SoftToken (token)"); + snprintf_fill((char *)pInfo->manufacturerID, + sizeof(pInfo->manufacturerID), + ' ', + "SoftToken (token)"); + snprintf_fill((char *)pInfo->model, + sizeof(pInfo->model), + ' ', + "SoftToken (token)"); + snprintf_fill((char *)pInfo->serialNumber, + sizeof(pInfo->serialNumber), + ' ', + "4711"); + pInfo->flags = + CKF_TOKEN_INITIALIZED | + CKF_USER_PIN_INITIALIZED; + + if (soft_token.flags.login_done == 0) + pInfo->flags |= CKF_LOGIN_REQUIRED; + + /* CFK_RNG | + CKF_RESTORE_KEY_NOT_NEEDED | + */ + pInfo->ulMaxSessionCount = MAX_NUM_SESSION; + pInfo->ulSessionCount = soft_token.open_sessions; + pInfo->ulMaxRwSessionCount = MAX_NUM_SESSION; + pInfo->ulRwSessionCount = soft_token.open_sessions; + pInfo->ulMaxPinLen = 1024; + pInfo->ulMinPinLen = 0; + pInfo->ulTotalPublicMemory = 4711; + pInfo->ulFreePublicMemory = 4712; + pInfo->ulTotalPrivateMemory = 4713; + pInfo->ulFreePrivateMemory = 4714; + pInfo->hardwareVersion.major = 2; + pInfo->hardwareVersion.minor = 0; + pInfo->firmwareVersion.major = 2; + pInfo->firmwareVersion.minor = 0; + + return CKR_OK; +} + +CK_RV +C_GetMechanismList(CK_SLOT_ID slotID, + CK_MECHANISM_TYPE_PTR pMechanismList, + CK_ULONG_PTR pulCount) +{ + st_logf("GetMechanismList\n"); + + *pulCount = 2; + if (pMechanismList == NULL_PTR) + return CKR_OK; + pMechanismList[0] = CKM_RSA_X_509; + pMechanismList[1] = CKM_RSA_PKCS; + + return CKR_OK; +} + +CK_RV +C_GetMechanismInfo(CK_SLOT_ID slotID, + CK_MECHANISM_TYPE type, + CK_MECHANISM_INFO_PTR pInfo) +{ + st_logf("GetMechanismInfo: slot %d type: %d\n", + (int)slotID, (int)type); + return CKR_FUNCTION_NOT_SUPPORTED; +} + +CK_RV +C_InitToken(CK_SLOT_ID slotID, + CK_UTF8CHAR_PTR pPin, + CK_ULONG ulPinLen, + CK_UTF8CHAR_PTR pLabel) +{ + st_logf("InitToken: slot %d\n", (int)slotID); + return CKR_FUNCTION_NOT_SUPPORTED; +} + +CK_RV +C_OpenSession(CK_SLOT_ID slotID, + CK_FLAGS flags, + CK_VOID_PTR pApplication, + CK_NOTIFY Notify, + CK_SESSION_HANDLE_PTR phSession) +{ + size_t i; + + st_logf("OpenSession: slot: %d\n", (int)slotID); + + if (soft_token.open_sessions == MAX_NUM_SESSION) + return CKR_SESSION_COUNT; + + soft_token.application = pApplication; + soft_token.notify = Notify; + + for (i = 0; i < MAX_NUM_SESSION; i++) + if (soft_token.state[i].session_handle == CK_INVALID_HANDLE) + break; + if (i == MAX_NUM_SESSION) + abort(); + + soft_token.open_sessions++; + + soft_token.state[i].session_handle = + (CK_SESSION_HANDLE)(random() & 0xfffff); + *phSession = soft_token.state[i].session_handle; + + return CKR_OK; +} + +CK_RV +C_CloseSession(CK_SESSION_HANDLE hSession) +{ + struct session_state *state; + st_logf("CloseSession\n"); + + if (verify_session_handle(hSession, &state) != CKR_OK) + application_error("closed session not open"); + else + close_session(state); + + return CKR_OK; +} + +CK_RV +C_CloseAllSessions(CK_SLOT_ID slotID) +{ + size_t i; + + st_logf("CloseAllSessions\n"); + + for (i = 0; i < MAX_NUM_SESSION; i++) + if (soft_token.state[i].session_handle != CK_INVALID_HANDLE) + close_session(&soft_token.state[i]); + + return CKR_OK; +} + +CK_RV +C_GetSessionInfo(CK_SESSION_HANDLE hSession, + CK_SESSION_INFO_PTR pInfo) +{ + st_logf("GetSessionInfo\n"); + + VERIFY_SESSION_HANDLE(hSession, NULL); + + memset(pInfo, 20, sizeof(*pInfo)); + + pInfo->slotID = 1; + if (soft_token.flags.login_done) + pInfo->state = CKS_RO_USER_FUNCTIONS; + else + pInfo->state = CKS_RO_PUBLIC_SESSION; + pInfo->flags = CKF_SERIAL_SESSION; + pInfo->ulDeviceError = 0; + + return CKR_OK; +} + +CK_RV +C_Login(CK_SESSION_HANDLE hSession, + CK_USER_TYPE userType, + CK_UTF8CHAR_PTR pPin, + CK_ULONG ulPinLen) +{ + char *pin = NULL; + int i; + + st_logf("Login\n"); + + VERIFY_SESSION_HANDLE(hSession, NULL); + + if (pPin != NULL_PTR) { + asprintf(&pin, "%.*s", (int)ulPinLen, pPin); + st_logf("type: %d password: %s\n", (int)userType, pin); + } + + for (i = 0; i < soft_token.object.num_objs; i++) { + struct st_object *o = soft_token.object.objs[i]; + FILE *f; + + if (o->type != STO_T_PRIVATE_KEY) + continue; + + if (o->u.private_key.key) + continue; + + f = fopen(o->u.private_key.file, "r"); + if (f == NULL) { + st_logf("can't open private file: %s\n", o->u.private_key.file); + continue; + } + + o->u.private_key.key = PEM_read_PrivateKey(f, NULL, NULL, pin); + fclose(f); + if (o->u.private_key.key == NULL) { + st_logf("failed to read key: %s error: %s\n", + o->u.private_key.file, + ERR_error_string(ERR_get_error(), NULL)); + /* just ignore failure */; + continue; + } + + /* XXX check keytype */ + RSA *rsa = EVP_PKEY_get0_RSA(o->u.private_key.key); + RSA_set_method(rsa, RSA_PKCS1_OpenSSL()); + + if (X509_check_private_key(o->u.private_key.cert, o->u.private_key.key) != 1) { + EVP_PKEY_free(o->u.private_key.key); + o->u.private_key.key = NULL; + st_logf("private key %s doesn't verify\n", o->u.private_key.file); + continue; + } + + soft_token.flags.login_done = 1; + } + free(pin); + + return soft_token.flags.login_done ? CKR_OK : CKR_PIN_INCORRECT; +} + +CK_RV +C_Logout(CK_SESSION_HANDLE hSession) +{ + st_logf("Logout\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + +CK_RV +C_GetObjectSize(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject, + CK_ULONG_PTR pulSize) +{ + st_logf("GetObjectSize\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + +CK_RV +C_GetAttributeValue(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulCount) +{ + struct session_state *state; + struct st_object *obj; + CK_ULONG i; + CK_RV ret; + int j; + + st_logf("GetAttributeValue: %lx\n", + (unsigned long)HANDLE_OBJECT_ID(hObject)); + VERIFY_SESSION_HANDLE(hSession, &state); + + if ((ret = object_handle_to_object(hObject, &obj)) != CKR_OK) { + st_logf("object not found: %lx\n", + (unsigned long)HANDLE_OBJECT_ID(hObject)); + return ret; + } + + ret = CKR_OK; + for (i = 0; i < ulCount; i++) { + st_logf(" getting 0x%08lx\n", (unsigned long)pTemplate[i].type); + for (j = 0; j < obj->num_attributes; j++) { + if (obj->attrs[j].secret) { + pTemplate[i].ulValueLen = (CK_ULONG)-1; + break; + } + if (pTemplate[i].type == obj->attrs[j].attribute.type) { + if (pTemplate[i].pValue != NULL_PTR && obj->attrs[j].secret == 0) { + if (pTemplate[i].ulValueLen >= obj->attrs[j].attribute.ulValueLen) + memcpy(pTemplate[i].pValue, obj->attrs[j].attribute.pValue, + obj->attrs[j].attribute.ulValueLen); + } + pTemplate[i].ulValueLen = obj->attrs[j].attribute.ulValueLen; + break; + } + } + if (j == obj->num_attributes) { + st_logf("key type: 0x%08lx not found\n", (unsigned long)pTemplate[i].type); + pTemplate[i].ulValueLen = (CK_ULONG)-1; + ret = CKR_ATTRIBUTE_TYPE_INVALID; + } + + } + return ret; +} + +CK_RV +C_FindObjectsInit(CK_SESSION_HANDLE hSession, + CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulCount) +{ + struct session_state *state; + + st_logf("FindObjectsInit\n"); + + VERIFY_SESSION_HANDLE(hSession, &state); + + if (state->find.next_object != -1) { + application_error("application didn't do C_FindObjectsFinal\n"); + find_object_final(state); + } + if (ulCount) { + CK_ULONG i; + + print_attributes(pTemplate, ulCount); + + state->find.attributes = + calloc(1, ulCount * sizeof(state->find.attributes[0])); + if (state->find.attributes == NULL) + return CKR_DEVICE_MEMORY; + for (i = 0; i < ulCount; i++) { + state->find.attributes[i].pValue = + malloc(pTemplate[i].ulValueLen); + if (state->find.attributes[i].pValue == NULL) { + find_object_final(state); + return CKR_DEVICE_MEMORY; + } + memcpy(state->find.attributes[i].pValue, + pTemplate[i].pValue, pTemplate[i].ulValueLen); + state->find.attributes[i].type = pTemplate[i].type; + state->find.attributes[i].ulValueLen = pTemplate[i].ulValueLen; + } + state->find.num_attributes = ulCount; + state->find.next_object = 0; + } else { + st_logf("find all objects\n"); + state->find.attributes = NULL; + state->find.num_attributes = 0; + state->find.next_object = 0; + } + + return CKR_OK; +} + +CK_RV +C_FindObjects(CK_SESSION_HANDLE hSession, + CK_OBJECT_HANDLE_PTR phObject, + CK_ULONG ulMaxObjectCount, + CK_ULONG_PTR pulObjectCount) +{ + struct session_state *state; + int i; + + st_logf("FindObjects\n"); + + VERIFY_SESSION_HANDLE(hSession, &state); + + if (state->find.next_object == -1) { + application_error("application didn't do C_FindObjectsInit\n"); + return CKR_ARGUMENTS_BAD; + } + if (ulMaxObjectCount == 0) { + application_error("application asked for 0 objects\n"); + return CKR_ARGUMENTS_BAD; + } + *pulObjectCount = 0; + for (i = state->find.next_object; i < soft_token.object.num_objs; i++) { + st_logf("FindObjects: %d\n", i); + state->find.next_object = i + 1; + if (attributes_match(soft_token.object.objs[i], + state->find.attributes, + state->find.num_attributes)) { + *phObject++ = soft_token.object.objs[i]->object_handle; + ulMaxObjectCount--; + (*pulObjectCount)++; + if (ulMaxObjectCount == 0) + break; + } + } + return CKR_OK; +} + +CK_RV +C_FindObjectsFinal(CK_SESSION_HANDLE hSession) +{ + struct session_state *state; + + st_logf("FindObjectsFinal\n"); + VERIFY_SESSION_HANDLE(hSession, &state); + find_object_final(state); + return CKR_OK; +} + +static CK_RV +commonInit(CK_ATTRIBUTE *attr_match, int attr_match_len, + const CK_MECHANISM_TYPE *mechs, int mechs_len, + const CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey, + struct st_object **o) +{ + CK_RV ret; + int i; + + *o = NULL; + if ((ret = object_handle_to_object(hKey, o)) != CKR_OK) + return ret; + + ret = attributes_match(*o, attr_match, attr_match_len); + if (!ret) { + application_error("called commonInit on key that doesn't " + "support required attr"); + return CKR_ARGUMENTS_BAD; + } + + for (i = 0; i < mechs_len; i++) + if (mechs[i] == pMechanism->mechanism) + break; + if (i == mechs_len) { + application_error("called mech (%08lx) not supported\n", + pMechanism->mechanism); + return CKR_ARGUMENTS_BAD; + } + return CKR_OK; +} + + +static CK_RV +dup_mechanism(CK_MECHANISM_PTR *dup, const CK_MECHANISM_PTR pMechanism) +{ + CK_MECHANISM_PTR p; + + p = malloc(sizeof(*p)); + if (p == NULL) + return CKR_DEVICE_MEMORY; + + if (*dup) + free(*dup); + *dup = p; + memcpy(p, pMechanism, sizeof(*p)); + + return CKR_OK; +} + + +CK_RV +C_EncryptInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) +{ + struct session_state *state; + CK_MECHANISM_TYPE mechs[] = { CKM_RSA_PKCS, CKM_RSA_X_509 }; + CK_BBOOL bool_true = CK_TRUE; + CK_ATTRIBUTE attr[] = { + { CKA_ENCRYPT, &bool_true, sizeof(bool_true) } + }; + struct st_object *o; + CK_RV ret; + + st_logf("EncryptInit\n"); + VERIFY_SESSION_HANDLE(hSession, &state); + + ret = commonInit(attr, sizeof(attr)/sizeof(attr[0]), + mechs, sizeof(mechs)/sizeof(mechs[0]), + pMechanism, hKey, &o); + if (ret) + return ret; + + ret = dup_mechanism(&state->encrypt_mechanism, pMechanism); + if (ret == CKR_OK) + state->encrypt_object = OBJECT_ID(o); + + return ret; +} + +CK_RV +C_Encrypt(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pData, + CK_ULONG ulDataLen, + CK_BYTE_PTR pEncryptedData, + CK_ULONG_PTR pulEncryptedDataLen) +{ + struct session_state *state; + struct st_object *o; + void *buffer = NULL; + CK_RV ret; + RSA *rsa; + int padding, len, buffer_len, padding_len; + + st_logf("Encrypt\n"); + + VERIFY_SESSION_HANDLE(hSession, &state); + + if (state->encrypt_object == -1) + return CKR_ARGUMENTS_BAD; + + o = soft_token.object.objs[state->encrypt_object]; + + if (o->u.public_key == NULL) { + st_logf("public key NULL\n"); + return CKR_ARGUMENTS_BAD; + } + + rsa = EVP_PKEY_get0_RSA(o->u.public_key); + if (rsa == NULL) + return CKR_ARGUMENTS_BAD; + + RSA_blinding_off(rsa); /* XXX RAND is broken while running in mozilla ? */ + + buffer_len = RSA_size(rsa); + + buffer = malloc(buffer_len); + if (buffer == NULL) { + ret = CKR_DEVICE_MEMORY; + goto out; + } + + ret = CKR_OK; + switch(state->encrypt_mechanism->mechanism) { + case CKM_RSA_PKCS: + padding = RSA_PKCS1_PADDING; + padding_len = RSA_PKCS1_PADDING_SIZE; + break; + case CKM_RSA_X_509: + padding = RSA_NO_PADDING; + padding_len = 0; + break; + default: + ret = CKR_FUNCTION_NOT_SUPPORTED; + goto out; + } + + if (buffer_len + padding_len < (long) ulDataLen) { + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + if (pulEncryptedDataLen == NULL) { + st_logf("pulEncryptedDataLen NULL\n"); + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + if (pData == NULL_PTR) { + st_logf("data NULL\n"); + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + len = RSA_public_encrypt(ulDataLen, pData, buffer, rsa, padding); + if (len <= 0) { + ret = CKR_DEVICE_ERROR; + goto out; + } + if (len > buffer_len) + abort(); + + if (pEncryptedData != NULL_PTR) + memcpy(pEncryptedData, buffer, len); + *pulEncryptedDataLen = len; + + out: + if (buffer) { + memset(buffer, 0, buffer_len); + free(buffer); + } + return ret; +} + +CK_RV +C_EncryptUpdate(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pPart, + CK_ULONG ulPartLen, + CK_BYTE_PTR pEncryptedPart, + CK_ULONG_PTR pulEncryptedPartLen) +{ + st_logf("EncryptUpdate\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + + +CK_RV +C_EncryptFinal(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pLastEncryptedPart, + CK_ULONG_PTR pulLastEncryptedPartLen) +{ + st_logf("EncryptFinal\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + + +/* C_DecryptInit initializes a decryption operation. */ +CK_RV +C_DecryptInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) +{ + struct session_state *state; + CK_MECHANISM_TYPE mechs[] = { CKM_RSA_PKCS, CKM_RSA_X_509 }; + CK_BBOOL bool_true = CK_TRUE; + CK_ATTRIBUTE attr[] = { + { CKA_DECRYPT, &bool_true, sizeof(bool_true) } + }; + struct st_object *o; + CK_RV ret; + + st_logf("DecryptInit\n"); + VERIFY_SESSION_HANDLE(hSession, &state); + + ret = commonInit(attr, sizeof(attr)/sizeof(attr[0]), + mechs, sizeof(mechs)/sizeof(mechs[0]), + pMechanism, hKey, &o); + if (ret) + return ret; + + ret = dup_mechanism(&state->decrypt_mechanism, pMechanism); + if (ret == CKR_OK) + state->decrypt_object = OBJECT_ID(o); + + return CKR_OK; +} + + +CK_RV +C_Decrypt(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pEncryptedData, + CK_ULONG ulEncryptedDataLen, + CK_BYTE_PTR pData, + CK_ULONG_PTR pulDataLen) +{ + struct session_state *state; + struct st_object *o; + void *buffer = NULL; + CK_RV ret; + RSA *rsa; + int padding, len, buffer_len, padding_len; + + st_logf("Decrypt\n"); + + VERIFY_SESSION_HANDLE(hSession, &state); + + if (state->decrypt_object == -1) + return CKR_ARGUMENTS_BAD; + + o = soft_token.object.objs[state->decrypt_object]; + + if (o->u.private_key.key == NULL) { + st_logf("private key NULL\n"); + return CKR_ARGUMENTS_BAD; + } + + rsa = EVP_PKEY_get0_RSA(o->u.private_key.key); + if (rsa == NULL) + return CKR_ARGUMENTS_BAD; + + RSA_blinding_off(rsa); /* XXX RAND is broken while running in mozilla ? */ + + buffer_len = RSA_size(rsa); + + buffer = malloc(buffer_len); + if (buffer == NULL) { + ret = CKR_DEVICE_MEMORY; + goto out; + } + + ret = CKR_OK; + switch(state->decrypt_mechanism->mechanism) { + case CKM_RSA_PKCS: + padding = RSA_PKCS1_PADDING; + padding_len = RSA_PKCS1_PADDING_SIZE; + break; + case CKM_RSA_X_509: + padding = RSA_NO_PADDING; + padding_len = 0; + break; + default: + ret = CKR_FUNCTION_NOT_SUPPORTED; + goto out; + } + + if (buffer_len + padding_len < (long) ulEncryptedDataLen) { + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + if (pulDataLen == NULL) { + st_logf("pulDataLen NULL\n"); + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + if (pEncryptedData == NULL_PTR) { + st_logf("data NULL\n"); + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + len = RSA_private_decrypt(ulEncryptedDataLen, pEncryptedData, buffer, + rsa, padding); + if (len <= 0) { + ret = CKR_DEVICE_ERROR; + goto out; + } + if (len > buffer_len) + abort(); + + if (pData != NULL_PTR) + memcpy(pData, buffer, len); + *pulDataLen = len; + + out: + if (buffer) { + memset(buffer, 0, buffer_len); + free(buffer); + } + return ret; +} + + +CK_RV +C_DecryptUpdate(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pEncryptedPart, + CK_ULONG ulEncryptedPartLen, + CK_BYTE_PTR pPart, + CK_ULONG_PTR pulPartLen) + +{ + st_logf("DecryptUpdate\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + + +CK_RV +C_DecryptFinal(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pLastPart, + CK_ULONG_PTR pulLastPartLen) +{ + st_logf("DecryptFinal\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + +CK_RV +C_DigestInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism) +{ + st_logf("DigestInit\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + +CK_RV +C_SignInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) +{ + struct session_state *state; + CK_MECHANISM_TYPE mechs[] = { CKM_RSA_PKCS, CKM_RSA_X_509 }; + CK_BBOOL bool_true = CK_TRUE; + CK_ATTRIBUTE attr[] = { + { CKA_SIGN, &bool_true, sizeof(bool_true) } + }; + struct st_object *o; + CK_RV ret; + + st_logf("SignInit\n"); + VERIFY_SESSION_HANDLE(hSession, &state); + + ret = commonInit(attr, sizeof(attr)/sizeof(attr[0]), + mechs, sizeof(mechs)/sizeof(mechs[0]), + pMechanism, hKey, &o); + if (ret) + return ret; + + ret = dup_mechanism(&state->sign_mechanism, pMechanism); + if (ret == CKR_OK) + state->sign_object = OBJECT_ID(o); + + return CKR_OK; +} + +CK_RV +C_Sign(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pData, + CK_ULONG ulDataLen, + CK_BYTE_PTR pSignature, + CK_ULONG_PTR pulSignatureLen) +{ + struct session_state *state; + struct st_object *o; + void *buffer = NULL; + CK_RV ret; + RSA *rsa; + int padding, len, buffer_len; + size_t padding_len; + + st_logf("Sign\n"); + VERIFY_SESSION_HANDLE(hSession, &state); + + if (state->sign_object == -1) + return CKR_ARGUMENTS_BAD; + + o = soft_token.object.objs[state->sign_object]; + + if (o->u.private_key.key == NULL) { + st_logf("private key NULL\n"); + return CKR_ARGUMENTS_BAD; + } + + rsa = EVP_PKEY_get0_RSA(o->u.private_key.key); + if (rsa == NULL) + return CKR_ARGUMENTS_BAD; + + RSA_blinding_off(rsa); /* XXX RAND is broken while running in mozilla ? */ + + buffer_len = RSA_size(rsa); + + buffer = malloc(buffer_len); + if (buffer == NULL) { + ret = CKR_DEVICE_MEMORY; + goto out; + } + + switch(state->sign_mechanism->mechanism) { + case CKM_RSA_PKCS: + padding = RSA_PKCS1_PADDING; + padding_len = RSA_PKCS1_PADDING_SIZE; + break; + case CKM_RSA_X_509: + padding = RSA_NO_PADDING; + padding_len = 0; + break; + default: + ret = CKR_FUNCTION_NOT_SUPPORTED; + goto out; + } + + if ((size_t) buffer_len < ulDataLen + padding_len) { + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + if (pulSignatureLen == NULL) { + st_logf("signature len NULL\n"); + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + if (pData == NULL_PTR) { + st_logf("data NULL\n"); + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + len = RSA_private_encrypt(ulDataLen, pData, buffer, rsa, padding); + st_logf("private encrypt done\n"); + if (len <= 0) { + ret = CKR_DEVICE_ERROR; + goto out; + } + if (len > buffer_len) + abort(); + + if (pSignature != NULL_PTR) + memcpy(pSignature, buffer, len); + *pulSignatureLen = len; + + ret = CKR_OK; + + out: + if (buffer) { + memset(buffer, 0, buffer_len); + free(buffer); + } + return ret; +} + +CK_RV +C_SignUpdate(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) +{ + st_logf("SignUpdate\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + + +CK_RV +C_SignFinal(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pSignature, + CK_ULONG_PTR pulSignatureLen) +{ + st_logf("SignUpdate\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + +CK_RV +C_VerifyInit(CK_SESSION_HANDLE hSession, + CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) +{ + struct session_state *state; + CK_MECHANISM_TYPE mechs[] = { CKM_RSA_PKCS, CKM_RSA_X_509 }; + CK_BBOOL bool_true = CK_TRUE; + CK_ATTRIBUTE attr[] = { + { CKA_VERIFY, &bool_true, sizeof(bool_true) } + }; + struct st_object *o; + CK_RV ret; + + st_logf("VerifyInit\n"); + VERIFY_SESSION_HANDLE(hSession, &state); + + ret = commonInit(attr, sizeof(attr)/sizeof(attr[0]), + mechs, sizeof(mechs)/sizeof(mechs[0]), + pMechanism, hKey, &o); + if (ret) + return ret; + + ret = dup_mechanism(&state->verify_mechanism, pMechanism); + if (ret == CKR_OK) + state->verify_object = OBJECT_ID(o); + + return ret; +} + +CK_RV +C_Verify(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pData, + CK_ULONG ulDataLen, + CK_BYTE_PTR pSignature, + CK_ULONG ulSignatureLen) +{ + struct session_state *state; + struct st_object *o; + void *buffer = NULL; + CK_RV ret; + RSA *rsa; + int padding, len, buffer_len; + + st_logf("Verify\n"); + VERIFY_SESSION_HANDLE(hSession, &state); + + if (state->verify_object == -1) + return CKR_ARGUMENTS_BAD; + + o = soft_token.object.objs[state->verify_object]; + + if (o->u.public_key == NULL) { + st_logf("public key NULL\n"); + return CKR_ARGUMENTS_BAD; + } + + rsa = EVP_PKEY_get0_RSA(o->u.public_key); + if (rsa == NULL) + return CKR_ARGUMENTS_BAD; + + RSA_blinding_off(rsa); /* XXX RAND is broken while running in mozilla ? */ + + buffer_len = RSA_size(rsa); + + buffer = malloc(buffer_len); + if (buffer == NULL) { + ret = CKR_DEVICE_MEMORY; + goto out; + } + + ret = CKR_OK; + switch(state->verify_mechanism->mechanism) { + case CKM_RSA_PKCS: + padding = RSA_PKCS1_PADDING; + break; + case CKM_RSA_X_509: + padding = RSA_NO_PADDING; + break; + default: + ret = CKR_FUNCTION_NOT_SUPPORTED; + goto out; + } + + if (buffer_len < (long) ulDataLen) { + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + if (pSignature == NULL) { + st_logf("signature NULL\n"); + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + if (pData == NULL_PTR) { + st_logf("data NULL\n"); + ret = CKR_ARGUMENTS_BAD; + goto out; + } + + len = RSA_public_decrypt(ulDataLen, pData, buffer, rsa, padding); + st_logf("private encrypt done\n"); + if (len <= 0) { + ret = CKR_DEVICE_ERROR; + goto out; + } + if (len > buffer_len) + abort(); + + if ((size_t) len != ulSignatureLen) { + ret = CKR_GENERAL_ERROR; + goto out; + } + + if (memcmp(pSignature, buffer, len) != 0) { + ret = CKR_GENERAL_ERROR; + goto out; + } + + out: + if (buffer) { + memset(buffer, 0, buffer_len); + free(buffer); + } + return ret; +} + + +CK_RV +C_VerifyUpdate(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) +{ + st_logf("VerifyUpdate\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + +CK_RV +C_VerifyFinal(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR pSignature, + CK_ULONG ulSignatureLen) +{ + st_logf("VerifyFinal\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + +CK_RV +C_GenerateRandom(CK_SESSION_HANDLE hSession, + CK_BYTE_PTR RandomData, + CK_ULONG ulRandomLen) +{ + st_logf("GenerateRandom\n"); + VERIFY_SESSION_HANDLE(hSession, NULL); + return CKR_FUNCTION_NOT_SUPPORTED; +} + + +CK_FUNCTION_LIST funcs = { + { 2, 11 }, + C_Initialize, + C_Finalize, + C_GetInfo, + C_GetFunctionList, + C_GetSlotList, + C_GetSlotInfo, + C_GetTokenInfo, + C_GetMechanismList, + C_GetMechanismInfo, + C_InitToken, + (void *)func_not_supported, /* C_InitPIN */ + (void *)func_not_supported, /* C_SetPIN */ + C_OpenSession, + C_CloseSession, + C_CloseAllSessions, + C_GetSessionInfo, + (void *)func_not_supported, /* C_GetOperationState */ + (void *)func_not_supported, /* C_SetOperationState */ + C_Login, + C_Logout, + (void *)func_not_supported, /* C_CreateObject */ + (void *)func_not_supported, /* C_CopyObject */ + (void *)func_not_supported, /* C_DestroyObject */ + (void *)func_not_supported, /* C_GetObjectSize */ + C_GetAttributeValue, + (void *)func_not_supported, /* C_SetAttributeValue */ + C_FindObjectsInit, + C_FindObjects, + C_FindObjectsFinal, + C_EncryptInit, + C_Encrypt, + C_EncryptUpdate, + C_EncryptFinal, + C_DecryptInit, + C_Decrypt, + C_DecryptUpdate, + C_DecryptFinal, + C_DigestInit, + (void *)func_not_supported, /* C_Digest */ + (void *)func_not_supported, /* C_DigestUpdate */ + (void *)func_not_supported, /* C_DigestKey */ + (void *)func_not_supported, /* C_DigestFinal */ + C_SignInit, + C_Sign, + C_SignUpdate, + C_SignFinal, + (void *)func_not_supported, /* C_SignRecoverInit */ + (void *)func_not_supported, /* C_SignRecover */ + C_VerifyInit, + C_Verify, + C_VerifyUpdate, + C_VerifyFinal, + (void *)func_not_supported, /* C_VerifyRecoverInit */ + (void *)func_not_supported, /* C_VerifyRecover */ + (void *)func_not_supported, /* C_DigestEncryptUpdate */ + (void *)func_not_supported, /* C_DecryptDigestUpdate */ + (void *)func_not_supported, /* C_SignEncryptUpdate */ + (void *)func_not_supported, /* C_DecryptVerifyUpdate */ + (void *)func_not_supported, /* C_GenerateKey */ + (void *)func_not_supported, /* C_GenerateKeyPair */ + (void *)func_not_supported, /* C_WrapKey */ + (void *)func_not_supported, /* C_UnwrapKey */ + (void *)func_not_supported, /* C_DeriveKey */ + (void *)func_not_supported, /* C_SeedRandom */ + C_GenerateRandom, + (void *)func_not_supported, /* C_GetFunctionStatus */ + (void *)func_not_supported, /* C_CancelFunction */ + (void *)func_not_supported /* C_WaitForSlotEvent */ +}; diff --git a/regress/unittests/Makefile b/regress/unittests/Makefile index e464b085..a0e5a37c 100644 --- a/regress/unittests/Makefile +++ b/regress/unittests/Makefile @@ -2,6 +2,6 @@ REGRESS_FAIL_EARLY?= yes SUBDIR= test_helper sshbuf sshkey bitmap kex hostkeys utf8 match conversion -SUBDIR+=authopt +SUBDIR+=pkcs11 authopt .include diff --git a/regress/unittests/pkcs11/Makefile b/regress/unittests/pkcs11/Makefile new file mode 100644 index 00000000..481b13d0 --- /dev/null +++ b/regress/unittests/pkcs11/Makefile @@ -0,0 +1,9 @@ + +PROG=test_pkcs11 +SRCS=tests.c +REGRESS_TARGETS=run-regress-${PROG} + +run-regress-${PROG}: ${PROG} + env ${TEST_ENV} ./${PROG} + +.include diff --git a/regress/unittests/pkcs11/tests.c b/regress/unittests/pkcs11/tests.c new file mode 100644 index 00000000..e83aca54 --- /dev/null +++ b/regress/unittests/pkcs11/tests.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2017 Red Hat + * + * Authors: Jakub Jelen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include + +#include "../test_helper/test_helper.h" + +#include "ssh-pkcs11-uri.h" + +#define EMPTY_URI compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL) + +/* prototypes are not public -- specify them here internally for tests */ +struct sshbuf *percent_encode(const char *, size_t, char *); +int percent_decode(char *, char **); + +void +compare_uri(struct pkcs11_uri *a, struct pkcs11_uri *b) +{ + ASSERT_PTR_NE(a, NULL); + ASSERT_PTR_NE(b, NULL); + ASSERT_SIZE_T_EQ(a->id_len, b->id_len); + ASSERT_MEM_EQ(a->id, b->id, a->id_len); + if (b->object != NULL) + ASSERT_STRING_EQ(a->object, b->object); + else /* both should be null */ + ASSERT_PTR_EQ(a->object, b->object); + if (b->module_path != NULL) + ASSERT_STRING_EQ(a->module_path, b->module_path); + else /* both should be null */ + ASSERT_PTR_EQ(a->module_path, b->module_path); + if (b->token != NULL) + ASSERT_STRING_EQ(a->token, b->token); + else /* both should be null */ + ASSERT_PTR_EQ(a->token, b->token); + if (b->manuf != NULL) + ASSERT_STRING_EQ(a->manuf, b->manuf); + else /* both should be null */ + ASSERT_PTR_EQ(a->manuf, b->manuf); + if (b->lib_manuf != NULL) + ASSERT_STRING_EQ(a->lib_manuf, b->lib_manuf); + else /* both should be null */ + ASSERT_PTR_EQ(a->lib_manuf, b->lib_manuf); +} + +void +check_parse_rv(char *uri, struct pkcs11_uri *expect, int expect_rv) +{ + char *buf = NULL, *str; + struct pkcs11_uri *pkcs11uri = NULL; + int rv; + + if (expect_rv == 0) + str = "Valid"; + else + str = "Invalid"; + asprintf(&buf, "%s PKCS#11 URI parsing: %s", str, uri); + TEST_START(buf); + free(buf); + pkcs11uri = pkcs11_uri_init(); + rv = pkcs11_uri_parse(uri, pkcs11uri); + ASSERT_INT_EQ(rv, expect_rv); + if (rv == 0) /* in case of failure result is undefined */ + compare_uri(pkcs11uri, expect); + pkcs11_uri_cleanup(pkcs11uri); + free(expect); + TEST_DONE(); +} + +void +check_parse(char *uri, struct pkcs11_uri *expect) +{ + check_parse_rv(uri, expect, 0); +} + +struct pkcs11_uri * +compose_uri(unsigned char *id, size_t id_len, char *token, char *lib_manuf, + char *manuf, char *module_path, char *object) +{ + struct pkcs11_uri *uri = pkcs11_uri_init(); + if (id_len > 0) { + uri->id_len = id_len; + uri->id = id; + } + uri->module_path = module_path; + uri->token = token; + uri->lib_manuf = lib_manuf; + uri->manuf = manuf; + uri->object = object; + return uri; +} + +static void +test_parse_valid(void) +{ + /* path arguments */ + check_parse("pkcs11:id=%01", + compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL)); + check_parse("pkcs11:id=%00%01", + compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL)); + check_parse("pkcs11:token=SSH%20Keys", + compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL)); + check_parse("pkcs11:library-manufacturer=OpenSC", + compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL)); + check_parse("pkcs11:manufacturer=piv_II", + compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL)); + check_parse("pkcs11:object=SIGN%20Key", + compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "SIGN Key")); + /* query arguments */ + check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so", + compose_uri(NULL, 0, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL)); + + /* combinations */ + /* ID SHOULD be percent encoded */ + check_parse("pkcs11:token=SSH%20Key;id=0", + compose_uri("0", 1, "SSH Key", NULL, NULL, NULL, NULL)); + check_parse( + "pkcs11:manufacturer=CAC?module-path=/usr/lib64/p11-kit-proxy.so", + compose_uri(NULL, 0, NULL, NULL, "CAC", + "/usr/lib64/p11-kit-proxy.so", NULL)); + check_parse( + "pkcs11:object=RSA%20Key?module-path=/usr/lib64/pkcs11/opencryptoki.so", + compose_uri(NULL, 0, NULL, NULL, NULL, + "/usr/lib64/pkcs11/opencryptoki.so", "RSA Key")); + + /* empty path component matches everything */ + check_parse("pkcs11:", EMPTY_URI); + + /* empty string is a valid to match against (and different from NULL) */ + check_parse("pkcs11:token=", + compose_uri(NULL, 0, "", NULL, NULL, NULL, NULL)); + /* Percent character needs to be percent-encoded */ + check_parse("pkcs11:token=%25", + compose_uri(NULL, 0, "%", NULL, NULL, NULL, NULL)); +} + +static void +test_parse_invalid(void) +{ + /* Invalid percent encoding */ + check_parse_rv("pkcs11:id=%0", EMPTY_URI, -1); + /* Invalid percent encoding */ + check_parse_rv("pkcs11:id=%ZZ", EMPTY_URI, -1); + /* Space MUST be percent encoded -- XXX not enforced yet */ + check_parse("pkcs11:token=SSH Keys", + compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL)); + /* MUST NOT contain duplicate attributes of the same name */ + check_parse_rv("pkcs11:id=%01;id=%02", EMPTY_URI, -1); + /* Unrecognized attribute in path SHOULD be error */ + check_parse_rv("pkcs11:key_name=SSH", EMPTY_URI, -1); + /* Unrecognized attribute in query SHOULD be ignored */ + check_parse("pkcs11:?key_name=SSH", EMPTY_URI); +} + +void +check_gen(char *expect, struct pkcs11_uri *uri) +{ + char *buf = NULL, *uri_str; + + asprintf(&buf, "Valid PKCS#11 URI generation: %s", expect); + TEST_START(buf); + free(buf); + uri_str = pkcs11_uri_get(uri); + ASSERT_PTR_NE(uri_str, NULL); + ASSERT_STRING_EQ(uri_str, expect); + free(uri_str); + TEST_DONE(); +} + +static void +test_generate_valid(void) +{ + /* path arguments */ + check_gen("pkcs11:id=%01", + compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL)); + check_gen("pkcs11:id=%00%01", + compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL)); + check_gen("pkcs11:token=SSH%20Keys", /* space must be percent encoded */ + compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL)); + /* library-manufacturer is not implmented now */ + /*check_gen("pkcs11:library-manufacturer=OpenSC", + compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL));*/ + check_gen("pkcs11:manufacturer=piv_II", + compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL)); + check_gen("pkcs11:object=RSA%20Key", + compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "RSA Key")); + /* query arguments */ + check_gen("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so", + compose_uri(NULL, 0, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL)); + + /* combinations */ + check_gen("pkcs11:id=%02;token=SSH%20Keys", + compose_uri("\x02", 1, "SSH Keys", NULL, NULL, NULL, NULL)); + check_gen("pkcs11:id=%EE%02?module-path=/usr/lib64/p11-kit-proxy.so", + compose_uri("\xEE\x02", 2, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL)); + check_gen("pkcs11:object=Encryption%20Key;manufacturer=piv_II", + compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, "Encryption Key")); + + /* empty path component matches everything */ + check_gen("pkcs11:", EMPTY_URI); + +} + +void +check_encode(char *source, size_t len, char *whitelist, char *expect) +{ + char *buf = NULL; + struct sshbuf *b; + + asprintf(&buf, "percent_encode: expected %s", expect); + TEST_START(buf); + free(buf); + + b = percent_encode(source, len, whitelist); + ASSERT_STRING_EQ(sshbuf_ptr(b), expect); + sshbuf_free(b); + TEST_DONE(); +} + +static void +test_percent_encode_multibyte(void) +{ + /* SHOULD be encoded as octets according to the UTF-8 character encoding */ + + /* multi-byte characters are "for free" */ + check_encode("$", 1, "", "%24"); + check_encode("¢", 2, "", "%C2%A2"); + check_encode("€", 3, "", "%E2%82%AC"); + check_encode("𐍈", 4, "", "%F0%90%8D%88"); + + /* CK_UTF8CHAR is unsigned char (1 byte) */ + /* labels SHOULD be normalized to NFC [UAX15] */ + +} + +static void +test_percent_encode(void) +{ + /* Without whitelist encodes everything (for CKA_ID) */ + check_encode("A*", 2, "", "%41%2A"); + check_encode("\x00", 1, "", "%00"); + check_encode("\x7F", 1, "", "%7F"); + check_encode("\x80", 1, "", "%80"); + check_encode("\xff", 1, "", "%FF"); + + /* Default whitelist encodes anything but safe letters */ + check_encode("test" "\x00" "0alpha", 11, PKCS11_URI_WHITELIST, + "test%000alpha"); + check_encode(" ", 1, PKCS11_URI_WHITELIST, + "%20"); /* Space MUST be percent encoded */ + check_encode("/", 1, PKCS11_URI_WHITELIST, + "%2F"); /* '/' delimiter MUST be percent encoded (in the path) */ + check_encode("?", 1, PKCS11_URI_WHITELIST, + "%3F"); /* delimiter '?' MUST be percent encoded (in the path) */ + check_encode("#", 1, PKCS11_URI_WHITELIST, + "%23"); /* '#' MUST be always percent encoded */ + check_encode("key=value;separator?query&#anch", 35, PKCS11_URI_WHITELIST, + "key%3Dvalue%3Bseparator%3Fquery%26amp%3B%23anch"); + + /* Components in query can have '/' unencoded (useful for paths) */ + check_encode("/path/to.file", 13, PKCS11_URI_WHITELIST "/", + "/path/to.file"); +} + +void +check_decode(char *source, char *expect, int expect_len) +{ + char *buf = NULL, *out = NULL; + int rv; + + asprintf(&buf, "percent_decode: %s", source); + TEST_START(buf); + free(buf); + + rv = percent_decode(source, &out); + ASSERT_INT_EQ(rv, expect_len); + if (rv >= 0) + ASSERT_MEM_EQ(out, expect, expect_len); + free(out); + TEST_DONE(); +} + +static void +test_percent_decode(void) +{ + /* simple valid cases */ + check_decode("%00", "\x00", 1); + check_decode("%FF", "\xFF", 1); + + /* normal strings shold be kept intact */ + check_decode("strings are left", "strings are left", 16); + check_decode("10%25 of trees", "10% of trees", 12); + + /* make sure no more than 2 bytes are parsed */ + check_decode("%222", "\x22" "2", 2); + + /* invalid expects failure */ + check_decode("%0", "", -1); + check_decode("%Z", "", -1); + check_decode("%FG", "", -1); +} + +void +tests(void) +{ + test_percent_encode(); + test_percent_encode_multibyte(); + test_percent_decode(); + test_parse_valid(); + test_parse_invalid(); + test_generate_valid(); +} diff --git a/ssh-add.c b/ssh-add.c index adcc4599..e4fd5623 100644 --- a/ssh-add.c +++ b/ssh-add.c @@ -64,6 +64,7 @@ #include "misc.h" #include "ssherr.h" #include "digest.h" +#include "ssh-pkcs11-uri.h" /* argv0 */ extern char *__progname; @@ -188,6 +189,24 @@ delete_all(int agent_fd) return ret; } +#ifdef ENABLE_PKCS11 +static int update_card(int, int, const char *); + +int +update_pkcs11_uri(int agent_fd, int adding, const char *pkcs11_uri) +{ + struct pkcs11_uri *uri; + + /* dry-run parse to make sure the URI is valid and to report errors */ + uri = pkcs11_uri_init(); + if (pkcs11_uri_parse((char *) pkcs11_uri, uri) != 0) + fatal("Failed to parse PKCS#11 URI"); + pkcs11_uri_cleanup(uri); + + return update_card(agent_fd, adding, pkcs11_uri); +} +#endif + static int add_file(int agent_fd, const char *filename, int key_only, int qflag) { @@ -480,6 +499,13 @@ lock_agent(int agent_fd, int lock) static int do_file(int agent_fd, int deleting, int key_only, char *file, int qflag) { +#ifdef ENABLE_PKCS11 + if (strlen(file) >= strlen(PKCS11_URI_SCHEME) && + strncmp(file, PKCS11_URI_SCHEME, + strlen(PKCS11_URI_SCHEME)) == 0) { + return update_pkcs11_uri(agent_fd, !deleting, file); + } +#endif if (deleting) { if (delete_file(agent_fd, file, key_only, qflag) == -1) return -1; diff --git a/ssh-agent.c b/ssh-agent.c index 2a4578b0..f6c86240 100644 --- a/ssh-agent.c +++ b/ssh-agent.c @@ -546,10 +546,72 @@ no_identities(SocketEntry *e) } #ifdef ENABLE_PKCS11 +static char * +sanitize_pkcs11_provider(const char *provider) +{ + struct pkcs11_uri *uri = NULL; + char *sane_uri, *module_path = NULL; /* default path */ + char canonical_provider[PATH_MAX]; + + if (provider == NULL) + return NULL; + + if (strlen(provider) >= strlen(PKCS11_URI_SCHEME) && + strncmp(provider, PKCS11_URI_SCHEME, + strlen(PKCS11_URI_SCHEME)) == 0) { + /* PKCS#11 URI */ + uri = pkcs11_uri_init(); + if (uri == NULL) { + error("Failed to init PCKS#11 URI"); + return NULL; + } + + if (pkcs11_uri_parse(provider, uri) != 0) { + error("Failed to parse PKCS#11 URI"); + return NULL; + } + /* validate also provider from URI */ + if (uri->module_path) + module_path = strdup(uri->module_path); + } else + module_path = strdup(provider); /* simple path */ + + if (module_path != NULL) { /* do not validate default NULL path in URI */ + if (realpath(module_path, canonical_provider) == NULL) { + verbose("failed PKCS#11 provider \"%.100s\": realpath: %s", + module_path, strerror(errno)); + free(module_path); + pkcs11_uri_cleanup(uri); + return NULL; + } + free(module_path); + if (match_pattern_list(canonical_provider, pkcs11_whitelist, 0) != 1) { + verbose("refusing PKCS#11 provider \"%.100s\": " + "not whitelisted", canonical_provider); + pkcs11_uri_cleanup(uri); + return NULL; + } + + /* copy verified and sanitized provider path back to the uri */ + if (uri) { + free(uri->module_path); + uri->module_path = xstrdup(canonical_provider); + } + } + + if (uri) { + sane_uri = pkcs11_uri_get(uri); + pkcs11_uri_cleanup(uri); + return sane_uri; + } else { + return xstrdup(canonical_provider); /* simple path */ + } +} + static void process_add_smartcard_key(SocketEntry *e) { - char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX]; + char *provider = NULL, *pin = NULL, *sane_uri = NULL; int r, i, count = 0, success = 0, confirm = 0; u_int seconds; time_t death = 0; @@ -585,28 +645,23 @@ process_add_smartcard_key(SocketEntry *e) goto send; } } - if (realpath(provider, canonical_provider) == NULL) { - verbose("failed PKCS#11 add of \"%.100s\": realpath: %s", - provider, strerror(errno)); - goto send; - } - if (match_pattern_list(canonical_provider, pkcs11_whitelist, 0) != 1) { - verbose("refusing PKCS#11 add of \"%.100s\": " - "provider not whitelisted", canonical_provider); + + sane_uri = sanitize_pkcs11_provider(provider); + if (sane_uri == NULL) goto send; - } - debug("%s: add %.100s", __func__, canonical_provider); + if (lifetime && !death) death = monotime() + lifetime; - count = pkcs11_add_provider(canonical_provider, pin, &keys); + debug("%s: add %.100s", __func__, sane_uri); + count = pkcs11_add_provider(sane_uri, pin, &keys); for (i = 0; i < count; i++) { k = keys[i]; if (lookup_identity(k) == NULL) { id = xcalloc(1, sizeof(Identity)); id->key = k; - id->provider = xstrdup(canonical_provider); - id->comment = xstrdup(canonical_provider); /* XXX */ + id->provider = xstrdup(sane_uri); + id->comment = xstrdup(sane_uri); id->death = death; id->confirm = confirm; TAILQ_INSERT_TAIL(&idtab->idlist, id, next); @@ -620,6 +675,7 @@ process_add_smartcard_key(SocketEntry *e) send: free(pin); free(provider); + free(sane_uri); free(keys); send_status(e, success); } @@ -627,7 +683,7 @@ send: static void process_remove_smartcard_key(SocketEntry *e) { - char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX]; + char *provider = NULL, *pin = NULL, *sane_uri = NULL; int r, success = 0; Identity *id, *nxt; @@ -638,30 +694,29 @@ process_remove_smartcard_key(SocketEntry *e) } free(pin); - if (realpath(provider, canonical_provider) == NULL) { - verbose("failed PKCS#11 add of \"%.100s\": realpath: %s", - provider, strerror(errno)); + sane_uri = sanitize_pkcs11_provider(provider); + if (sane_uri == NULL) goto send; - } - debug("%s: remove %.100s", __func__, canonical_provider); + debug("%s: remove %.100s", __func__, sane_uri); for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) { nxt = TAILQ_NEXT(id, next); /* Skip file--based keys */ if (id->provider == NULL) continue; - if (!strcmp(canonical_provider, id->provider)) { + if (!strcmp(sane_uri, id->provider)) { TAILQ_REMOVE(&idtab->idlist, id, next); free_identity(id); idtab->nentries--; } } - if (pkcs11_del_provider(canonical_provider) == 0) + if (pkcs11_del_provider(sane_uri) == 0) success = 1; else error("%s: pkcs11_del_provider failed", __func__); send: free(provider); + free(sane_uri); send_status(e, success); } #endif /* ENABLE_PKCS11 */ diff --git a/ssh-keygen.c b/ssh-keygen.c index d1ffc30c..00e38049 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -820,6 +820,7 @@ do_download(struct passwd *pw) free(fp); } else { (void) sshkey_write(keys[i], stdout); /* XXX check */ + (void) pkcs11_uri_write(keys[i], stdout); fprintf(stdout, "\n"); } sshkey_free(keys[i]); diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c index a023f5f4..882e8381 100644 --- a/ssh-pkcs11-client.c +++ b/ssh-pkcs11-client.c @@ -117,6 +117,7 @@ pkcs11_rsa_private_encrypt(int flen, const u_char *from, u_char *to, RSA *rsa, return (-1); key.type = KEY_RSA; key.rsa = rsa; + key.ecdsa_nid = 0; if (key_to_blob(&key, &blob, &blen) == 0) return -1; buffer_init(&msg); @@ -195,6 +196,8 @@ pkcs11_add_provider(char *name, char *pin, Key ***keysp) u_int blen; Buffer msg; + debug("%s: called, name = %s", __func__, name); + if (fd < 0 && pkcs11_start_helper() < 0) return (-1); @@ -208,6 +211,7 @@ pkcs11_add_provider(char *name, char *pin, Key ***keysp) if (recv_msg(&msg) == SSH2_AGENT_IDENTITIES_ANSWER) { nkeys = buffer_get_int(&msg); *keysp = xcalloc(nkeys, sizeof(Key *)); + debug("%s: nkeys = %d", __func__, nkeys); for (i = 0; i < nkeys; i++) { blob = buffer_get_string(&msg, &blen); free(buffer_get_string(&msg, NULL)); diff --git a/ssh-pkcs11-uri.c b/ssh-pkcs11-uri.c new file mode 100644 index 00000000..da15c164 --- /dev/null +++ b/ssh-pkcs11-uri.c @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2017 Red Hat + * + * Authors: Jakub Jelen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#ifdef ENABLE_PKCS11 + +#include +#include + +#include "sshkey.h" +#include "log.h" + +#define CRYPTOKI_COMPAT +#include "pkcs11.h" + +#include "ssh-pkcs11-uri.h" + +#define PKCS11_URI_PATH_SEPARATOR ";" +#define PKCS11_URI_QUERY_SEPARATOR "&" +#define PKCS11_URI_VALUE_SEPARATOR "=" +#define PKCS11_URI_ID "id" +#define PKCS11_URI_TOKEN "token" +#define PKCS11_URI_OBJECT "object" +#define PKCS11_URI_LIB_MANUF "library-manufacturer" +#define PKCS11_URI_MANUF "manufacturer" +#define PKCS11_URI_MODULE_PATH "module-path" + +/* Keyword tokens. */ +typedef enum { + pId, pToken, pObject, pLibraryManufacturer, pManufacturer, pModulePath, + pBadOption +} pkcs11uriOpCodes; + +/* Textual representation of the tokens. */ +static struct { + const char *name; + pkcs11uriOpCodes opcode; +} keywords[] = { + { PKCS11_URI_ID, pId }, + { PKCS11_URI_TOKEN, pToken }, + { PKCS11_URI_OBJECT, pObject }, + { PKCS11_URI_LIB_MANUF, pLibraryManufacturer }, + { PKCS11_URI_MANUF, pManufacturer }, + { PKCS11_URI_MODULE_PATH, pModulePath }, + { NULL, pBadOption } +}; + +static pkcs11uriOpCodes +parse_token(const char *cp) +{ + u_int i; + + for (i = 0; keywords[i].name; i++) + if (strncasecmp(cp, keywords[i].name, + strlen(keywords[i].name)) == 0) + return keywords[i].opcode; + + return pBadOption; +} + +int +percent_decode(char *data, char **outp) +{ + char tmp[3]; + char *out, *tmp_end; + char *p = data; + long value; + size_t outlen = 0; + + out = malloc(strlen(data)+1); /* upper bound */ + if (out == NULL) + return -1; + while (*p != '\0') { + switch (*p) { + case '%': + p++; + if (*p == '\0') + goto fail; + tmp[0] = *p++; + if (*p == '\0') + goto fail; + tmp[1] = *p++; + tmp[2] = '\0'; + tmp_end = NULL; + value = strtol(tmp, &tmp_end, 16); + if (tmp_end != tmp+2) + goto fail; + else + out[outlen++] = (char) value; + break; + default: + out[outlen++] = *p++; + break; + } + } + + /* zero terminate */ + out[outlen] = '\0'; + *outp = out; + return outlen; +fail: + free(out); + return -1; +} + +struct sshbuf * +percent_encode(const char *data, size_t length, const char *whitelist) +{ + struct sshbuf *b = NULL; + char tmp[4], *cp; + size_t i; + + if ((b = sshbuf_new()) == NULL) + return NULL; + for (i = 0; i < length; i++) { + cp = strchr(whitelist, data[i]); + /* if c is specified as '\0' pointer to terminator is returned !! */ + if (cp != NULL && *cp != '\0') { + if (sshbuf_put(b, &data[i], 1) != 0) + goto err; + } else + if (snprintf(tmp, 4, "%%%02X", (unsigned char) data[i]) < 3 + || sshbuf_put(b, tmp, 3) != 0) + goto err; + } + if (sshbuf_put(b, "\0", 1) == 0) + return b; +err: + sshbuf_free(b); + return NULL; +} + +char * +pkcs11_uri_append(char *part, const char *separator, const char *key, + struct sshbuf *value) +{ + char *new_part; + size_t size; + + if (value == NULL) + return NULL; + + size = asprintf(&new_part, + "%s%s%s" PKCS11_URI_VALUE_SEPARATOR "%s", + (part != NULL ? part : ""), + (part != NULL ? separator : ""), + key, sshbuf_ptr(value)); + sshbuf_free(value); + free(part); + + if (size < 0) + return NULL; + return new_part; +} + +char * +pkcs11_uri_get(struct pkcs11_uri *uri) +{ + size_t size = -1; + char *p = NULL, *path = NULL, *query = NULL; + + /* compose a percent-encoded ID */ + if (uri->id_len > 0) { + struct sshbuf *key_id = percent_encode(uri->id, uri->id_len, ""); + path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR, + PKCS11_URI_ID, key_id); + if (path == NULL) + goto err; + } + + /* Write object label */ + if (uri->object) { + struct sshbuf *label = percent_encode(uri->object, strlen(uri->object), + PKCS11_URI_WHITELIST); + path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR, + PKCS11_URI_OBJECT, label); + if (path == NULL) + goto err; + } + + /* Write token label */ + if (uri->token) { + struct sshbuf *label = percent_encode(uri->token, strlen(uri->token), + PKCS11_URI_WHITELIST); + path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR, + PKCS11_URI_TOKEN, label); + if (path == NULL) + goto err; + } + + /* Write manufacturer */ + if (uri->manuf) { + struct sshbuf *manuf = percent_encode(uri->manuf, + strlen(uri->manuf), PKCS11_URI_WHITELIST); + path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR, + PKCS11_URI_MANUF, manuf); + if (path == NULL) + goto err; + } + + /* Write module_path */ + if (uri->module_path) { + struct sshbuf *module = percent_encode(uri->module_path, + strlen(uri->module_path), PKCS11_URI_WHITELIST "/"); + query = pkcs11_uri_append(query, PKCS11_URI_QUERY_SEPARATOR, + PKCS11_URI_MODULE_PATH, module); + if (query == NULL) + goto err; + } + + size = asprintf(&p, PKCS11_URI_SCHEME "%s%s%s", + path != NULL ? path : "", + query != NULL ? "?" : "", + query != NULL ? query : ""); +err: + free(query); + free(path); + if (size < 0) + return NULL; + return p; +} + +struct pkcs11_uri * +pkcs11_uri_init() +{ + struct pkcs11_uri *d = calloc(1, sizeof(struct pkcs11_uri)); + return d; +} + +void +pkcs11_uri_cleanup(struct pkcs11_uri *pkcs11) +{ + free(pkcs11->id); + free(pkcs11->module_path); + free(pkcs11->token); + free(pkcs11->object); + free(pkcs11->lib_manuf); + free(pkcs11->manuf); + free(pkcs11); +} + +int +pkcs11_uri_parse(const char *uri, struct pkcs11_uri *pkcs11) +{ + char *saveptr1, *saveptr2, *str1, *str2, *tok; + int rv = 0, len; + char *p = NULL; + + size_t scheme_len = strlen(PKCS11_URI_SCHEME); + if (strlen(uri) < scheme_len || /* empty URI matches everything */ + strncmp(uri, PKCS11_URI_SCHEME, scheme_len) != 0) { + error("%s: The '%s' does not look like PKCS#11 URI", + __func__, uri); + return -1; + } + + if (pkcs11 == NULL) { + error("%s: Bad arguments. The pkcs11 can't be null", __func__); + return -1; + } + + /* skip URI schema name */ + p = strdup(uri); + str1 = p; + + /* everything before ? */ + tok = strtok_r(str1, "?", &saveptr1); + if (tok == NULL) { + free(p); + error("%s: pk11-path expected, got EOF", __func__); + return -1; + } + + /* skip URI schema name: + * the scheme ensures that there is at least something before "?" + * allowing empty pk11-path. Resulting token at worst pointing to + * \0 byte */ + tok = tok + scheme_len; + + /* parse pk11-path */ + for (str2 = tok; ; str2 = NULL) { + char **charptr; + pkcs11uriOpCodes opcode; + tok = strtok_r(str2, PKCS11_URI_PATH_SEPARATOR, &saveptr2); + if (tok == NULL) + break; + opcode = parse_token(tok); + if (opcode == pBadOption) { + free(p); + verbose("Unknown key in PKCS#11 URI: %s", tok); + return -1; + } + + char *arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */ + switch (opcode) { + case pId: + /* CKA_ID */ + if (pkcs11->id != NULL) { + verbose("%s: The id already set in the PKCS#11 URI", + __func__); + rv = -1; + } + len = percent_decode(arg, &pkcs11->id); + if (len <= 0) { + verbose("%s: Failed to percent-decode CKA_ID: %s", + __func__, arg); + rv = -1; + } else + pkcs11->id_len = len; + debug3("%s: Setting CKA_ID = %s from PKCS#11 URI", + __func__, arg); + break; + case pToken: + /* CK_TOKEN_INFO -> label */ + charptr = &pkcs11->token; + parse_string: + if (*charptr != NULL) { + verbose("%s: The %s already set in the PKCS#11 URI", + keywords[opcode].name, __func__); + rv = -1; + } + percent_decode(arg, charptr); + debug3("%s: Setting %s = %s from PKCS#11 URI", + __func__, keywords[opcode].name, *charptr); + break; + + case pObject: + /* CK_TOKEN_INFO -> manufacturerID */ + charptr = &pkcs11->object; + goto parse_string; + + case pManufacturer: + /* CK_TOKEN_INFO -> manufacturerID */ + charptr = &pkcs11->manuf; + goto parse_string; + + case pLibraryManufacturer: + /* CK_INFO -> manufacturerID */ + charptr = &pkcs11->lib_manuf; + goto parse_string; + + case pBadOption: + default: + /* Unrecognized attribute in the URI path SHOULD be error */ + verbose("%s: Unknown part of path in PKCS#11 URI: %s", + __func__, tok); + rv = -1; + } + } + + tok = strtok_r(NULL, "?", &saveptr1); + if (tok == NULL) { + free(p); + return rv; + } + /* parse pk11-query (optional) */ + for (str2 = tok; ; str2 = NULL) { + size_t key_len = strlen(PKCS11_URI_MODULE_PATH) + 1; + tok = strtok_r(str2, PKCS11_URI_QUERY_SEPARATOR, &saveptr2); + if (tok == NULL) + break; + if (strncasecmp(tok, PKCS11_URI_MODULE_PATH + PKCS11_URI_VALUE_SEPARATOR, key_len) == 0) { + /* module-path is PKCS11Provider */ + if (pkcs11->module_path != NULL) { + verbose("%s: Multiple module-path attributes are" + "not supported the PKCS#11 URI", __func__); + rv = -1; + } + percent_decode(tok + key_len, &pkcs11->module_path); + debug3("%s: Setting PKCS11Provider = %s from PKCS#11 URI", + __func__, pkcs11->module_path); + /* } else if ( pin-value ) { */ + } else { + /* Unrecognized attribute in the URI query SHOULD be ignored */ + verbose("%s: Unknown part of query in PKCS#11 URI: %s", + __func__, tok); + } + } + free(p); + return rv; +} + +#endif /* ENABLE_PKCS11 */ diff --git a/ssh-pkcs11-uri.h b/ssh-pkcs11-uri.h new file mode 100644 index 00000000..609c4df1 --- /dev/null +++ b/ssh-pkcs11-uri.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017 Red Hat + * + * Authors: Jakub Jelen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define PKCS11_URI_SCHEME "pkcs11:" +#define PKCS11_URI_WHITELIST "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "0123456789_-.()" + +struct pkcs11_uri { + /* path */ + char *id; + size_t id_len; + char *token; + char *object; + char *lib_manuf; + char *manuf; + /* query */ + char *module_path; +}; + +struct pkcs11_uri *pkcs11_uri_init(); +void pkcs11_uri_cleanup(struct pkcs11_uri *); +int pkcs11_uri_parse(const char *, struct pkcs11_uri *); +struct pkcs11_uri *pkcs11_uri_init(); +char * pkcs11_uri_get(struct pkcs11_uri *uri); + diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c index 88c9d6e2..a29b4451 100644 --- a/ssh-pkcs11.c +++ b/ssh-pkcs11.c @@ -48,8 +48,8 @@ struct pkcs11_slotinfo { int logged_in; }; -struct pkcs11_provider { - char *name; +struct pkcs11_module { + char *module_path; void *handle; CK_FUNCTION_LIST *function_list; CK_INFO info; @@ -58,6 +58,13 @@ struct pkcs11_provider { struct pkcs11_slotinfo *slotinfo; int valid; int refcount; +}; + +struct pkcs11_provider { + char *name; + struct pkcs11_module *module; /* can be shared between various providers */ + int refcount; + int valid; TAILQ_ENTRY(pkcs11_provider) next; }; @@ -70,10 +77,46 @@ struct pkcs11_key { RSA_METHOD rsa_method; char *keyid; int keyid_len; + char *label; }; int pkcs11_interactive = 0; +/* + * This can't be in the ssh-pkcs11-uri, becase we can not depend on + * PKCS#11 structures in ssh-agent (using client-helper communication) + */ +int +pkcs11_uri_write(const struct sshkey *key, FILE *f) +{ + char *p = NULL; + struct pkcs11_uri uri; + struct pkcs11_key *k11; + + /* sanity - is it a RSA key with associated app_data? */ + if (key->type != KEY_RSA || + (k11 = RSA_get_app_data(key->rsa)) == NULL) + return -1; + + /* omit type -- we are looking for private-public or private-certificate pairs */ + uri.id = k11->keyid; + uri.id_len = k11->keyid_len; + uri.token = k11->provider->module->slotinfo[k11->slotidx].token.label; + uri.object = k11->label; + uri.module_path = k11->provider->module->module_path; + uri.lib_manuf = k11->provider->module->info.manufacturerID; + uri.manuf = k11->provider->module->slotinfo[k11->slotidx].token.manufacturerID; + + p = pkcs11_uri_get(&uri); + /* do not cleanup -- we do not allocate here, only reference */ + if (p == NULL) + return -1; + + fprintf(f, " %s", p); + free(p); + return 0; +} + int pkcs11_init(int interactive) { @@ -89,26 +132,63 @@ pkcs11_init(int interactive) * this is called when a provider gets unregistered. */ static void -pkcs11_provider_finalize(struct pkcs11_provider *p) +pkcs11_module_finalize(struct pkcs11_module *m) { CK_RV rv; CK_ULONG i; - debug("pkcs11_provider_finalize: %p refcount %d valid %d", - p, p->refcount, p->valid); - if (!p->valid) + debug("%s: %p refcount %d valid %d", __func__, + m, m->refcount, m->valid); + if (!m->valid) return; - for (i = 0; i < p->nslots; i++) { - if (p->slotinfo[i].session && - (rv = p->function_list->C_CloseSession( - p->slotinfo[i].session)) != CKR_OK) + for (i = 0; i < m->nslots; i++) { + if (m->slotinfo[i].session && + (rv = m->function_list->C_CloseSession( + m->slotinfo[i].session)) != CKR_OK) error("C_CloseSession failed: %lu", rv); } - if ((rv = p->function_list->C_Finalize(NULL)) != CKR_OK) + if ((rv = m->function_list->C_Finalize(NULL)) != CKR_OK) error("C_Finalize failed: %lu", rv); + m->valid = 0; + m->function_list = NULL; + dlclose(m->handle); +} + +/* + * remove a reference to the pkcs11 module. + * called when a provider is unregistered. + */ +static void +pkcs11_module_unref(struct pkcs11_module *m) +{ + debug("%s: %p refcount %d", __func__, m, m->refcount); + if (--m->refcount <= 0) { + pkcs11_module_finalize(m); + if (m->valid) + error("%s: %p still valid", __func__, m); + free(m->slotlist); + free(m->slotinfo); + free(m->module_path); + free(m); + } +} + +/* + * finalize a provider shared libarary, it's no longer usable. + * however, there might still be keys referencing this provider, + * so the actuall freeing of memory is handled by pkcs11_provider_unref(). + * this is called when a provider gets unregistered. + */ +static void +pkcs11_provider_finalize(struct pkcs11_provider *p) +{ + debug("%s: %p refcount %d valid %d", __func__, + p, p->refcount, p->valid); + if (!p->valid) + return; + pkcs11_module_unref(p->module); + p->module = NULL; p->valid = 0; - p->function_list = NULL; - dlclose(p->handle); } /* @@ -118,12 +198,11 @@ pkcs11_provider_finalize(struct pkcs11_provider *p) static void pkcs11_provider_unref(struct pkcs11_provider *p) { - debug("pkcs11_provider_unref: %p refcount %d", p, p->refcount); + debug("%s: %p refcount %d", __func__, p, p->refcount); if (--p->refcount <= 0) { - if (p->valid) - error("pkcs11_provider_unref: %p still valid", p); - free(p->slotlist); - free(p->slotinfo); + if (p->module) + pkcs11_module_unref(p->module); + free(p->name); free(p); } } @@ -141,6 +220,20 @@ pkcs11_terminate(void) } } +/* lookup provider by module path */ +static struct pkcs11_module * +pkcs11_provider_lookup_module(char *module_path) +{ + struct pkcs11_provider *p; + + TAILQ_FOREACH(p, &pkcs11_providers, next) { + debug("check %p %s (%s)", p, p->name, p->module->module_path); + if (!strcmp(module_path, p->module->module_path)) + return (p->module); + } + return (NULL); +} + /* lookup provider by name */ static struct pkcs11_provider * pkcs11_provider_lookup(char *provider_id) @@ -155,19 +248,52 @@ pkcs11_provider_lookup(char *provider_id) return (NULL); } +int pkcs11_del_provider_by_uri(struct pkcs11_uri *); + /* unregister provider by name */ int pkcs11_del_provider(char *provider_id) +{ + int rv; + struct pkcs11_uri *uri; + + debug("%s: called, provider_id = %s", __func__, provider_id); + + uri = pkcs11_uri_init(); + if (uri == NULL) + fatal("Failed to init PCKS#11 URI"); + + if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) && + strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) { + if (pkcs11_uri_parse(provider_id, uri) != 0) + fatal("Failed to parse PKCS#11 URI"); + } else { + uri->module_path = strdup(provider_id); + } + + rv = pkcs11_del_provider_by_uri(uri); + pkcs11_uri_cleanup(uri); + return rv; +} + +/* unregister provider by PKCS#11 URI */ +int +pkcs11_del_provider_by_uri(struct pkcs11_uri *uri) { struct pkcs11_provider *p; + int rv = -1; + char *provider_uri = pkcs11_uri_get(uri); - if ((p = pkcs11_provider_lookup(provider_id)) != NULL) { + debug3("%s(%s): called", __func__, provider_uri); + + if ((p = pkcs11_provider_lookup(provider_uri)) != NULL) { TAILQ_REMOVE(&pkcs11_providers, p, next); pkcs11_provider_finalize(p); pkcs11_provider_unref(p); - return (0); + rv = 0; } - return (-1); + free(provider_uri); + return rv; } /* openssl callback for freeing an RSA key */ @@ -183,6 +309,7 @@ pkcs11_rsa_finish(RSA *rsa) if (k11->provider) pkcs11_provider_unref(k11->provider); free(k11->keyid); + free(k11->label); free(k11); } return (rv); @@ -199,8 +327,8 @@ pkcs11_find(struct pkcs11_provider *p, CK_ULONG slotidx, CK_ATTRIBUTE *attr, CK_RV rv; int ret = -1; - f = p->function_list; - session = p->slotinfo[slotidx].session; + f = p->module->function_list; + session = p->module->slotinfo[slotidx].session; if ((rv = f->C_FindObjectsInit(session, attr, nattr)) != CKR_OK) { error("C_FindObjectsInit failed (nattr %lu): %lu", nattr, rv); return (-1); @@ -247,12 +375,13 @@ pkcs11_rsa_private_encrypt(int flen, const u_char *from, u_char *to, RSA *rsa, error("RSA_get_app_data failed for rsa %p", rsa); return (-1); } - if (!k11->provider || !k11->provider->valid) { + if (!k11->provider || !k11->provider->valid || !k11->provider->module + || !k11->provider->module->valid) { error("no pkcs11 (valid) provider for rsa %p", rsa); return (-1); } - f = k11->provider->function_list; - si = &k11->provider->slotinfo[k11->slotidx]; + f = k11->provider->module->function_list; + si = &k11->provider->module->slotinfo[k11->slotidx]; if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) { if (!pkcs11_interactive) { error("need pin entry%s", (si->token.flags & @@ -311,7 +440,7 @@ pkcs11_rsa_private_decrypt(int flen, const u_char *from, u_char *to, RSA *rsa, /* redirect private key operations for rsa key to pkcs11 token */ static int pkcs11_rsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx, - CK_ATTRIBUTE *keyid_attrib, RSA *rsa) + CK_ATTRIBUTE *keyid_attrib, CK_ATTRIBUTE *label_attrib, RSA *rsa) { struct pkcs11_key *k11; const RSA_METHOD *def = RSA_get_default_method(); @@ -326,6 +455,11 @@ pkcs11_rsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx, k11->keyid = xmalloc(k11->keyid_len); memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len); } + if (label_attrib->ulValueLen > 0 ) { + k11->label = xmalloc(label_attrib->ulValueLen+1); + memcpy(k11->label, label_attrib->pValue, label_attrib->ulValueLen); + k11->label[label_attrib->ulValueLen] = 0; + } k11->orig_finish = def->finish; memcpy(&k11->rsa_method, def, sizeof(k11->rsa_method)); k11->rsa_method.name = "pkcs11"; @@ -372,16 +506,16 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin) CK_SESSION_HANDLE session; int login_required; - f = p->function_list; - login_required = p->slotinfo[slotidx].token.flags & CKF_LOGIN_REQUIRED; + f = p->module->function_list; + login_required = p->module->slotinfo[slotidx].token.flags & CKF_LOGIN_REQUIRED; if (pin && login_required && !strlen(pin)) { error("pin required"); return (-1); } - if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION| + if ((rv = f->C_OpenSession(p->module->slotlist[slotidx], CKF_RW_SESSION| CKF_SERIAL_SESSION, NULL, NULL, &session)) != CKR_OK) { - error("C_OpenSession failed: %lu", rv); + error("C_OpenSession failed for slot %lu: %lu", slotidx, rv); return (-1); } if (login_required && pin) { @@ -393,9 +527,9 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin) error("C_CloseSession failed: %lu", rv); return (-1); } - p->slotinfo[slotidx].logged_in = 1; + p->module->slotinfo[slotidx].logged_in = 1; } - p->slotinfo[slotidx].session = session; + p->module->slotinfo[slotidx].session = session; return (0); } @@ -405,38 +539,62 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin) * keysp points to an (possibly empty) array with *nkeys keys. */ static int pkcs11_fetch_keys_filter(struct pkcs11_provider *, CK_ULONG, - CK_ATTRIBUTE [], CK_ATTRIBUTE [3], struct sshkey ***, int *) + CK_ATTRIBUTE [], size_t, CK_ATTRIBUTE [3], struct sshkey ***, int *) __attribute__((__bounded__(__minbytes__,4, 3 * sizeof(CK_ATTRIBUTE)))); static int pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx, - struct sshkey ***keysp, int *nkeys) + struct sshkey ***keysp, int *nkeys, struct pkcs11_uri *uri) { + size_t filter_size = 1; CK_OBJECT_CLASS pubkey_class = CKO_PUBLIC_KEY; CK_OBJECT_CLASS cert_class = CKO_CERTIFICATE; CK_ATTRIBUTE pubkey_filter[] = { - { CKA_CLASS, NULL, sizeof(pubkey_class) } + { CKA_CLASS, NULL, sizeof(pubkey_class) }, + { CKA_ID, NULL, 0 }, + { CKA_LABEL, NULL, 0 } }; CK_ATTRIBUTE cert_filter[] = { - { CKA_CLASS, NULL, sizeof(cert_class) } + { CKA_CLASS, NULL, sizeof(cert_class) }, + { CKA_ID, NULL, 0 }, + { CKA_LABEL, NULL, 0 } }; CK_ATTRIBUTE pubkey_attribs[] = { { CKA_ID, NULL, 0 }, + { CKA_LABEL, NULL, 0 }, { CKA_MODULUS, NULL, 0 }, { CKA_PUBLIC_EXPONENT, NULL, 0 } }; CK_ATTRIBUTE cert_attribs[] = { { CKA_ID, NULL, 0 }, + { CKA_LABEL, NULL, 0 }, { CKA_SUBJECT, NULL, 0 }, { CKA_VALUE, NULL, 0 } }; pubkey_filter[0].pValue = &pubkey_class; cert_filter[0].pValue = &cert_class; - if (pkcs11_fetch_keys_filter(p, slotidx, pubkey_filter, pubkey_attribs, - keysp, nkeys) < 0 || - pkcs11_fetch_keys_filter(p, slotidx, cert_filter, cert_attribs, - keysp, nkeys) < 0) + if (uri->id != NULL) { + pubkey_filter[filter_size].pValue = uri->id; + pubkey_filter[filter_size].ulValueLen = uri->id_len; + cert_filter[filter_size].pValue = uri->id; + cert_filter[filter_size].ulValueLen = uri->id_len; + filter_size++; + } + if (uri->object != NULL) { + pubkey_filter[filter_size].pValue = uri->object; + pubkey_filter[filter_size].ulValueLen = strlen(uri->object); + pubkey_filter[filter_size].type = CKA_LABEL; + cert_filter[filter_size].pValue = uri->object; + cert_filter[filter_size].ulValueLen = strlen(uri->object); + cert_filter[filter_size].type = CKA_LABEL; + filter_size++; + } + + if (pkcs11_fetch_keys_filter(p, slotidx, pubkey_filter, filter_size, + pubkey_attribs, keysp, nkeys) < 0 || + pkcs11_fetch_keys_filter(p, slotidx, cert_filter, filter_size, + cert_attribs, keysp, nkeys) < 0) return (-1); return (0); } @@ -454,14 +612,15 @@ pkcs11_key_included(struct sshkey ***keysp, int *nkeys, struct sshkey *key) static int pkcs11_fetch_keys_filter(struct pkcs11_provider *p, CK_ULONG slotidx, - CK_ATTRIBUTE filter[], CK_ATTRIBUTE attribs[3], + CK_ATTRIBUTE filter[], size_t filter_size, CK_ATTRIBUTE attribs[4], struct sshkey ***keysp, int *nkeys) { struct sshkey *key; RSA *rsa; X509 *x509; - EVP_PKEY *evp; + EVP_PKEY *evp = NULL; int i; + int nattribs = 4; const u_char *cp; CK_RV rv; CK_OBJECT_HANDLE obj; @@ -470,16 +629,15 @@ pkcs11_fetch_keys_filter(struct pkcs11_provider *p, CK_ULONG slotidx, CK_SESSION_HANDLE session; CK_FUNCTION_LIST *f; - f = p->function_list; - session = p->slotinfo[slotidx].session; + f = p->module->function_list; + session = p->module->slotinfo[slotidx].session; /* setup a filter the looks for public keys */ - if ((rv = f->C_FindObjectsInit(session, filter, 1)) != CKR_OK) { + if ((rv = f->C_FindObjectsInit(session, filter, filter_size)) != CKR_OK) { error("C_FindObjectsInit failed: %lu", rv); return (-1); } while (1) { - /* XXX 3 attributes in attribs[] */ - for (i = 0; i < 3; i++) { + for (i = 0; i < nattribs; i++) { attribs[i].pValue = NULL; attribs[i].ulValueLen = 0; } @@ -487,22 +645,22 @@ pkcs11_fetch_keys_filter(struct pkcs11_provider *p, CK_ULONG slotidx, || nfound == 0) break; /* found a key, so figure out size of the attributes */ - if ((rv = f->C_GetAttributeValue(session, obj, attribs, 3)) + if ((rv = f->C_GetAttributeValue(session, obj, attribs, nattribs)) != CKR_OK) { error("C_GetAttributeValue failed: %lu", rv); continue; } /* - * Allow CKA_ID (always first attribute) to be empty, but - * ensure that none of the others are zero length. + * Allow CKA_ID (always first attribute) and CKA_LABEL (second) + * to be empty, but ensure that none of the others are zero length. * XXX assumes CKA_ID is always first. */ - if (attribs[1].ulValueLen == 0 || - attribs[2].ulValueLen == 0) { + if (attribs[2].ulValueLen == 0 || + attribs[3].ulValueLen == 0) { continue; } /* allocate buffers for attributes */ - for (i = 0; i < 3; i++) { + for (i = 0; i < nattribs; i++) { if (attribs[i].ulValueLen > 0) { attribs[i].pValue = xmalloc( attribs[i].ulValueLen); @@ -510,27 +668,27 @@ pkcs11_fetch_keys_filter(struct pkcs11_provider *p, CK_ULONG slotidx, } /* - * retrieve ID, modulus and public exponent of RSA key, - * or ID, subject and value for certificates. + * retrieve ID, label, modulus and public exponent of RSA key, + * or ID, label, subject and value for certificates. */ rsa = NULL; - if ((rv = f->C_GetAttributeValue(session, obj, attribs, 3)) + if ((rv = f->C_GetAttributeValue(session, obj, attribs, nattribs)) != CKR_OK) { error("C_GetAttributeValue failed: %lu", rv); - } else if (attribs[1].type == CKA_MODULUS ) { + } else if (attribs[2].type == CKA_MODULUS ) { if ((rsa = RSA_new()) == NULL) { error("RSA_new failed"); } else { - rsa->n = BN_bin2bn(attribs[1].pValue, - attribs[1].ulValueLen, NULL); - rsa->e = BN_bin2bn(attribs[2].pValue, + rsa->n = BN_bin2bn(attribs[2].pValue, attribs[2].ulValueLen, NULL); + rsa->e = BN_bin2bn(attribs[3].pValue, + attribs[3].ulValueLen, NULL); } } else { - cp = attribs[2].pValue; + cp = attribs[3].pValue; if ((x509 = X509_new()) == NULL) { error("X509_new failed"); - } else if (d2i_X509(&x509, &cp, attribs[2].ulValueLen) + } else if (d2i_X509(&x509, &cp, attribs[3].ulValueLen) == NULL) { error("d2i_X509 failed"); } else if ((evp = X509_get_pubkey(x509)) == NULL || @@ -546,9 +704,10 @@ pkcs11_fetch_keys_filter(struct pkcs11_provider *p, CK_ULONG slotidx, error("RSAPublicKey_dup"); } X509_free(x509); + EVP_PKEY_free(evp); } if (rsa && rsa->n && rsa->e && - pkcs11_rsa_wrap(p, slotidx, &attribs[0], rsa) == 0) { + pkcs11_rsa_wrap(p, slotidx, &attribs[0], &attribs[1], rsa) == 0) { if ((key = sshkey_new(KEY_UNSPEC)) == NULL) fatal("sshkey_new failed"); key->rsa = rsa; @@ -569,7 +728,7 @@ pkcs11_fetch_keys_filter(struct pkcs11_provider *p, CK_ULONG slotidx, } else if (rsa) { RSA_free(rsa); } - for (i = 0; i < 3; i++) + for (i = 0; i < nattribs; i++) free(attribs[i].pValue); } if ((rv = f->C_FindObjectsFinal(session)) != CKR_OK) @@ -581,126 +740,238 @@ pkcs11_fetch_keys_filter(struct pkcs11_provider *p, CK_ULONG slotidx, int pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp) { - int nkeys, need_finalize = 0; - struct pkcs11_provider *p = NULL; + int rv; + struct pkcs11_uri *uri; + + debug("%s: called, provider_id = %s", __func__, provider_id); + + uri = pkcs11_uri_init(); + if (uri == NULL) + fatal("Failed to init PCKS#11 URI"); + + if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) && + strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) { + if (pkcs11_uri_parse(provider_id, uri) != 0) + fatal("Failed to parse PKCS#11 URI"); + } else { + uri->module_path = strdup(provider_id); + } + + rv = pkcs11_add_provider_by_uri(uri, pin, keyp); + pkcs11_uri_cleanup(uri); + return rv; +} + +struct pkcs11_provider * +pkcs11_provider_initialize(struct pkcs11_uri *uri) +{ + int need_finalize = 0; void *handle = NULL; CK_RV (*getfunctionlist)(CK_FUNCTION_LIST **); CK_RV rv; CK_FUNCTION_LIST *f = NULL; CK_TOKEN_INFO *token; CK_ULONG i; + char *provider_module = NULL; + struct pkcs11_provider *p; + struct pkcs11_module *m; - *keyp = NULL; - if (pkcs11_provider_lookup(provider_id) != NULL) { - debug("%s: provider already registered: %s", - __func__, provider_id); + /* if no provider specified, fallback to p11-kit */ + if (uri->module_path == NULL) { +#ifdef PKCS11_DEFAULT_PROVIDER + provider_module = strdup(PKCS11_DEFAULT_PROVIDER); +#else + error("%s: No module path provided", __func__); goto fail; +#endif + } else + provider_module = strdup(uri->module_path); + + p = xcalloc(1, sizeof(*p)); + p->name = pkcs11_uri_get(uri); + + if ((m = pkcs11_provider_lookup_module(provider_module)) != NULL + && m->valid) { + debug("%s: provider module already initialized: %s", + __func__, provider_module); + free(provider_module); + /* Skip the initialization of PKCS#11 module */ + m->refcount++; + p->module = m; + p->valid = 1; + TAILQ_INSERT_TAIL(&pkcs11_providers, p, next); + p->refcount++; /* add to provider list */ + return p; + } else { + m = xcalloc(1, sizeof(*m)); + p->module = m; + m->refcount++; } + /* open shared pkcs11-libarary */ - if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) { - error("dlopen %s failed: %s", provider_id, dlerror()); + if ((handle = dlopen(provider_module, RTLD_NOW)) == NULL) { + error("dlopen %s failed: %s", provider_module, dlerror()); goto fail; } if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL) { error("dlsym(C_GetFunctionList) failed: %s", dlerror()); goto fail; } - p = xcalloc(1, sizeof(*p)); - p->name = xstrdup(provider_id); - p->handle = handle; + m->handle = handle; /* setup the pkcs11 callbacks */ if ((rv = (*getfunctionlist)(&f)) != CKR_OK) { error("C_GetFunctionList for provider %s failed: %lu", - provider_id, rv); + provider_module, rv); goto fail; } - p->function_list = f; + m->function_list = f; if ((rv = f->C_Initialize(NULL)) != CKR_OK) { error("C_Initialize for provider %s failed: %lu", - provider_id, rv); + provider_module, rv); goto fail; } need_finalize = 1; - if ((rv = f->C_GetInfo(&p->info)) != CKR_OK) { + if ((rv = f->C_GetInfo(&m->info)) != CKR_OK) { error("C_GetInfo for provider %s failed: %lu", - provider_id, rv); + provider_module, rv); goto fail; } - rmspace(p->info.manufacturerID, sizeof(p->info.manufacturerID)); - rmspace(p->info.libraryDescription, sizeof(p->info.libraryDescription)); + rmspace(m->info.manufacturerID, sizeof(m->info.manufacturerID)); + if (uri->lib_manuf != NULL && + strcmp(uri->lib_manuf, m->info.manufacturerID)) { + debug("%s: Skipping provider %s not matching library_manufacturer", + __func__, m->info.manufacturerID); + goto fail; + } + rmspace(m->info.libraryDescription, sizeof(m->info.libraryDescription)); debug("provider %s: manufacturerID <%s> cryptokiVersion %d.%d" " libraryDescription <%s> libraryVersion %d.%d", - provider_id, - p->info.manufacturerID, - p->info.cryptokiVersion.major, - p->info.cryptokiVersion.minor, - p->info.libraryDescription, - p->info.libraryVersion.major, - p->info.libraryVersion.minor); - if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) { + provider_module, + m->info.manufacturerID, + m->info.cryptokiVersion.major, + m->info.cryptokiVersion.minor, + m->info.libraryDescription, + m->info.libraryVersion.major, + m->info.libraryVersion.minor); + + if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &m->nslots)) != CKR_OK) { error("C_GetSlotList failed: %lu", rv); goto fail; } - if (p->nslots == 0) { + if (m->nslots == 0) { debug("%s: provider %s returned no slots", __func__, - provider_id); + provider_module); goto fail; } - p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID)); - if ((rv = f->C_GetSlotList(CK_TRUE, p->slotlist, &p->nslots)) + m->slotlist = xcalloc(m->nslots, sizeof(CK_SLOT_ID)); + if ((rv = f->C_GetSlotList(CK_TRUE, m->slotlist, &m->nslots)) != CKR_OK) { error("C_GetSlotList for provider %s failed: %lu", - provider_id, rv); + provider_module, rv); goto fail; } - p->slotinfo = xcalloc(p->nslots, sizeof(struct pkcs11_slotinfo)); + m->slotinfo = xcalloc(m->nslots, sizeof(struct pkcs11_slotinfo)); + m->valid = 1; p->valid = 1; - nkeys = 0; - for (i = 0; i < p->nslots; i++) { - token = &p->slotinfo[i].token; - if ((rv = f->C_GetTokenInfo(p->slotlist[i], token)) + + for (i = 0; i < m->nslots; i++) { + token = &m->slotinfo[i].token; + if ((rv = f->C_GetTokenInfo(m->slotlist[i], token)) != CKR_OK) { error("C_GetTokenInfo for provider %s slot %lu " - "failed: %lu", provider_id, (unsigned long)i, rv); + "failed: %lu", provider_module, (unsigned long)i, rv); continue; } if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) { - debug2("%s: ignoring uninitialised token in " - "provider %s slot %lu", __func__, - provider_id, (unsigned long)i); continue; } rmspace(token->label, sizeof(token->label)); rmspace(token->manufacturerID, sizeof(token->manufacturerID)); rmspace(token->model, sizeof(token->model)); rmspace(token->serialNumber, sizeof(token->serialNumber)); + } + m->module_path = provider_module; + provider_module = NULL; + + /* insert unconditionally -- remove if there will be no keys later */ + TAILQ_INSERT_TAIL(&pkcs11_providers, p, next); + p->refcount++; /* add to provider list */ + return p; + +fail: + if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK) + error("C_Finalize for provider %s failed: %lu", + provider_module, rv); + free(provider_module); + free(p); + if (handle) + dlclose(handle); + return NULL; +} + +int +pkcs11_add_provider_by_uri(struct pkcs11_uri *uri, char *pin, struct sshkey ***keyp) +{ + int nkeys; + struct pkcs11_provider *p = NULL; + CK_TOKEN_INFO *token; + CK_ULONG i; + char *provider_uri = pkcs11_uri_get(uri); + + debug("%s: called, provider_uri = %s", __func__, provider_uri); + + *keyp = NULL; + if ((p = pkcs11_provider_initialize(uri)) == NULL) { + debug("%s: failed to initialize provider: %s", + __func__, provider_uri); + goto fail; + } + + nkeys = 0; + for (i = 0; i < p->module->nslots; i++) { + token = &p->module->slotinfo[i].token; + if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) { + debug2("%s: ignoring uninitialised token in " + "provider %s slot %lu", __func__, + provider_uri, (unsigned long)i); + continue; + } + if (uri->token != NULL && + strcmp(token->label, uri->token) != 0) { + debug2("%s: ignoring token not matching label (%s) " + "specified by PKCS#11 URI in slot %lu", __func__, + token->label, (unsigned long)i); + continue; + } + if (uri->manuf != NULL && + strcmp(token->manufacturerID, uri->manuf) != 0) { + debug2("%s: ignoring token not matching requrested " + "manufacturerID (%s) specified by PKCS#11 URI in " + "slot %lu", __func__, + token->manufacturerID, (unsigned long)i); + continue; + } debug("provider %s slot %lu: label <%s> manufacturerID <%s> " "model <%s> serial <%s> flags 0x%lx", - provider_id, (unsigned long)i, + provider_uri, (unsigned long)i, token->label, token->manufacturerID, token->model, token->serialNumber, token->flags); - /* open session, login with pin and retrieve public keys */ - if (pkcs11_open_session(p, i, pin) == 0) - pkcs11_fetch_keys(p, i, keyp, &nkeys); + /* open session if not yet opened, login with pin + * and retrieve public keys */ + if ((p->module->slotinfo[i].session != 0) || + pkcs11_open_session(p, i, pin) == 0) + pkcs11_fetch_keys(p, i, keyp, &nkeys, uri); } if (nkeys > 0) { - TAILQ_INSERT_TAIL(&pkcs11_providers, p, next); - p->refcount++; /* add to provider list */ + free(provider_uri); return (nkeys); } - debug("%s: provider %s returned no keys", __func__, provider_id); + debug("%s: provider %s returned no keys", __func__, provider_uri); /* don't add the provider, since it does not have any keys */ fail: - if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK) - error("C_Finalize for provider %s failed: %lu", - provider_id, rv); if (p) { - free(p->slotlist); - free(p->slotinfo); - free(p); + pkcs11_provider_unref(p); } - if (handle) - dlclose(handle); + free(provider_uri); return (-1); } diff --git a/ssh-pkcs11.h b/ssh-pkcs11.h index 0ced74f2..c63a88f6 100644 --- a/ssh-pkcs11.h +++ b/ssh-pkcs11.h @@ -14,10 +14,15 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include "ssh-pkcs11-uri.h" + int pkcs11_init(int); void pkcs11_terminate(void); int pkcs11_add_provider(char *, char *, struct sshkey ***); +int pkcs11_add_provider_by_uri(struct pkcs11_uri *, char *, struct sshkey ***); int pkcs11_del_provider(char *); +int pkcs11_uri_write(const struct sshkey *, FILE *); #if !defined(WITH_OPENSSL) && defined(ENABLE_PKCS11) #undef ENABLE_PKCS11 diff --git a/ssh.c b/ssh.c index d3619fe2..180eb2e0 100644 --- a/ssh.c +++ b/ssh.c @@ -769,6 +769,14 @@ main(int ac, char **av) options.gss_deleg_creds = 1; break; case 'i': +#ifdef ENABLE_PKCS11 + if (strlen(optarg) >= strlen(PKCS11_URI_SCHEME) && + strncmp(optarg, PKCS11_URI_SCHEME, + strlen(PKCS11_URI_SCHEME)) == 0) { + add_identity_file(&options, NULL, optarg, 1); + break; + } +#endif p = tilde_expand_filename(optarg, original_real_uid); if (stat(p, &st) < 0) fprintf(stderr, "Warning: Identity file %s " @@ -1999,6 +2007,45 @@ ssh_session2(struct ssh *ssh, struct passwd *pw) options.escape_char : SSH_ESCAPECHAR_NONE, id); } +#ifdef ENABLE_PKCS11 +static void +load_pkcs11_identity(char *pkcs11_uri, char *identity_files[], + struct sshkey *identity_keys[], int *n_ids) +{ + int nkeys, i; + struct sshkey **keys; + struct pkcs11_uri *uri; + + debug("identity file '%s' from pkcs#11", pkcs11_uri); + uri = pkcs11_uri_init(); + if (uri == NULL) + fatal("Failed to init PCKS#11 URI"); + + if (pkcs11_uri_parse(pkcs11_uri, uri) != 0) + fatal("Failed to parse PKCS#11 URI %s", pkcs11_uri); + + /* we need to merge URI and provider together */ + if (options.pkcs11_provider != NULL && uri->module_path == NULL) + uri->module_path = strdup(options.pkcs11_provider); + + if (options.num_identity_files < SSH_MAX_IDENTITY_FILES && + (nkeys = pkcs11_add_provider_by_uri(uri, NULL, &keys)) > 0) { + for (i = 0; i < nkeys; i++) { + if (*n_ids >= SSH_MAX_IDENTITY_FILES) { + key_free(keys[i]); + continue; + } + identity_keys[*n_ids] = keys[i]; + identity_files[*n_ids] = pkcs11_uri_get(uri); + (*n_ids)++; + } + free(keys); + } + + pkcs11_uri_cleanup(uri); +} +#endif /* ENABLE_PKCS11 */ + /* Loads all IdentityFile and CertificateFile keys */ static void load_public_identity_files(struct passwd *pw) @@ -2011,10 +2058,6 @@ load_public_identity_files(struct passwd *pw) struct sshkey *identity_keys[SSH_MAX_IDENTITY_FILES]; char *certificate_files[SSH_MAX_CERTIFICATE_FILES]; struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES]; -#ifdef ENABLE_PKCS11 - struct sshkey **keys; - int nkeys; -#endif /* PKCS11 */ n_ids = n_certs = 0; memset(identity_files, 0, sizeof(identity_files)); @@ -2023,35 +2066,48 @@ load_public_identity_files(struct passwd *pw) memset(certificates, 0, sizeof(certificates)); #ifdef ENABLE_PKCS11 - if (options.pkcs11_provider != NULL && - options.num_identity_files < SSH_MAX_IDENTITY_FILES && - (pkcs11_init(!options.batch_mode) == 0) && - (nkeys = pkcs11_add_provider(options.pkcs11_provider, NULL, - &keys)) > 0) { - for (i = 0; i < nkeys; i++) { - if (n_ids >= SSH_MAX_IDENTITY_FILES) { - key_free(keys[i]); - continue; - } - identity_keys[n_ids] = keys[i]; - identity_files[n_ids] = - xstrdup(options.pkcs11_provider); /* XXX */ - n_ids++; - } - free(keys); + /* handle fallback from PKCS11Provider option */ + pkcs11_init(!options.batch_mode); + + if (options.pkcs11_provider != NULL) { + struct pkcs11_uri *uri; + + uri = pkcs11_uri_init(); + if (uri == NULL) + fatal("Failed to init PCKS#11 URI"); + + /* Construct simple PKCS#11 URI to simplify access */ + uri->module_path = strdup(options.pkcs11_provider); + + /* Add it as any other IdentityFile */ + cp = pkcs11_uri_get(uri); + add_identity_file(&options, NULL, cp, 1); + free(cp); + + pkcs11_uri_cleanup(uri); } #endif /* ENABLE_PKCS11 */ if ((pw = getpwuid(original_real_uid)) == NULL) fatal("load_public_identity_files: getpwuid failed"); for (i = 0; i < options.num_identity_files; i++) { + char *name = options.identity_files[i]; if (n_ids >= SSH_MAX_IDENTITY_FILES || - strcasecmp(options.identity_files[i], "none") == 0) { + strcasecmp(name, "none") == 0) { free(options.identity_files[i]); options.identity_files[i] = NULL; continue; } - cp = tilde_expand_filename(options.identity_files[i], - original_real_uid); +#ifdef ENABLE_PKCS11 + if (strlen(name) >= strlen(PKCS11_URI_SCHEME) && + strncmp(name, PKCS11_URI_SCHEME, + strlen(PKCS11_URI_SCHEME)) == 0) { + load_pkcs11_identity(name, identity_files, + identity_keys, &n_ids); + free(options.identity_files[i]); + continue; + } +#endif /* ENABLE_PKCS11 */ + cp = tilde_expand_filename(name, original_real_uid); filename = percent_expand(cp, "d", pw->pw_dir, "u", pw->pw_name, "l", thishost, "h", host, "r", options.user, (char *)NULL); diff --git a/ssh_config.5 b/ssh_config.5 index 71705cab..e0266609 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -919,6 +919,19 @@ may also be used in conjunction with .Cm CertificateFile in order to provide any certificate also needed for authentication with the identity. +.Pp +The authentication identity can be also specified in a form of PKCS#11 URI +starting with a string +.Cm pkcs11: . +There is supported a subset of the PKCS#11 URI as defined +in RFC 7512 (implemented path arguments +.Cm id , +.Cm manufacturer , +.Cm object , +.Cm token +and query argument +.Cm module-path +). The URI can not be in quotes. .It Cm IgnoreUnknown Specifies a pattern-list of unknown options to be ignored if they are encountered in configuration parsing.