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

Reply via email to