diff options
author | Sumit Bose <sbose@redhat.com> | 2015-07-15 09:40:00 +0200 |
---|---|---|
committer | Jakub Hrozek <jhrozek@redhat.com> | 2015-07-31 09:52:06 +0200 |
commit | 4de84af23db74e13e867985c9093f394c9fa8d51 (patch) | |
tree | 62ed6590bbad5200b47b5fa8662d96639c2a8e4d | |
parent | 5242964d275d0b2e96c9b0d1f8a9958c85d566fc (diff) | |
download | sssd-4de84af23db74e13e867985c9093f394c9fa8d51.tar.gz sssd-4de84af23db74e13e867985c9093f394c9fa8d51.tar.xz sssd-4de84af23db74e13e867985c9093f394c9fa8d51.zip |
ssh: generate public keys from certificate
Resolves: https://fedorahosted.org/sssd/ticket/2711
Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
-rw-r--r-- | Makefile.am | 7 | ||||
-rw-r--r-- | src/confdb/confdb.h | 2 | ||||
-rw-r--r-- | src/config/SSSDConfig/__init__.py.in | 1 | ||||
-rw-r--r-- | src/config/etc/sssd.api.conf | 1 | ||||
-rw-r--r-- | src/man/sssd.conf.5.xml | 13 | ||||
-rw-r--r-- | src/responder/ssh/sshsrv.c | 9 | ||||
-rw-r--r-- | src/responder/ssh/sshsrv_cmd.c | 54 | ||||
-rw-r--r-- | src/responder/ssh/sshsrv_private.h | 1 | ||||
-rw-r--r-- | src/tests/cmocka/test_cert_utils.c | 62 | ||||
-rw-r--r-- | src/util/cert.h | 4 | ||||
-rw-r--r-- | src/util/cert/libcrypto/cert.c | 93 | ||||
-rw-r--r-- | src/util/cert/nss/cert.c | 130 |
12 files changed, 364 insertions, 13 deletions
diff --git a/Makefile.am b/Makefile.am index fd78c1bb7..5345d90d2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1129,10 +1129,13 @@ sssd_ssh_SOURCES = \ src/responder/ssh/sshsrv.c \ src/responder/ssh/sshsrv_dp.c \ src/responder/ssh/sshsrv_cmd.c \ - $(SSSD_RESPONDER_OBJ) + $(SSSD_RESPONDER_OBJ) \ + $(NULL) sssd_ssh_LDADD = \ $(SSSD_LIBS) \ - $(SSSD_INTERNAL_LTLIBS) + $(SSSD_INTERNAL_LTLIBS) \ + libsss_cert.la \ + $(NULL) endif sssd_pac_SOURCES = \ diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index c3cdf49e0..df454337a 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -135,6 +135,8 @@ #define CONFDB_DEFAULT_SSH_HASH_KNOWN_HOSTS true #define CONFDB_SSH_KNOWN_HOSTS_TIMEOUT "ssh_known_hosts_timeout" #define CONFDB_DEFAULT_SSH_KNOWN_HOSTS_TIMEOUT 180 +#define CONFDB_SSH_CA_DB "ca_db" +#define CONFDB_DEFAULT_SSH_CA_DB SYSCONFDIR"/pki/nssdb" /* PAC */ #define CONFDB_PAC_CONF_ENTRY "config/pac" diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in index 4b519eddd..7d361026c 100644 --- a/src/config/SSSDConfig/__init__.py.in +++ b/src/config/SSSDConfig/__init__.py.in @@ -99,6 +99,7 @@ option_strings = { # [ssh] 'ssh_hash_known_hosts': _('Whether to hash host names and addresses in the known_hosts file'), 'ssh_known_hosts_timeout': _('How many seconds to keep a host in the known_hosts file after its host keys were requested'), + 'ca_db': _('Path to storage of trusted CA certificates'), # [pac] 'allowed_uids': _('List of UIDs or user names allowed to access the PAC responder'), diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index 29fd896cc..cf6ce6301 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -72,6 +72,7 @@ autofs_negative_timeout = int, None, false # ssh service ssh_hash_known_hosts = bool, None, false ssh_known_hosts_timeout = int, None, false +ca_db = str, None, false [pac] # PAC responder diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index b063d7cb8..3f57862c6 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -1082,6 +1082,19 @@ pam_account_expired_message = Account expired, please call help desk. </para> </listitem> </varlistentry> + <varlistentry> + <term>ca_db (string)</term> + <listitem> + <para> + Path to a storage of trusted CA certificates. The + option is used to validate user certificates before + deriving public ssh keys from them. + </para> + <para> + Default: /etc/pki/nssdb + </para> + </listitem> + </varlistentry> </variablelist> </refsect2> diff --git a/src/responder/ssh/sshsrv.c b/src/responder/ssh/sshsrv.c index 9439b9d89..d4e202d87 100644 --- a/src/responder/ssh/sshsrv.c +++ b/src/responder/ssh/sshsrv.c @@ -163,6 +163,15 @@ int ssh_process_init(TALLOC_CTX *mem_ctx, goto fail; } + ret = confdb_get_string(ssh_ctx->rctx->cdb, ssh_ctx, + CONFDB_SSH_CONF_ENTRY, CONFDB_SSH_CA_DB, + CONFDB_DEFAULT_SSH_CA_DB, &ssh_ctx->ca_db); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading CA DB from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, NULL); if (ret != EOK) { DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n"); diff --git a/src/responder/ssh/sshsrv_cmd.c b/src/responder/ssh/sshsrv_cmd.c index 483358791..f630e5f03 100644 --- a/src/responder/ssh/sshsrv_cmd.c +++ b/src/responder/ssh/sshsrv_cmd.c @@ -27,6 +27,7 @@ #include "util/util.h" #include "util/crypto/sss_crypto.h" #include "util/sss_ssh.h" +#include "util/cert.h" #include "db/sysdb.h" #include "db/sysdb_ssh.h" #include "providers/data_provider.h" @@ -219,7 +220,8 @@ static errno_t ssh_user_pubkeys_search_next(struct ssh_cmd_ctx *cmd_ctx) { errno_t ret; - const char *attrs[] = { SYSDB_NAME, SYSDB_SSH_PUBKEY, NULL }; + const char *attrs[] = { SYSDB_NAME, SYSDB_SSH_PUBKEY, SYSDB_USER_CERT, + NULL }; struct ldb_result *res; DEBUG(SSSDBG_TRACE_FUNC, @@ -794,6 +796,8 @@ ssh_cmd_parse_request(struct ssh_cmd_ctx *cmd_ctx) static errno_t decode_and_add_base64_data(struct ssh_cmd_ctx *cmd_ctx, struct ldb_message_element *el, + bool cert_data, + struct ssh_ctx *ssh_ctx, size_t fqname_len, const char *fqname, size_t *c) @@ -819,12 +823,22 @@ static errno_t decode_and_add_base64_data(struct ssh_cmd_ctx *cmd_ctx, } for (d = 0; d < el->num_values; d++) { - key = sss_base64_decode(tmp_ctx, (const char *) el->values[d].data, - &key_len); - if (key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); - ret = ENOMEM; - goto done; + if (cert_data) { + ret = cert_to_ssh_key(tmp_ctx, ssh_ctx->ca_db, + el->values[d].data, el->values[d].length, + &key, &key_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "cert_to_ssh_key failed.\n"); + return ret; + } + } else { + key = sss_base64_decode(tmp_ctx, (const char *) el->values[d].data, + &key_len); + if (key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + ret = ENOMEM; + goto done; + } } ret = sss_packet_grow(cctx->creq->out, @@ -862,10 +876,13 @@ ssh_cmd_build_reply(struct ssh_cmd_ctx *cmd_ctx) struct ldb_message_element *el = NULL; struct ldb_message_element *el_override = NULL; struct ldb_message_element *el_orig = NULL; + struct ldb_message_element *el_user_cert = NULL; uint32_t count = 0; const char *name; char *fqname; uint32_t fqname_len; + struct ssh_ctx *ssh_ctx = talloc_get_type(cctx->rctx->pvt_ctx, + struct ssh_ctx); ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), @@ -893,6 +910,12 @@ ssh_cmd_build_reply(struct ssh_cmd_ctx *cmd_ctx) } } + el_user_cert = ldb_msg_find_element(cmd_ctx->result, SYSDB_USER_CERT); + if (el_user_cert) { + /* TODO check if cert is valid */ + count += el_user_cert->num_values; + } + ret = sss_packet_grow(cctx->creq->out, 2*sizeof(uint32_t)); if (ret != EOK) { return ret; @@ -922,20 +945,29 @@ ssh_cmd_build_reply(struct ssh_cmd_ctx *cmd_ctx) fqname_len = strlen(fqname)+1; - ret = decode_and_add_base64_data(cmd_ctx, el, fqname_len, fqname, &c); + ret = decode_and_add_base64_data(cmd_ctx, el, false, ssh_ctx, + fqname_len, fqname, &c); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "decode_and_add_base64_data failed.\n"); + return ret; + } + + ret = decode_and_add_base64_data(cmd_ctx, el_orig, false, ssh_ctx, + fqname_len, fqname, &c); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "decode_and_add_base64_data failed.\n"); return ret; } - ret = decode_and_add_base64_data(cmd_ctx, el_orig, fqname_len, fqname, &c); + ret = decode_and_add_base64_data(cmd_ctx, el_override, false, ssh_ctx, + fqname_len, fqname, &c); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "decode_and_add_base64_data failed.\n"); return ret; } - ret = decode_and_add_base64_data(cmd_ctx, el_override, fqname_len, fqname, - &c); + ret = decode_and_add_base64_data(cmd_ctx, el_user_cert, true, ssh_ctx, + fqname_len, fqname, &c); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "decode_and_add_base64_data failed.\n"); return ret; diff --git a/src/responder/ssh/sshsrv_private.h b/src/responder/ssh/sshsrv_private.h index ebb30ce7c..beb8e18db 100644 --- a/src/responder/ssh/sshsrv_private.h +++ b/src/responder/ssh/sshsrv_private.h @@ -32,6 +32,7 @@ struct ssh_ctx { bool hash_known_hosts; int known_hosts_timeout; + char *ca_db; }; struct ssh_cmd_ctx { diff --git a/src/tests/cmocka/test_cert_utils.c b/src/tests/cmocka/test_cert_utils.c index 8063b1a65..7bd8cf234 100644 --- a/src/tests/cmocka/test_cert_utils.c +++ b/src/tests/cmocka/test_cert_utils.c @@ -21,15 +21,18 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "config.h" #include <popt.h> #ifdef HAVE_LIBCRYPTO #include <openssl/objects.h> +#include <openssl/crypto.h> #endif #include "util/cert.h" #include "tests/cmocka/common_mock.h" #include "util/crypto/nss/nss_util.h" +#include "util/crypto/sss_crypto.h" /* TODO: create a certificate for this test */ @@ -306,6 +309,63 @@ void test_sss_cert_derb64_to_ldap_filter(void **state) talloc_free(filter); } + +#define SSH_TEST_CERT \ +"MIIECTCCAvGgAwIBAgIBCDANBgkqhkiG9w0BAQsFADA0MRIwEAYDVQQKDAlJUEEu" \ +"REVWRUwxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xNTA2MjMx" \ +"NjMyMDdaFw0xNzA2MjMxNjMyMDdaMDIxEjAQBgNVBAoMCUlQQS5ERVZFTDEcMBoG" \ +"A1UEAwwTaXBhLWRldmVsLmlwYS5kZXZlbDCCASIwDQYJKoZIhvcNAQEBBQADggEP" \ +"ADCCAQoCggEBALXUq56VlY+Z0aWLLpFAjFfbElPBXGQsbZb85J3cGyPjaMHC9wS+" \ +"wjB6Ve4HmQyPLx8hbINdDmbawMHYQvTScLYfsqLtj0Lqw20sUUmedk+Es5Oh9VHo" \ +"nd8MavYx25Du2u+T0iSgNIDikXguiwCmtAj8VC49ebbgITcjJGzMmiiuJkV3o93Y" \ +"vvYF0VjLGDQbQWOy7IxzYJeNVJnZWKo67CHdok6qOrm9rxQt81rzwV/mGLbCMUbr" \ +"+N4M8URtd7EmzaYZQmNm//s2owFrCYMxpLiURPj+URZVuB72504/Ix7X0HCbA/AV" \ +"26J27fPY5nc8DMwfhUDCbTqPH/JEjd3mvY8CAwEAAaOCASYwggEiMB8GA1UdIwQY" \ +"MBaAFJOq+KAQmPEnNp8Wok23eGTdE7aDMDsGCCsGAQUFBwEBBC8wLTArBggrBgEF" \ +"BQcwAYYfaHR0cDovL2lwYS1jYS5pcGEuZGV2ZWwvY2Evb2NzcDAOBgNVHQ8BAf8E" \ +"BAMCBPAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHQGA1UdHwRtMGsw" \ +"aaAxoC+GLWh0dHA6Ly9pcGEtY2EuaXBhLmRldmVsL2lwYS9jcmwvTWFzdGVyQ1JM" \ +"LmJpbqI0pDIwMDEOMAwGA1UECgwFaXBhY2ExHjAcBgNVBAMMFUNlcnRpZmljYXRl" \ +"IEF1dGhvcml0eTAdBgNVHQ4EFgQUFaDNd5a53QGpaw5m63hnwXicMQ8wDQYJKoZI" \ +"hvcNAQELBQADggEBADH7Nj00qqGhGJeXJQAsepqSskz/wooqXh8vgVyb8SS4N0/c" \ +"0aQtVmY81xamlXE12ZFpwDX43d+EufBkwCUKFX/+8JFDd2doAyeJxv1xM22kKRpc" \ +"AqITPgMsa9ToGMWxjbVpc/X/5YfZixWPF0/eZUTotBj9oaR039UrhGfyN7OguF/G" \ +"rzmxtB5y4ZrMpcD/Oe90mkd9HY7sA/fB8OWOUgeRfQoh97HNS0UiDWsPtfxmjQG5" \ +"zotpoBIZmdH+ipYsu58HohHVlM9Wi5H4QmiiXl+Soldkq7eXYlafcmT7wv8+cKwz" \ +"Nz0Tm3+eYpFqRo3skr6QzXi525Jkg3r6r+kkhxU=" + +#define SSH_PUB_KEY "AAAAB3NzaC1yc2EAAAADAQABAAABAQC11KuelZWPmdGliy6RQIxX2xJTwVxkLG2W/OSd3Bsj42jBwvcEvsIwelXuB5kMjy8fIWyDXQ5m2sDB2EL00nC2H7Ki7Y9C6sNtLFFJnnZPhLOTofVR6J3fDGr2MduQ7trvk9IkoDSA4pF4LosAprQI/FQuPXm24CE3IyRszJooriZFd6Pd2L72BdFYyxg0G0FjsuyMc2CXjVSZ2ViqOuwh3aJOqjq5va8ULfNa88Ff5hi2wjFG6/jeDPFEbXexJs2mGUJjZv/7NqMBawmDMaS4lET4/lEWVbge9udOPyMe19BwmwPwFduidu3z2OZ3PAzMH4VAwm06jx/yRI3d5r2P" + +void test_cert_to_ssh_key(void **state) +{ + int ret; + uint8_t *key; + size_t key_size; + uint8_t *exp_key; + size_t exp_key_size; + uint8_t *der; + size_t der_size; + + struct test_state *ts = talloc_get_type_abort(*state, struct test_state); + assert_non_null(ts); + + der = sss_base64_decode(ts, SSH_TEST_CERT, &der_size); + assert_non_null(der); + + exp_key = sss_base64_decode(ts, SSH_PUB_KEY, &exp_key_size); + assert_non_null(exp_key); + + ret = cert_to_ssh_key(ts, "sql:" ABS_SRC_DIR "/src/tests/cmocka/p11_nssdb", + der, der_size, &key, &key_size); + assert_int_equal(ret, EOK); + assert_int_equal(key_size, exp_key_size); + assert_memory_equal(key, exp_key, exp_key_size); + + talloc_free(der); + talloc_free(key); + talloc_free(exp_key); +} + int main(int argc, const char *argv[]) { poptContext pc; @@ -330,6 +390,8 @@ int main(int argc, const char *argv[]) setup, teardown), cmocka_unit_test_setup_teardown(test_sss_cert_derb64_to_ldap_filter, setup, teardown), + cmocka_unit_test_setup_teardown(test_cert_to_ssh_key, + setup, teardown), }; /* Set debug level to invalid value so we can deside if -d 0 was used. */ diff --git a/src/util/cert.h b/src/util/cert.h index 79ea1a4ab..edbafc492 100644 --- a/src/util/cert.h +++ b/src/util/cert.h @@ -44,4 +44,8 @@ errno_t sss_cert_derb64_to_ldap_filter(TALLOC_CTX *mem_ctx, const char *derb64, errno_t bin_to_ldap_filter_value(TALLOC_CTX *mem_ctx, const uint8_t *blob, size_t blob_size, char **_str); + +errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db, + const uint8_t *der_blob, size_t der_size, + uint8_t **key, size_t *key_size); #endif /* __CERT_H__ */ diff --git a/src/util/cert/libcrypto/cert.c b/src/util/cert/libcrypto/cert.c index 1a250f60d..01f9554b9 100644 --- a/src/util/cert/libcrypto/cert.c +++ b/src/util/cert/libcrypto/cert.c @@ -166,3 +166,96 @@ done: return ret; } + +#define SSH_RSA_HEADER "ssh-rsa" +#define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1) + +errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db, + const uint8_t *der_blob, size_t der_size, + uint8_t **key, size_t *key_size) +{ + int ret; + size_t size; + const unsigned char *d; + uint8_t *buf = NULL; + size_t c; + X509 *cert = NULL; + EVP_PKEY *cert_pub_key = NULL; + int modulus_len; + unsigned char modulus[OPENSSL_RSA_MAX_MODULUS_BITS/8]; + 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; + } + + /* TODO: verify certificate !!!!! */ + + 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 (cert_pub_key->type != EVP_PKEY_RSA) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected RSA public key, found unsupported [%d].\n", + cert_pub_key->type); + ret = EINVAL; + goto done; + } + + modulus_len = BN_bn2bin(cert_pub_key->pkey.rsa->n, modulus); + exponent_len = BN_bn2bin(cert_pub_key->pkey.rsa->e, exponent); + + size = SSH_RSA_HEADER_LEN + 3 * sizeof(uint32_t) + + modulus_len + + exponent_len + + 1; /* see comment about missing 00 below */ + + buf = talloc_size(mem_ctx, size); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + ret = ENOMEM; + goto done; + } + + c = 0; + + SAFEALIGN_SET_UINT32(buf, htobe32(SSH_RSA_HEADER_LEN), &c); + safealign_memcpy(&buf[c], SSH_RSA_HEADER, SSH_RSA_HEADER_LEN, &c); + SAFEALIGN_SET_UINT32(&buf[c], htobe32(exponent_len), &c); + safealign_memcpy(&buf[c], exponent, exponent_len, &c); + + /* Adding missing 00 which afaik is added to make sure + * the bigint is handled as positive number */ + /* TODO: make a better check if 00 must be added or not, e.g. ... & 0x80) + */ + SAFEALIGN_SET_UINT32(&buf[c], htobe32(modulus_len + 1), &c); + SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c); + safealign_memcpy(&buf[c], modulus, modulus_len, &c); + + *key = buf; + *key_size = size; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(buf); + } + EVP_PKEY_free(cert_pub_key); + X509_free(cert); + + return ret; +} diff --git a/src/util/cert/nss/cert.c b/src/util/cert/nss/cert.c index a20abf63a..1ada35b63 100644 --- a/src/util/cert/nss/cert.c +++ b/src/util/cert/nss/cert.c @@ -20,9 +20,13 @@ #include "util/util.h" +#include <nss.h> #include <cert.h> #include <base64.h> +#include <key.h> +#include <prerror.h> +#include "util/crypto/sss_crypto.h" #include "util/crypto/nss/nss_util.h" #define NS_CERT_HEADER "-----BEGIN CERTIFICATE-----" @@ -210,3 +214,129 @@ done: return ret; } + +#define SSH_RSA_HEADER "ssh-rsa" +#define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1) + +errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db, + const uint8_t *der_blob, size_t der_size, + uint8_t **key, 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; + size_t c; + NSSInitContext *nss_ctx; + NSSInitParameters parameters = { 0 }; + parameters.length = sizeof (parameters); + SECStatus rv; + + if (der_blob == NULL || der_size == 0) { + return EINVAL; + } + + /* initialize NSS with context, we might have already called + * NSS_NoDB_Init() but for validation we need to have access to a DB with + * the trusted issuer cert. Only NSS_InitContext will really open the DB + * in this case. I'm not sure about how long validation might need e.g. if + * CRLs or OSCP is enabled, maybe it would be better to run validation in + * p11_child ? */ + nss_ctx = NSS_InitContext(ca_db, "", "", SECMOD_DB, ¶meters, + NSS_INIT_READONLY); + if (nss_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "NSS_InitContext failed [%d].\n", + PR_GetError()); + return EIO; + } + + 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; + } + + rv = CERT_VerifyCertificateNow(handle, cert, PR_TRUE, + certificateUsageSSLClient, NULL, NULL); + if (rv != SECSuccess) { + DEBUG(SSSDBG_CRIT_FAILURE, "CERT_VerifyCertificateNow failed [%d].\n", + PR_GetError()); + ret = EACCES; + 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; + } + + size = SSH_RSA_HEADER_LEN + 3 * sizeof(uint32_t) + + cert_pub_key->u.rsa.modulus.len + + cert_pub_key->u.rsa.publicExponent.len + + 1; /* see comment about missing 00 below */ + + buf = talloc_size(mem_ctx, size); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + ret = ENOMEM; + goto done; + } + + c = 0; + + SAFEALIGN_SET_UINT32(buf, htobe32(SSH_RSA_HEADER_LEN), &c); + safealign_memcpy(&buf[c], SSH_RSA_HEADER, SSH_RSA_HEADER_LEN, &c); + SAFEALIGN_SET_UINT32(&buf[c], + htobe32(cert_pub_key->u.rsa.publicExponent.len), &c); + safealign_memcpy(&buf[c], cert_pub_key->u.rsa.publicExponent.data, + cert_pub_key->u.rsa.publicExponent.len, &c); + + /* Looks like nss drops the leading 00 which afaik is added to make sure + * the bigint is handled as positive number */ + /* TODO: make a better check if 00 must be added or not, e.g. ... & 0x80) + */ + SAFEALIGN_SET_UINT32(&buf[c], + htobe32(cert_pub_key->u.rsa.modulus.len + 1 ), &c); + SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c); + safealign_memcpy(&buf[c], cert_pub_key->u.rsa.modulus.data, + cert_pub_key->u.rsa.modulus.len, &c); + + *key = buf; + *key_size = size; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(buf); + } + SECKEY_DestroyPublicKey(cert_pub_key); + CERT_DestroyCertificate(cert); + + rv = NSS_ShutdownContext(nss_ctx); + if (rv != SECSuccess) { + DEBUG(SSSDBG_OP_FAILURE, "NSS_ShutdownContext failed [%d].\n", + PR_GetError()); + } + + return ret; +} |