URL: https://github.com/SSSD/sssd/pull/698 Author: sumit-bose Title: #698: Add support for EC keys Action: opened
PR body: """ This patch set adds support for elliptic curve (EC) keys to p11_child and the ssh key extraction code. Related to https://pagure.io/SSSD/sssd/issue/3887 """ To pull the PR as Git branch: git remote add ghsssd https://github.com/SSSD/sssd git fetch ghsssd pull/698/head:pr698 git checkout pr698
From 215dfe16eb67760b5a5fd211f4d9a09c88529419 Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Fri, 9 Nov 2018 13:34:33 +0100 Subject: [PATCH 1/9] p11_child(NSS): print key type in a debug message NSS can handle EC keys automatically but a debug message indicating which key type is used might be useful. Related to https://pagure.io/SSSD/sssd/issue/3887 --- src/p11_child/p11_child_nss.c | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/p11_child/p11_child_nss.c b/src/p11_child/p11_child_nss.c index f9cbf3f37a..d3064ff98d 100644 --- a/src/p11_child/p11_child_nss.c +++ b/src/p11_child/p11_child_nss.c @@ -477,6 +477,40 @@ bool do_verification_b64(struct p11_ctx *p11_ctx, const char *cert_b64) return res; } +static const char *keytype2str(KeyType keyType) { + switch (keyType) { + case nullKey: + return "nullKey"; + break; + case rsaKey: + return "rsaKey"; + break; + case dsaKey: + return "dsaKey"; + break; + case fortezzaKey: + return "fortezzaKey"; + break; + case dhKey: + return "dhKey"; + break; + case keaKey: + return "keaKey"; + break; + case ecKey: + return "ecKey"; + break; + case rsaPssKey: + return "rsaPssKey"; + break; + case rsaOaepKey: + return "rsaOaepKey"; + break; + default: + return "Unknown key type"; + } +} + errno_t do_card(TALLOC_CTX *mem_ctx, struct p11_ctx *p11_ctx, enum op_mode mode, const char *pin, const char *module_name_in, const char *token_name_in, @@ -798,6 +832,8 @@ errno_t do_card(TALLOC_CTX *mem_ctx, struct p11_ctx *p11_ctx, goto done; } + DEBUG(SSSDBG_TRACE_ALL, "Private key has type [%s].\n", + keytype2str(priv_key->keyType)); algtag = SEC_GetSignatureAlgorithmOidTag(priv_key->keyType, SEC_OID_SHA1); if (algtag == SEC_OID_UNKNOWN) { From ec92509e28328758542ae21d4b1819e9bee6a1dc Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Fri, 9 Nov 2018 14:04:38 +0100 Subject: [PATCH 2/9] pam_test_srv: set default value for SOFTHSM2_CONF Currently the SOFTHSM2_CONF is not set by any fixture but some tests sets them and other might rely on the setting done by a previous test. This means that the tests have to run in a given order and depend on each other. To remove this dependency SOFTHSM2_CONF is set in the fixture to the "default" SoftHSM2 configuration with one valid certificate. Any test which needs a different setup must now set SOFTHSM2_CONF explicitly. Related to https://pagure.io/SSSD/sssd/issue/3887 --- src/tests/cmocka/test_pam_srv.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c index 7fc9224e15..b299612555 100644 --- a/src/tests/cmocka/test_pam_srv.c +++ b/src/tests/cmocka/test_pam_srv.c @@ -356,6 +356,10 @@ static void pam_test_setup_common(void) { errno_t ret; +#ifndef HAVE_NSS + putenv(discard_const("SOFTHSM2_CONF=" ABS_BUILD_DIR "/src/tests/test_CA/softhsm2_one.conf")); +#endif + pam_test_ctx->pam_user_fqdn = \ sss_create_internal_fqname(pam_test_ctx, "pamuser", @@ -1926,6 +1930,7 @@ void test_pam_preauth_cert_nocert(void **state) set_cert_auth_param(pam_test_ctx->pctx, "/no/path"); #else set_cert_auth_param(pam_test_ctx->pctx, CA_DB); + unsetenv("SOFTHSM2_CONF"); #endif From 833ae697bb4a0c4ed808905320362bb6bf754937 Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Fri, 9 Nov 2018 14:06:03 +0100 Subject: [PATCH 3/9] tests: add ECC CA To be able to test certificates with elliptic curve (EC) keys a new test CA with this kind of keys is added. Related to https://pagure.io/SSSD/sssd/issue/3887 --- Makefile.am | 6 +- configure.ac | 1 + src/tests/test_ECC_CA/Makefile.am | 95 +++++++++++++++++++ src/tests/test_ECC_CA/SSSD_test_ECC_CA.config | 47 +++++++++ .../test_ECC_CA/SSSD_test_ECC_CA_key.pem | 9 ++ .../SSSD_test_ECC_cert_0001.config | 20 ++++ .../SSSD_test_ECC_cert_key_0001.pem | 9 ++ 7 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 src/tests/test_ECC_CA/Makefile.am create mode 100644 src/tests/test_ECC_CA/SSSD_test_ECC_CA.config create mode 100644 src/tests/test_ECC_CA/SSSD_test_ECC_CA_key.pem create mode 100644 src/tests/test_ECC_CA/SSSD_test_ECC_cert_0001.config create mode 100644 src/tests/test_ECC_CA/SSSD_test_ECC_cert_key_0001.pem diff --git a/Makefile.am b/Makefile.am index 3667856c68..430506028f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -21,7 +21,8 @@ if HAVE_MANPAGES SUBDIRS += src/man endif -SUBDIRS += . src/tests/cwrap src/tests/intg src/tests/test_CA +SUBDIRS += . src/tests/cwrap src/tests/intg src/tests/test_CA \ + src/tests/test_ECC_CA # Some old versions of automake don't define builddir builddir ?= . @@ -5394,8 +5395,9 @@ CLEANFILES += *.X */*.X */*/*.X test_CA: test_CA.stamp -test_CA.stamp: $(srcdir)/src/tests/test_CA/* +test_CA.stamp: $(srcdir)/src/tests/test_CA/* $(srcdir)/src/tests/test_ECC_CA/* $(MAKE) -C src/tests/test_CA ca_all + $(MAKE) -C src/tests/test_ECC_CA ca_all touch $@ if BUILD_TEST_CA diff --git a/configure.ac b/configure.ac index 5816b04c66..fb01a7c3b1 100644 --- a/configure.ac +++ b/configure.ac @@ -521,6 +521,7 @@ AC_CONFIG_FILES([Makefile contrib/sssd.spec src/examples/rwtab src/doxy.config src/sysv/sssd src/sysv/gentoo/sssd src/sysv/SUSE/sssd po/Makefile.in src/man/Makefile src/tests/cwrap/Makefile src/tests/intg/Makefile src/tests/test_CA/Makefile + src/tests/test_ECC_CA/Makefile src/lib/ipa_hbac/ipa_hbac.pc src/lib/ipa_hbac/ipa_hbac.doxy src/lib/idmap/sss_idmap.pc src/lib/idmap/sss_idmap.doxy src/lib/certmap/sss_certmap.pc src/lib/certmap/sss_certmap.doxy diff --git a/src/tests/test_ECC_CA/Makefile.am b/src/tests/test_ECC_CA/Makefile.am new file mode 100644 index 0000000000..47af991c3a --- /dev/null +++ b/src/tests/test_ECC_CA/Makefile.am @@ -0,0 +1,95 @@ +dist_noinst_DATA = \ + SSSD_test_ECC_CA.config \ + SSSD_test_ECC_CA_key.pem \ + SSSD_test_ECC_cert_0001.config \ + SSSD_test_ECC_cert_key_0001.pem + +openssl_ecc_ca_config = $(srcdir)/SSSD_test_ECC_CA.config +openssl_ecc_ca_key = $(srcdir)/SSSD_test_ECC_CA_key.pem +pwdfile = pwdfile + +configs := $(notdir $(wildcard $(srcdir)/SSSD_test_ECC_cert_*.config)) +ids := $(subst SSSD_test_ECC_cert_,,$(basename $(configs))) +certs = $(addprefix SSSD_test_ECC_cert_x509_,$(addsuffix .pem,$(ids))) +certs_h = $(addprefix SSSD_test_ECC_cert_x509_,$(addsuffix .h,$(ids))) +pubkeys = $(addprefix SSSD_test_ECC_cert_pubsshkey_,$(addsuffix .pub,$(ids))) +pubkeys_h = $(addprefix SSSD_test_ECC_cert_pubsshkey_,$(addsuffix .h,$(ids))) +pkcs12 = $(addprefix SSSD_test_ECC_cert_pkcs12_,$(addsuffix .pem,$(ids))) + +if HAVE_NSS +extra = p11_ecc_nssdb +else +extra = softhsm2_ecc_one p11_ecc_nssdb +endif + +# If openssl is run in parallel there might be conflicts with the serial +.NOTPARALLEL: + +ca_all: clean serial SSSD_test_ECC_CA.pem $(certs) $(certs_h) $(pubkeys) $(pubkeys_h) $(pkcs12) $(extra) + +$(pwdfile): + @echo "123456" > $@ + +SSSD_test_ECC_CA.pem: $(openssl_ecc_ca_key) $(openssl_ecc_ca_config) serial + $(OPENSSL) req -batch -config ${openssl_ecc_ca_config} -x509 -new -nodes -key $< -sha384 -days 1024 -set_serial 0 -extensions v3_ca -out $@ + + +SSSD_test_ECC_cert_req_%.pem: $(srcdir)/SSSD_test_ECC_cert_key_%.pem $(srcdir)/SSSD_test_ECC_cert_%.config + $(OPENSSL) req -new -nodes -key $< -reqexts req_exts -config $(srcdir)/SSSD_test_ECC_cert_$*.config -out $@ + +SSSD_test_ECC_cert_x509_%.pem: SSSD_test_ECC_cert_req_%.pem $(openssl_ecc_ca_config) SSSD_test_ECC_CA.pem + $(OPENSSL) ca -config ${openssl_ecc_ca_config} -batch -notext -keyfile $(openssl_ecc_ca_key) -in $< -days 200 -extensions usr_cert -out $@ + +SSSD_test_ECC_cert_pkcs12_%.pem: SSSD_test_ECC_cert_x509_%.pem $(srcdir)/SSSD_test_ECC_cert_key_%.pem $(pwdfile) + $(OPENSSL) pkcs12 -export -in SSSD_test_ECC_cert_x509_$*.pem -inkey $(srcdir)/SSSD_test_ECC_cert_key_$*.pem -nodes -passout file:$(pwdfile) -out $@ + +SSSD_test_ECC_cert_pubkey_%.pem: SSSD_test_ECC_cert_x509_%.pem + $(OPENSSL) x509 -in $< -pubkey -noout > $@ + +SSSD_test_ECC_cert_pubsshkey_%.pub: SSSD_test_ECC_cert_pubkey_%.pem + $(SSH_KEYGEN) -i -m PKCS8 -f $< > $@ + +SSSD_test_ECC_cert_x509_%.h: SSSD_test_ECC_cert_x509_%.pem + @echo "#define SSSD_TEST_ECC_CERT_$* \""$(shell cat $< |openssl x509 -outform der | base64 -w 0)"\"" > $@ + +SSSD_test_ECC_cert_pubsshkey_%.h: SSSD_test_ECC_cert_pubsshkey_%.pub + @echo "#define SSSD_TEST_ECC_CERT_SSH_KEY_$* \""$(shell cut -d' ' -f2 $<)"\"" > $@ + + +p11_ecc_nssdb: SSSD_test_ECC_cert_pkcs12_0001.pem SSSD_test_ECC_CA.pem $(pwdfile) + mkdir $@ + $(CERTUTIL) -d sql:./$@ -N -f $(pwdfile) + $(CERTUTIL) -d sql:./$@ -A -n 'SSSD test ECC CA' -t CT,CT,CT -a -i SSSD_test_ECC_CA.pem -f $(pwdfile) + $(PK12UTIL) -d sql:./$@ -i SSSD_test_ECC_cert_pkcs12_0001.pem -w $(pwdfile) -k $(pwdfile) + + +softhsm2_ecc_one: softhsm2_ecc_one.conf + mkdir $@ + SOFTHSM2_CONF=./$< $(SOFTHSM2_UTIL) --init-token --label "SSSD Test ECC Token" --pin 123456 --so-pin 123456 --free + GNUTLS_PIN=123456 SOFTHSM2_CONF=./$< $(P11TOOL) --provider=$(SOFTHSM2_PATH) --write --no-mark-private --load-certificate=SSSD_test_ECC_cert_x509_0001.pem --login --label 'SSSD test ECC cert 0001' --id '190E513C9A3DFAACDE5D2D0592F0FDFF559C10CB' + GNUTLS_PIN=123456 SOFTHSM2_CONF=./$< $(P11TOOL) --provider=$(SOFTHSM2_PATH) --write --load-privkey=$(srcdir)/SSSD_test_ECC_cert_key_0001.pem --login --label 'SSSD test ECC cert 0001' --id '190E513C9A3DFAACDE5D2D0592F0FDFF559C10CB' + +softhsm2_ecc_one.conf: + @echo "directories.tokendir = "$(abs_top_builddir)"/src/tests/test_ECC_CA/softhsm2_ecc_one" > $@ + @echo "objectstore.backend = file" >> $@ + @echo "slots.removable = true" >> $@ + +CLEANFILES = \ + index.txt index.txt.attr \ + index.txt.attr.old index.txt.old \ + serial serial.old \ + SSSD_test_ECC_CA.pem $(pwdfile) \ + $(certs) $(certs_h) $(pubkeys) $(pubkeys_h) $(pkcs12) \ + softhsm2_*.conf \ + $(NULL) + +clean-local: + rm -rf newcerts + rm -rf p11_ecc_nssdb + rm -rf softhsm* + +serial: clean + touch index.txt + touch index.txt.attr + mkdir newcerts + echo -n 01 > serial diff --git a/src/tests/test_ECC_CA/SSSD_test_ECC_CA.config b/src/tests/test_ECC_CA/SSSD_test_ECC_CA.config new file mode 100644 index 0000000000..c1e4e22a6c --- /dev/null +++ b/src/tests/test_ECC_CA/SSSD_test_ECC_CA.config @@ -0,0 +1,47 @@ +[ ca ] +default_ca = ECC_CA_default + +[ ECC_CA_default ] +dir = . +database = $dir/index.txt +new_certs_dir = $dir/newcerts + +certificate = $dir/SSSD_test_ECC_CA.pem +serial = $dir/serial +private_key = $dir/SSSD_test_ECC_CA_key.pem +RANDFILE = $dir/rand + +default_days = 365 +default_crl_days = 30 +default_md = sha256 + +policy = policy_any +email_in_dn = no + +name_opt = ca_default +cert_opt = ca_default +copy_extensions = copy + +[ usr_cert ] +authorityKeyIdentifier = keyid, issuer + +[ v3_ca ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +basicConstraints = CA:true +keyUsage = critical, digitalSignature, cRLSign, keyCertSign + +[ policy_any ] +organizationName = supplied +organizationalUnitName = supplied +commonName = supplied +emailAddress = optional + +[ req ] +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] +O = SSSD +OU = SSSD test +CN = SSSD test ECC CA diff --git a/src/tests/test_ECC_CA/SSSD_test_ECC_CA_key.pem b/src/tests/test_ECC_CA/SSSD_test_ECC_CA_key.pem new file mode 100644 index 0000000000..c5cb3ef429 --- /dev/null +++ b/src/tests/test_ECC_CA/SSSD_test_ECC_CA_key.pem @@ -0,0 +1,9 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDBKk+ue3IyidXo3+befiqrcKrpVpy/pWz9CMTIALHMBc/a83Q3h9yEB +CNpdsF8B2zegBwYFK4EEACKhZANiAAR/mCPIYxyT4tbjgpJT+oKCGfGjfs3FVnRr +GLnNnT/L2b9PACMjjugM/1RNOuLdzRFBVWlQ80ISH5w17R2uhbiDJ/Q254Ele4Ak +5e2nR/9x0ZIAqc05tkBDhsXfJ3id3/0= +-----END EC PRIVATE KEY----- diff --git a/src/tests/test_ECC_CA/SSSD_test_ECC_cert_0001.config b/src/tests/test_ECC_CA/SSSD_test_ECC_cert_0001.config new file mode 100644 index 0000000000..17c9192d44 --- /dev/null +++ b/src/tests/test_ECC_CA/SSSD_test_ECC_cert_0001.config @@ -0,0 +1,20 @@ +# This certificate is used in +# - src/tests/cmocka/test_cert_utils.c +# - src/tests/cmocka/test_pam_srv.c +[ req ] +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] +O = SSSD +OU = SSSD test ECC +CN = SSSD test ECC cert 0001 + +[ req_exts ] +basicConstraints = CA:FALSE +nsCertType = client, email +nsComment = "SSSD test Certificate" +subjectKeyIdentifier = hash +keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, emailProtection +subjectAltName = email:sssd-devel@lists.fedorahosted.org,URI:https://pagure.io/SSSD/sssd// diff --git a/src/tests/test_ECC_CA/SSSD_test_ECC_cert_key_0001.pem b/src/tests/test_ECC_CA/SSSD_test_ECC_cert_key_0001.pem new file mode 100644 index 0000000000..8c9321048b --- /dev/null +++ b/src/tests/test_ECC_CA/SSSD_test_ECC_cert_key_0001.pem @@ -0,0 +1,9 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDDVZu1S6+U+1Fs1eAn/6O1iX7LH2w4AaToxqutXtkrdEpuTX7SZskTQ +UCL0Lf5oQjigBwYFK4EEACKhZANiAAQheZFBntzcARA52Gba7c01BElFRds1F439 +KotFOoDx4fJf67hmD69bKuTbWLvc7l3Lf2TKdI5GCp/u9SPhGtve0CaYm9Hcoxwp +2yYnhq3stoW+far//4h3mQxU/hG9pj0= +-----END EC PRIVATE KEY----- From a3f99af2bb37ee72876a9ffd50df17502f6cfaea Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Fri, 9 Nov 2018 14:01:20 +0100 Subject: [PATCH 4/9] test_pam_srv: add test for certificate with EC keys Add an authentication test with a certificate with EC keys. Related to https://pagure.io/SSSD/sssd/issue/3887 --- src/tests/cmocka/test_pam_srv.c | 114 ++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c index b299612555..f55e6222ea 100644 --- a/src/tests/cmocka/test_pam_srv.c +++ b/src/tests/cmocka/test_pam_srv.c @@ -42,9 +42,13 @@ #ifdef HAVE_TEST_CA #include "tests/test_CA/SSSD_test_cert_x509_0001.h" #include "tests/test_CA/SSSD_test_cert_x509_0002.h" + +#include "tests/test_ECC_CA/SSSD_test_ECC_cert_x509_0001.h" #else #define SSSD_TEST_CERT_0001 "" #define SSSD_TEST_CERT_0002 "" + +#define SSSD_TEST_ECC_CERT_0001 "" #endif #define TESTS_PATH "tp_" BASE_FILE_STEM @@ -58,10 +62,16 @@ #define NSS_DB_PATH_2CERTS TESTS_PATH "_2certs" #define NSS_DB_2CERTS "sql:"NSS_DB_PATH_2CERTS + +#define NSS_DB_PATH_ECC TESTS_PATH "_ecc" +#define NSS_DB_ECC "sql:"NSS_DB_PATH_ECC + #ifdef HAVE_NSS #define CA_DB NSS_DB +#define ECC_CA_DB NSS_DB_ECC #else #define CA_DB ABS_BUILD_DIR"/src/tests/test_CA/SSSD_test_CA.pem" +#define ECC_CA_DB ABS_BUILD_DIR"/src/tests/test_ECC_CA/SSSD_test_ECC_CA.pem" #endif #define TEST_TOKEN_NAME "SSSD Test Token" @@ -122,6 +132,13 @@ static errno_t setup_nss_db(void) return ret; } + ret = mkdir(NSS_DB_PATH_ECC, 0775); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to create " NSS_DB_PATH_ECC ".\n"); + return ret; + } + child_pid = fork(); if (child_pid == 0) { /* child */ ret = execlp("certutil", "certutil", "-N", "--empty-password", "-d", @@ -154,6 +171,22 @@ static errno_t setup_nss_db(void) return ret; } + child_pid = fork(); + if (child_pid == 0) { /* child */ + ret = execlp("certutil", "certutil", "-N", "--empty-password", "-d", + NSS_DB_ECC, NULL); + if (ret == -1) { + DEBUG(SSSDBG_FATAL_FAILURE, "execl() failed.\n"); + exit(-1); + } + } else if (child_pid > 0) { + wait(&status); + } else { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, "fork() failed\n"); + return ret; + } + fp = fopen(NSS_DB_PATH"/pkcs11.txt", "w"); if (fp == NULL) { DEBUG(SSSDBG_FATAL_FAILURE, "fopen() failed.\n"); @@ -196,6 +229,27 @@ static errno_t setup_nss_db(void) return ret; } + fp = fopen(NSS_DB_PATH_ECC"/pkcs11.txt", "w"); + if (fp == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "fopen() failed.\n"); + return ret; + } + ret = fprintf(fp, "library=libsoftokn3.so\nname=soft\n"); + if (ret < 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "fprintf() failed.\n"); + return ret; + } + ret = fprintf(fp, "parameters=configdir='sql:%s/src/tests/test_ECC_CA/p11_ecc_nssdb' dbSlotDescription='SSSD Test ECC Slot' dbTokenDescription='SSSD Test ECC Token' secmod='secmod.db' flags=readOnly \n\n", ABS_BUILD_DIR); + if (ret < 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "fprintf() failed.\n"); + return ret; + } + ret = fclose(fp); + if (ret != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "fclose() failed.\n"); + return ret; + } + return EOK; } @@ -242,6 +296,26 @@ static void cleanup_nss_db(void) if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to remove " NSS_DB_PATH "\n"); } + + ret = unlink(NSS_DB_PATH_ECC"/cert9.db"); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to remove cert9.db.\n"); + } + + ret = unlink(NSS_DB_PATH_ECC"/key4.db"); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to remove key4.db.\n"); + } + + ret = unlink(NSS_DB_PATH_ECC"/pkcs11.txt"); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to remove pkcs11.db.\n"); + } + + ret = rmdir(NSS_DB_PATH_ECC); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to remove " NSS_DB_PATH "\n"); + } } struct pam_ctx *mock_pctx(TALLOC_CTX *mem_ctx) @@ -2347,6 +2421,44 @@ void test_pam_cert_auth(void **state) assert_int_equal(ret, EOK); } +void test_pam_ecc_cert_auth(void **state) +{ + int ret; + +#ifndef HAVE_NSS + putenv(discard_const("SOFTHSM2_CONF=" ABS_BUILD_DIR "/src/tests/test_ECC_CA/softhsm2_ecc_one.conf")); +#endif + set_cert_auth_param(pam_test_ctx->pctx, ECC_CA_DB); + + /* Here the last option must be set to true because the backend is only + * connected once. During authentication the backend is connected first to + * see if it can handle Smartcard authentication, but before that the user + * is looked up. Since the first mocked reply already adds the certificate + * to the user entry the lookup by certificate will already find the user + * in the cache and no second request to the backend is needed. */ + mock_input_pam_cert(pam_test_ctx, "pamuser", "123456", + "SSSD Test ECC Token", + TEST_MODULE_NAME, + "190E513C9A3DFAACDE5D2D0592F0FDFF559C10CB", NULL, + test_lookup_by_cert_cb, SSSD_TEST_ECC_CERT_0001, true); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + + /* Assume backend cannot handle Smartcard credentials */ + pam_test_ctx->exp_pam_status = PAM_BAD_ITEM; + + + set_cmd_cb(test_pam_simple_check_success); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + void test_pam_cert_auth_no_logon_name(void **state) { int ret; @@ -3022,6 +3134,8 @@ int main(int argc, const char *argv[]) cmocka_unit_test_setup_teardown(test_pam_cert_auth, pam_test_setup_no_verification, pam_test_teardown), + cmocka_unit_test_setup_teardown(test_pam_ecc_cert_auth, + pam_test_setup, pam_test_teardown), cmocka_unit_test_setup_teardown(test_pam_cert_auth_double_cert, pam_test_setup, pam_test_teardown), cmocka_unit_test_setup_teardown(test_pam_cert_preauth_2certs_one_mapping, From f779cb90469f4d5f4ede906a5e576c740dd27d00 Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Fri, 9 Nov 2018 14:01:46 +0100 Subject: [PATCH 5/9] p11_child(openssl): add support for EC keys Add support for EC keys to the OpenSSL version of p11_child. Please see comments in the code for some technical details. Related to https://pagure.io/SSSD/sssd/issue/3887 --- src/p11_child/p11_child_openssl.c | 319 +++++++++++++++++++++++++++++- 1 file changed, 309 insertions(+), 10 deletions(-) diff --git a/src/p11_child/p11_child_openssl.c b/src/p11_child/p11_child_openssl.c index af55523a79..0f8ba3d3cc 100644 --- a/src/p11_child/p11_child_openssl.c +++ b/src/p11_child/p11_child_openssl.c @@ -137,6 +137,7 @@ static OCSP_RESPONSE *query_responder(BIO *cbio, const char *host, #define X509_STORE_get0_objects(store) (store->objs) #define X509_OBJECT_get_type(object) (object->type) #define X509_OBJECT_get0_X509(object) (object->data.x509) +#define EVP_MD_CTX_free EVP_MD_CTX_destroy #endif OCSP_RESPONSE *process_responder(OCSP_REQUEST *req, @@ -860,6 +861,243 @@ static int read_certs(TALLOC_CTX *mem_ctx, CK_FUNCTION_LIST *module, return ret; } +/* Currently this funtion is only used the print the curve type in the debug + * messages. */ +static void get_ec_curve_type(CK_FUNCTION_LIST *module, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key_handle) +{ + CK_ATTRIBUTE attribute; + CK_RV rv; + EC_GROUP *ec_group; + const unsigned char *p; + int len; + char der_buf[128]; /* FIXME: any other size ?? */ + char oid_buf[128]; /* FIXME: any other size ?? */ + + attribute.type = CKA_ECDSA_PARAMS; + attribute.pValue = &der_buf; + attribute.ulValueLen = sizeof(der_buf); + + rv = module->C_GetAttributeValue(session, key_handle, &attribute, 1); + if (rv != CKR_OK) { + free(attribute.pValue); + DEBUG(SSSDBG_OP_FAILURE, + "C_GetAttributeValue failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return; + } + + p = (const unsigned char *) attribute.pValue; + ec_group = d2i_ECPKParameters(NULL, &p, attribute.ulValueLen); + len = OBJ_obj2txt(oid_buf, sizeof(oid_buf), + OBJ_nid2obj(EC_GROUP_get_curve_name(ec_group)), 1); + DEBUG(SSSDBG_TRACE_ALL, "Curve name [%s][%s][%.*s].\n", + OBJ_nid2sn(EC_GROUP_get_curve_name(ec_group)), + OBJ_nid2ln(EC_GROUP_get_curve_name(ec_group)), + len, oid_buf); + + return; +} + +static CK_KEY_TYPE get_key_type(CK_FUNCTION_LIST *module, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key_handle) +{ + CK_ATTRIBUTE attribute; + CK_RV rv; + CK_KEY_TYPE type; + + attribute.type = CKA_KEY_TYPE; + attribute.pValue = &type; + attribute.ulValueLen = sizeof(CK_KEY_TYPE); + + rv = module->C_GetAttributeValue(session, key_handle, &attribute, 1); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "C_GetAttributeValue failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return CK_UNAVAILABLE_INFORMATION; + } + + if (attribute.ulValueLen == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Key type attribute cannot be read.\n"); + return CK_UNAVAILABLE_INFORMATION; + } + + if (type == CKK_EC && DEBUG_IS_SET(SSSDBG_TRACE_ALL)) { + get_ec_curve_type(module, session, key_handle); + } + + return type; +} + +static int do_hash(TALLOC_CTX *mem_ctx, const EVP_MD *evp_md, + CK_BYTE *in, size_t in_len, + CK_BYTE **hash, size_t *hash_len) +{ + EVP_MD_CTX *md_ctx = NULL; + int ret; + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned int md_len; + CK_BYTE *out = NULL; + + md_ctx = EVP_MD_CTX_create(); + if (md_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_MD_CTX_create failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = EVP_DigestInit(md_ctx, evp_md); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_DigestInit failed.\n"); + ret = EINVAL; + goto done; + } + + ret = EVP_DigestUpdate(md_ctx, in, in_len); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_DigestUpdate failed.\n"); + ret = EINVAL; + goto done; + } + + ret = EVP_DigestFinal_ex(md_ctx, md_value, &md_len); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_DigestFinal failed.\n"); + ret = EINVAL; + goto done; + } + + out = talloc_size(mem_ctx, md_len * sizeof(CK_BYTE)); + if (out == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + ret = ENOMEM; + goto done; + } + + memcpy(out, md_value, md_len); + + *hash = out; + *hash_len = md_len; + + ret = EOK; + +done: + + if (ret != EOK) { + free(out); + EVP_MD_CTX_free(md_ctx); + } + + return ret; +} + +/* A ECDSA signature consists of 2 integer values r and s. According to the + * "PKCS #11 Cryptographic Token Interface Current Mechanisms Specification": + * + * """ + * For the purposes of these mechanisms, an ECDSA signature is an octet string + * of even length which is at most two times nLen octets, where nLen is the + * length in octets of the base point order n. The signature octets correspond + * to the concatenation of the ECDSA values r and s, both represented as an + * octet string of equal length of at most nLen with the most significant byte + * first. If r and s have different octet length, the shorter of both must be + * padded with leading zero octets such that both have the same octet length. + * Loosely spoken, the first half of the signature is r and the second half is + * s. For signatures created by a token, the resulting signature is always of + * length 2nLen. + * """ + * + * Unfortunately OpenSSL expects the 2 integer values r and s DER encoded as + * specified in X9.62 "Public Key Cryptography For The Financial Services + * Industry: The Elliptic Curve Digital Signature Algorithm (ECDSA)": + * + * """ + * When a digital signature is identified by the OID ecdsa-with-SHA1 , the + * digital signature shall be ASN.1 encoded using the following syntax: + * ECDSA-Sig-Value ::= SEQUENCE { + * r INTEGER, + * s INTEGER + * } + * """ + * + * The following function translates from the PKCS#11 to the X9.62 format by + * manually creating the DER sequence after splitting the PKCS#11 signature. + * Since r and s are positive values we have to make sure that the leading + * bit is not set in the DER encoding by prepending a 0-byte if needed. + */ +static int rs_to_seq(TALLOC_CTX *mem_ctx, CK_BYTE *rs_sig, CK_ULONG rs_sig_len, + CK_BYTE **seq_sig, CK_ULONG *seq_sig_len) +{ + CK_BYTE *r; + size_t r_len; + CK_BYTE *s; + size_t s_len; + size_t r_add = 0; + size_t s_add = 0; + CK_BYTE *out; + size_t out_len; + + if (rs_sig_len % 2 != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected signature size [%lu].\n", + rs_sig_len); + return EINVAL; + } + + r_len = s_len = rs_sig_len / 2; + r = rs_sig; + s = rs_sig + r_len; + + /* Remove padding */ + while(r_len > 1 && *r == 0x00) { + r++; + r_len--; + } + while(s_len > 1 && *s == 0x00) { + s++; + s_len--; + } + + /* r and s are positive, check if the highest bit is set which would + * indicate a negative value. In this case a 0x00 must be added. */ + if ( *r & 0x80 ) { + r_add = 1; + } + if ( *s & 0x80 ) { + s_add = 1; + } + + out_len = r_len + r_add + s_len + s_add + 6; + out = talloc_size(mem_ctx, out_len * sizeof(CK_BYTE)); + if (out == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + out[0] = 0x30; + out[1] = (CK_BYTE) (out_len - 2); + out[2] = 0x02; + out[3] = (CK_BYTE) (r_len + r_add); + if (r_add == 1) { + out[4] = 0x00; + } + memcpy(&out[4 + r_add], r, r_len); + out[4 + r_add + r_len] = 0x02; + out[5 + r_add + r_len] = (CK_BYTE) (s_len + s_add); + if (s_add == 1) { + out[6 + r_add + r_len] = 0x00; + } + memcpy(&out[6 + r_add + r_len + s_add], s, s_len); + + *seq_sig = out; + *seq_sig_len = out_len; + + return EOK; +} + static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, struct cert_list *cert) { @@ -870,17 +1108,25 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, {CKA_SIGN, &key_sign, sizeof(key_sign)}, {CKA_ID, NULL, 0} }; - CK_MECHANISM mechanism = { CKM_SHA1_RSA_PKCS, NULL, 0 }; + CK_MECHANISM mechanism = { CK_UNAVAILABLE_INFORMATION, NULL, 0 }; CK_OBJECT_HANDLE priv_key_object; CK_ULONG object_count; CK_BYTE random_value[128]; CK_BYTE *signature = NULL; CK_ULONG signature_size = 0; + CK_BYTE *seq_sig = NULL; + CK_ULONG seq_sig_size = 0; CK_RV rv; CK_RV rv_f; EVP_PKEY *cert_pub_key = NULL; EVP_MD_CTX *md_ctx; int ret; + const EVP_MD *evp_md = NULL; + CK_BYTE *hash_val = NULL; + size_t hash_len = 0; + CK_BYTE *val_to_sign = NULL; + size_t val_to_sign_len = 0; + bool card_does_hash = false; key_template[2].pValue = cert->attributes[ATTR_ID].pValue; key_template[2].ulValueLen = cert->attributes[ATTR_ID].ulValueLen; @@ -910,9 +1156,31 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, return EINVAL; } + switch (get_key_type(module, session, priv_key_object)) { + case CKK_RSA: + DEBUG(SSSDBG_TRACE_ALL, "Found RSA key using CKM_SHA1_RSA_PKCS.\n"); + mechanism.mechanism = CKM_SHA1_RSA_PKCS; + evp_md = EVP_sha1(); + card_does_hash = true; + break; + case CKK_EC: + DEBUG(SSSDBG_TRACE_ALL, "Found ECC key using CKM_ECDSA.\n"); + mechanism.mechanism = CKM_ECDSA; + evp_md = EVP_sha1(); + card_does_hash = false; + break; + case CK_UNAVAILABLE_INFORMATION: + DEBUG(SSSDBG_CRIT_FAILURE, "get_key_type failed.\n"); + return EIO; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported key type.\n"); + return EIO; + } + rv = module->C_SignInit(session, &mechanism, priv_key_object); if (rv != CKR_OK) { - DEBUG(SSSDBG_OP_FAILURE, "C_SignInit failed [%lu][%s].", + DEBUG(SSSDBG_OP_FAILURE, "C_SignInit failed [%lu][%s].\n", rv, p11_kit_strerror(rv)); return EIO; } @@ -923,7 +1191,22 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, return EINVAL; } - rv = module->C_Sign(session, random_value, sizeof(random_value), NULL, + if (card_does_hash) { + val_to_sign = random_value; + val_to_sign_len = sizeof(random_value); + } else { + ret = do_hash(cert, evp_md, random_value, sizeof(random_value), + &hash_val, &hash_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "do_hash failed.\n"); + return ret; + } + + val_to_sign = hash_val; + val_to_sign_len = hash_len; + } + + rv = module->C_Sign(session, val_to_sign, val_to_sign_len, NULL, &signature_size); if (rv != CKR_OK || signature_size == 0) { DEBUG(SSSDBG_OP_FAILURE, "C_Sign failed [%lu][%s].\n", @@ -937,7 +1220,7 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, return ENOMEM; } - rv = module->C_Sign(session, random_value, sizeof(random_value), signature, + rv = module->C_Sign(session, val_to_sign, val_to_sign_len, signature, &signature_size); if (rv != CKR_OK) { DEBUG(SSSDBG_OP_FAILURE, "C_Sign failed [%lu][%s].\n", @@ -958,7 +1241,7 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, ret = ENOMEM; goto done; } - ret = EVP_VerifyInit(md_ctx, EVP_sha1()); + ret = EVP_VerifyInit(md_ctx, evp_md); if (ret != 1) { DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyInit failed.\n"); ret = EINVAL; @@ -972,11 +1255,27 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, goto done; } - ret = EVP_VerifyFinal(md_ctx, signature, signature_size, cert_pub_key); - if (ret != 1) { - DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyFinal failed.\n"); - ret = EINVAL; - goto done; + if (mechanism.mechanism == CKM_ECDSA) { + ret = rs_to_seq(signature, signature, signature_size, + &seq_sig, &seq_sig_size); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "rs_to_seq failed.\n"); + goto done; + } + + ret = EVP_VerifyFinal(md_ctx, seq_sig, seq_sig_size, cert_pub_key); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyFinal failed.\n"); + ret = EINVAL; + goto done; + } + } else { + ret = EVP_VerifyFinal(md_ctx, signature, signature_size, cert_pub_key); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyFinal failed.\n"); + ret = EINVAL; + goto done; + } } ret = EOK; From eb33e959e10a8f0de8cb3660cead05c75dac586f Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Wed, 14 Nov 2018 15:02:33 +0100 Subject: [PATCH 6/9] utils: refactor ssh key extraction (OpenSSL) Prepare the current code to allow adding other key types. Related to https://pagure.io/SSSD/sssd/issue/3887 --- src/util/cert/libcrypto/cert.c | 87 +++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/src/util/cert/libcrypto/cert.c b/src/util/cert/libcrypto/cert.c index c8e07837f1..d925c5c5b2 100644 --- a/src/util/cert/libcrypto/cert.c +++ b/src/util/cert/libcrypto/cert.c @@ -171,17 +171,13 @@ errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem, #define SSH_RSA_HEADER "ssh-rsa" #define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1) -errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, - const uint8_t *der_blob, size_t der_size, - uint8_t **key_blob, size_t *key_size) +static errno_t rsa_pub_key_to_ssh(TALLOC_CTX *mem_ctx, EVP_PKEY *cert_pub_key, + uint8_t **key_blob, size_t *key_size) { int ret; + size_t c; size_t size; - const unsigned char *d; uint8_t *buf = NULL; - size_t c; - X509 *cert = NULL; - EVP_PKEY *cert_pub_key = NULL; const BIGNUM *n; const BIGNUM *e; int modulus_len; @@ -189,33 +185,6 @@ errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, int exponent_len; unsigned char exponent[OPENSSL_RSA_MAX_PUBEXP_BITS/8]; - if (der_blob == NULL || der_size == 0) { - return EINVAL; - } - - d = (const unsigned char *) der_blob; - - cert = d2i_X509(NULL, &d, (int) der_size); - if (cert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "d2i_X509 failed.\n"); - return EINVAL; - } - - cert_pub_key = X509_get_pubkey(cert); - if (cert_pub_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "X509_get_pubkey failed.\n"); - ret = EIO; - goto done; - } - - if (EVP_PKEY_base_id(cert_pub_key) != EVP_PKEY_RSA) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Expected RSA public key, found unsupported [%d].\n", - EVP_PKEY_base_id(cert_pub_key)); - ret = EINVAL; - goto done; - } - #if OPENSSL_VERSION_NUMBER >= 0x10100000L RSA *rsa_pub_key = NULL; rsa_pub_key = EVP_PKEY_get0_RSA(cert_pub_key); @@ -268,6 +237,56 @@ errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, if (ret != EOK) { talloc_free(buf); } + + return ret; +} + +errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, + const uint8_t *der_blob, size_t der_size, + uint8_t **key_blob, size_t *key_size) +{ + int ret; + const unsigned char *d; + X509 *cert = NULL; + EVP_PKEY *cert_pub_key = NULL; + + if (der_blob == NULL || der_size == 0) { + return EINVAL; + } + + d = (const unsigned char *) der_blob; + + cert = d2i_X509(NULL, &d, (int) der_size); + if (cert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "d2i_X509 failed.\n"); + return EINVAL; + } + + cert_pub_key = X509_get_pubkey(cert); + if (cert_pub_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "X509_get_pubkey failed.\n"); + ret = EIO; + goto done; + } + + switch (EVP_PKEY_base_id(cert_pub_key)) { + case EVP_PKEY_RSA: + ret = rsa_pub_key_to_ssh(mem_ctx, cert_pub_key, key_blob, key_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "rsa_pub_key_to_ssh failed.\n"); + goto done; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected RSA public key, found unsupported [%d].\n", + EVP_PKEY_base_id(cert_pub_key)); + ret = EINVAL; + goto done; + } + +done: + EVP_PKEY_free(cert_pub_key); X509_free(cert); From 65d06a63cc571b00e3788203e2acf529325d69a0 Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Wed, 14 Nov 2018 21:13:53 +0100 Subject: [PATCH 7/9] utils: add ec_pub_key_to_ssh() (OpenSSL) Add EC key support for the OpenSSL version of the ssh key extraction code. Related to https://pagure.io/SSSD/sssd/issue/3887 --- src/tests/cmocka/test_cert_utils.c | 70 ++++++++++++++++ src/util/cert/libcrypto/cert.c | 126 ++++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 1 deletion(-) diff --git a/src/tests/cmocka/test_cert_utils.c b/src/tests/cmocka/test_cert_utils.c index 26fffb870c..9273356ebe 100644 --- a/src/tests/cmocka/test_cert_utils.c +++ b/src/tests/cmocka/test_cert_utils.c @@ -40,11 +40,15 @@ #include "tests/test_CA/SSSD_test_cert_x509_0001.h" #include "tests/test_CA/SSSD_test_cert_pubsshkey_0002.h" #include "tests/test_CA/SSSD_test_cert_x509_0002.h" +#include "tests/test_ECC_CA/SSSD_test_ECC_cert_pubsshkey_0001.h" +#include "tests/test_ECC_CA/SSSD_test_ECC_cert_x509_0001.h" #else #define SSSD_TEST_CERT_0001 "" #define SSSD_TEST_CERT_SSH_KEY_0001 "" #define SSSD_TEST_CERT_0002 "" #define SSSD_TEST_CERT_SSH_KEY_0002 "" +#define SSSD_TEST_ECC_CERT_0001 "" +#define SSSD_TEST_ECC_CERT_SSH_KEY_0001 "" #endif /* When run under valgrind with --trace-children=yes we have to increase the @@ -564,6 +568,70 @@ void test_cert_to_ssh_2keys_invalid_send(void **state) talloc_free(ev); } +void test_ec_cert_to_ssh_key_done(struct tevent_req *req) +{ + int ret; + struct test_state *ts = tevent_req_callback_data(req, struct test_state); + struct ldb_val *keys; + uint8_t *exp_key; + size_t exp_key_size; + size_t valid_keys; + + assert_non_null(ts); + ts->done = true; + + ret = cert_to_ssh_key_recv(req, ts, &keys, &valid_keys); + talloc_free(req); + assert_int_equal(ret, 0); + assert_non_null(keys[0].data); + assert_int_equal(valid_keys, 1); + + exp_key = sss_base64_decode(ts, SSSD_TEST_ECC_CERT_SSH_KEY_0001, + &exp_key_size); + assert_non_null(exp_key); + assert_int_equal(keys[0].length, exp_key_size); + assert_memory_equal(keys[0].data, exp_key, exp_key_size); + + talloc_free(exp_key); + talloc_free(keys); +} + +void test_ec_cert_to_ssh_key_send(void **state) +{ + struct tevent_context *ev; + struct tevent_req *req; + struct ldb_val val[1]; + + struct test_state *ts = talloc_get_type_abort(*state, struct test_state); + assert_non_null(ts); + ts->done = false; + + val[0].data = sss_base64_decode(ts, SSSD_TEST_ECC_CERT_0001, + &val[0].length); + assert_non_null(val[0].data); + + ev = tevent_context_init(ts); + assert_non_null(ev); + + req = cert_to_ssh_key_send(ts, ev, -1, P11_CHILD_TIMEOUT, +#ifdef HAVE_NSS + "sql:" ABS_BUILD_DIR "/src/tests/test_ECC_CA/p11_ecc_nssdb", +#else + ABS_BUILD_DIR "/src/tests/test_ECC_CA/SSSD_test_ECC_CA.pem", +#endif + 1, &val[0], NULL); + assert_non_null(req); + + tevent_req_set_callback(req, test_ec_cert_to_ssh_key_done, ts); + + while (!ts->done) { + tevent_loop_once(ev); + } + + talloc_free(val[0].data); + talloc_free(ev); +} + int main(int argc, const char *argv[]) { poptContext pc; @@ -595,6 +663,8 @@ int main(int argc, const char *argv[]) setup, teardown), cmocka_unit_test_setup_teardown(test_cert_to_ssh_2keys_invalid_send, setup, teardown), + cmocka_unit_test_setup_teardown(test_ec_cert_to_ssh_key_send, + setup, teardown), #endif }; diff --git a/src/util/cert/libcrypto/cert.c b/src/util/cert/libcrypto/cert.c index d925c5c5b2..acca07dd04 100644 --- a/src/util/cert/libcrypto/cert.c +++ b/src/util/cert/libcrypto/cert.c @@ -168,6 +168,123 @@ errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem, } +/* SSH EC keys are defined in https://tools.ietf.org/html/rfc5656 */ +#define ECDSA_SHA2_HEADER "ecdsa-sha2-" +/* Looks like OpenSSH currently only supports the following 3 required + * curves. */ +#define IDENTIFIER_NISTP256 "nistp256" +#define IDENTIFIER_NISTP384 "nistp384" +#define IDENTIFIER_NISTP521 "nistp521" + +static errno_t ec_pub_key_to_ssh(TALLOC_CTX *mem_ctx, EVP_PKEY *cert_pub_key, + uint8_t **key_blob, size_t *key_size) +{ + int ret; + size_t c; + uint8_t *buf = NULL; + size_t buf_len; + EC_KEY *ec_key = NULL; + const EC_GROUP *ec_group = NULL; + const EC_POINT *ec_public_key = NULL; + BN_CTX *bn_ctx = NULL; + int key_len; + const char *identifier = NULL; + int identifier_len; + const char *header = NULL; + int header_len; + + ec_key = EVP_PKEY_get1_EC_KEY(cert_pub_key); + if (ec_key == NULL) { + ret = ENOMEM; + goto done; + } + + ec_group = EC_KEY_get0_group(ec_key); + + switch(EC_GROUP_get_curve_name(ec_group)) { + case NID_X9_62_prime256v1: + identifier = IDENTIFIER_NISTP256; + header = ECDSA_SHA2_HEADER IDENTIFIER_NISTP256; + break; + case NID_secp384r1: + identifier = IDENTIFIER_NISTP384; + header = ECDSA_SHA2_HEADER IDENTIFIER_NISTP384; + break; + case NID_secp521r1: + identifier = IDENTIFIER_NISTP521; + header = ECDSA_SHA2_HEADER IDENTIFIER_NISTP521; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported curve [%s]\n", + OBJ_nid2sn(EC_GROUP_get_curve_name(ec_group))); + ret = EINVAL; + goto done; + } + + header_len = strlen(header); + identifier_len = strlen(identifier); + + ec_public_key = EC_KEY_get0_public_key(ec_key); + + bn_ctx = BN_CTX_new(); + if (bn_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "BN_CTX_new failed.\n"); + ret = ENOMEM; + goto done; + } + + key_len = EC_POINT_point2oct(ec_group, ec_public_key, + POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bn_ctx); + if (key_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "EC_POINT_point2oct failed.\n"); + ret = EINVAL; + goto done; + } + + buf_len = header_len + identifier_len + key_len + 3 * sizeof(uint32_t); + buf = talloc_size(mem_ctx, buf_len * sizeof(uint8_t)); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + ret = ENOMEM; + goto done; + } + + c = 0; + + SAFEALIGN_SET_UINT32(buf, htobe32(header_len), &c); + safealign_memcpy(&buf[c], header, header_len, &c); + + SAFEALIGN_SET_UINT32(&buf[c], htobe32(identifier_len), &c); + safealign_memcpy(&buf[c], identifier , identifier_len, &c); + + SAFEALIGN_SET_UINT32(&buf[c], htobe32(key_len), &c); + + if (EC_POINT_point2oct(ec_group, ec_public_key, + POINT_CONVERSION_UNCOMPRESSED, buf + c, key_len, + bn_ctx) + != key_len) { + DEBUG(SSSDBG_OP_FAILURE, "EC_POINT_point2oct failed.\n"); + ret = EINVAL; + goto done; + } + + *key_size = buf_len; + *key_blob = buf; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(buf); + } + + BN_CTX_free(bn_ctx); + EC_KEY_free(ec_key); + + return ret; +} + + #define SSH_RSA_HEADER "ssh-rsa" #define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1) @@ -277,9 +394,16 @@ errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, goto done; } break; + case EVP_PKEY_EC: + ret = ec_pub_key_to_ssh(mem_ctx, cert_pub_key, key_blob, key_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "rsa_pub_key_to_ssh failed.\n"); + goto done; + } + break; default: DEBUG(SSSDBG_CRIT_FAILURE, - "Expected RSA public key, found unsupported [%d].\n", + "Expected RSA or EC public key, found unsupported [%d].\n", EVP_PKEY_base_id(cert_pub_key)); ret = EINVAL; goto done; From 7304b8fe0ab4f39b807517cf552dea110d812f5a Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Fri, 16 Nov 2018 17:31:00 +0100 Subject: [PATCH 8/9] utils: refactor ssh key extraction (NSS) Prepare the current code to allow adding other key types. Related to https://pagure.io/SSSD/sssd/issue/3887 --- src/util/cert/nss/cert.c | 110 +++++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/src/util/cert/nss/cert.c b/src/util/cert/nss/cert.c index a8efef818a..b5c4769a87 100644 --- a/src/util/cert/nss/cert.c +++ b/src/util/cert/nss/cert.c @@ -223,14 +223,10 @@ errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem, #define SSH_RSA_HEADER "ssh-rsa" #define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1) -errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, - uint8_t *der_blob, size_t der_size, - uint8_t **key_blob, size_t *key_size) +static errno_t rsa_pub_key_to_ssh(TALLOC_CTX *mem_ctx, + SECKEYPublicKey *cert_pub_key, + uint8_t **key_blob, size_t *key_size) { - CERTCertDBHandle *handle; - CERTCertificate *cert = NULL; - SECItem der_item; - SECKEYPublicKey *cert_pub_key = NULL; int ret; size_t size; uint8_t *buf = NULL; @@ -238,44 +234,6 @@ errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, size_t exponent_prefix_len; size_t modulus_prefix_len; - if (der_blob == NULL || der_size == 0) { - return EINVAL; - } - - /* initialize NSS if needed */ - ret = nspr_nss_init(); - if (ret != EOK) { - ret = EIO; - goto done; - } - - handle = CERT_GetDefaultCertDB(); - - der_item.len = der_size; - der_item.data = discard_const(der_blob); - - cert = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE); - if (cert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n"); - ret = EINVAL; - goto done; - } - - cert_pub_key = CERT_ExtractPublicKey(cert); - if (cert_pub_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "CERT_ExtractPublicKey failed.\n"); - ret = EIO; - goto done; - } - - if (cert_pub_key->keyType != rsaKey) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Expected RSA public key, found unsupported [%d].\n", - cert_pub_key->keyType); - ret = EINVAL; - goto done; - } - /* Looks like nss drops the leading 00 which AFAIK is added to make sure * the bigint is handled as positive number if the leading bit is set. */ exponent_prefix_len = 0; @@ -330,6 +288,68 @@ errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, if (ret != EOK) { talloc_free(buf); } + + return ret; +} + +errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, + uint8_t *der_blob, size_t der_size, + uint8_t **key_blob, size_t *key_size) +{ + CERTCertDBHandle *handle; + CERTCertificate *cert = NULL; + SECItem der_item; + SECKEYPublicKey *cert_pub_key = NULL; + int ret; + + if (der_blob == NULL || der_size == 0) { + return EINVAL; + } + + /* initialize NSS if needed */ + ret = nspr_nss_init(); + if (ret != EOK) { + ret = EIO; + goto done; + } + + handle = CERT_GetDefaultCertDB(); + + der_item.len = der_size; + der_item.data = discard_const(der_blob); + + cert = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE); + if (cert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n"); + ret = EINVAL; + goto done; + } + + cert_pub_key = CERT_ExtractPublicKey(cert); + if (cert_pub_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "CERT_ExtractPublicKey failed.\n"); + ret = EIO; + goto done; + } + + switch (cert_pub_key->keyType) { + case rsaKey: + ret = rsa_pub_key_to_ssh(mem_ctx, cert_pub_key, key_blob, key_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "rsa_pub_key_to_ssh failed.\n"); + goto done; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected RSA public key, found unsupported [%d].\n", + cert_pub_key->keyType); + ret = EINVAL; + goto done; + } + +done: + SECKEY_DestroyPublicKey(cert_pub_key); CERT_DestroyCertificate(cert); From 1db14432c48b10e3f68ac1f2031026b7942c8377 Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Fri, 16 Nov 2018 18:15:32 +0100 Subject: [PATCH 9/9] utils: add ec_pub_key_to_ssh() (NSS) Add EC key support for the NSS version of the ssh key extraction code. Related to https://pagure.io/SSSD/sssd/issue/3887 --- src/util/cert/nss/cert.c | 121 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/src/util/cert/nss/cert.c b/src/util/cert/nss/cert.c index b5c4769a87..ad90da0dab 100644 --- a/src/util/cert/nss/cert.c +++ b/src/util/cert/nss/cert.c @@ -220,6 +220,118 @@ errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem, return ret; } +/* taken from NSS's lib/cryptohi/seckey.c */ +static SECOidTag +sss_SECKEY_GetECCOid(const SECKEYECParams *params) +{ + SECItem oid = { siBuffer, NULL, 0 }; + SECOidData *oidData = NULL; + + /* + * params->data needs to contain the ASN encoding of an object ID (OID) + * representing a named curve. Here, we strip away everything + * before the actual OID and use the OID to look up a named curve. + */ + if (params->data[0] != SEC_ASN1_OBJECT_ID) + return 0; + oid.len = params->len - 2; + oid.data = params->data + 2; + if ((oidData = SECOID_FindOID(&oid)) == NULL) + return 0; + + return oidData->offset; +} + +/* SSH EC keys are defined in https://tools.ietf.org/html/rfc5656 */ +#define ECDSA_SHA2_HEADER "ecdsa-sha2-" +/* Looks like OpenSSH currently only supports the following 3 required + * curves. */ +#define IDENTIFIER_NISTP256 "nistp256" +#define IDENTIFIER_NISTP384 "nistp384" +#define IDENTIFIER_NISTP521 "nistp521" + +static errno_t ec_pub_key_to_ssh(TALLOC_CTX *mem_ctx, + SECKEYPublicKey *cert_pub_key, + uint8_t **key_blob, size_t *key_size) +{ + int ret; + size_t c; + uint8_t *buf = NULL; + size_t buf_len; + SECOidTag curve_tag; + int key_len; + const char *identifier = NULL; + int identifier_len; + const char *header = NULL; + int header_len; + SECItem *ec_public_key; + + curve_tag = sss_SECKEY_GetECCOid(&cert_pub_key->u.ec.DEREncodedParams); + switch(curve_tag) { + case SEC_OID_ANSIX962_EC_PRIME256V1: + identifier = IDENTIFIER_NISTP256; + header = ECDSA_SHA2_HEADER IDENTIFIER_NISTP256; + break; + case SEC_OID_SECG_EC_SECP384R1: + identifier = IDENTIFIER_NISTP384; + header = ECDSA_SHA2_HEADER IDENTIFIER_NISTP384; + break; + case SEC_OID_SECG_EC_SECP521R1: + identifier = IDENTIFIER_NISTP521; + header = ECDSA_SHA2_HEADER IDENTIFIER_NISTP521; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported curve [%s]\n", + SECOID_FindOIDTagDescription(curve_tag)); + ret = EINVAL; + goto done; + } + + header_len = strlen(header); + identifier_len = strlen(identifier); + + ec_public_key = &cert_pub_key->u.ec.publicValue; + + key_len = ec_public_key->len; + if (key_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "EC_POINT_point2oct failed.\n"); + ret = EINVAL; + goto done; + } + + buf_len = header_len + identifier_len + key_len + 3 * sizeof(uint32_t); + buf = talloc_size(mem_ctx, buf_len * sizeof(uint8_t)); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + ret = ENOMEM; + goto done; + } + + c = 0; + + SAFEALIGN_SET_UINT32(buf, htobe32(header_len), &c); + safealign_memcpy(&buf[c], header, header_len, &c); + + SAFEALIGN_SET_UINT32(&buf[c], htobe32(identifier_len), &c); + safealign_memcpy(&buf[c], identifier , identifier_len, &c); + + SAFEALIGN_SET_UINT32(&buf[c], htobe32(key_len), &c); + + safealign_memcpy(&buf[c], ec_public_key->data, key_len, &c); + + *key_size = buf_len; + *key_blob = buf; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(buf); + } + + return ret; +} + #define SSH_RSA_HEADER "ssh-rsa" #define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1) @@ -340,9 +452,16 @@ errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, goto done; } break; + case ecKey: + ret = ec_pub_key_to_ssh(mem_ctx, cert_pub_key, key_blob, key_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "rsa_pub_key_to_ssh failed.\n"); + goto done; + } + break; default: DEBUG(SSSDBG_CRIT_FAILURE, - "Expected RSA public key, found unsupported [%d].\n", + "Expected RSA or EC public key, found unsupported [%d].\n", cert_pub_key->keyType); ret = EINVAL; goto done;
_______________________________________________ sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org Fedora Code of Conduct: https://getfedora.org/code-of-conduct.html List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/sssd-devel@lists.fedorahosted.org