From 41c4661b6fd237b156606bfd0d8ca3edd5a16795 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Wed, 14 Nov 2018 21:13:53 +0100 Subject: [PATCH 72/74] 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 Reviewed-by: Jakub Hrozek --- 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 26fffb870..9273356eb 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 d925c5c5b..acca07dd0 100644 --- a/src/util/cert/libcrypto/cert.c +++ b/src/util/cert/libcrypto/cert.c @@ -168,6 +168,123 @@ done: } +/* 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; -- 2.19.1