From 4f7cb6076a1d43696528927fc1161e0b81d88e47 Mon Sep 17 00:00:00 2001 From: Anderson Toshiyuki Sasaki Date: Fri, 30 Aug 2019 18:41:16 +0200 Subject: pki_crypto: Support Ed25519 keys in PEM files This adds support for Ed25519 keys from files in PEM format when using OpenSSL with Ed25519 support. The default encoding for the PEM file is expected to be PKCS#8. Encrypted files are supported. For the lack of an API, it is not possible to export keys in PEM format, only in OpenSSH format. Signed-off-by: Anderson Toshiyuki Sasaki Reviewed-by: Jakub Jelen (cherry picked from commit a3a0529b41e5ce4789cc8a5bd5e09b4ed15efe32) --- src/pki_crypto.c | 87 ++++++++++++++++++++--- tests/torture_key.c | 38 +++++++--- tests/unittests/torture_pki_ed25519.c | 126 +++++++++++++++++++++++++++++++++- 3 files changed, 229 insertions(+), 22 deletions(-) diff --git a/src/pki_crypto.c b/src/pki_crypto.c index 13198249..4517e11d 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -730,29 +730,58 @@ ssh_string pki_private_key_to_pem(const ssh_key key, return NULL; } - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - goto err; - } - switch (key->type) { case SSH_KEYTYPE_DSS: + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + goto err; + } + rc = EVP_PKEY_set1_DSA(pkey, key->dsa); break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + goto err; + } + rc = EVP_PKEY_set1_RSA(pkey, key->rsa); break; #ifdef HAVE_ECC case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + goto err; + } + rc = EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa); break; #endif case SSH_KEYTYPE_ED25519: +#ifdef HAVE_OPENSSL_ED25519 + /* In OpenSSL, the input is the private key seed only, which means + * the first half of the SSH private key (the second half is the + * public key) */ + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, + (const uint8_t *)key->ed25519_privkey, + ED25519_KEY_LEN); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto err; + } + + /* Mark the operation as successful as for the other key types */ + rc = 1; + break; +#else SSH_LOG(SSH_LOG_WARN, "PEM output not supported for key type ssh-ed25519"); goto err; +#endif case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: @@ -865,7 +894,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, SSH_LOG(SSH_LOG_WARN, "Parsing private key: %s", ERR_error_string(ERR_get_error(),NULL)); - return NULL; + goto fail; } type = SSH_KEYTYPE_DSS; break; @@ -875,7 +904,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, SSH_LOG(SSH_LOG_WARN, "Parsing private key: %s", ERR_error_string(ERR_get_error(),NULL)); - return NULL; + goto fail; } type = SSH_KEYTYPE_RSA; break; @@ -886,7 +915,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, SSH_LOG(SSH_LOG_WARN, "Parsing private key: %s", ERR_error_string(ERR_get_error(), NULL)); - return NULL; + goto fail; } /* pki_privatekey_type_from_string always returns P256 for ECDSA @@ -898,6 +927,43 @@ ssh_key pki_private_key_from_base64(const char *b64_key, } break; +#endif +#ifdef HAVE_OPENSSL_ED25519 + case EVP_PKEY_ED25519: + { + size_t key_len; + int evp_rc = 0; + + /* Get the key length */ + evp_rc = EVP_PKEY_get_raw_private_key(pkey, NULL, &key_len); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private key length: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (key_len != ED25519_KEY_LEN) { + goto fail; + } + + ed25519 = malloc(key_len); + if (ed25519 == NULL) { + SSH_LOG(SSH_LOG_WARN, "Out of memory"); + goto fail; + } + + evp_rc = EVP_PKEY_get_raw_private_key(pkey, (uint8_t *)ed25519, + &key_len); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + type = SSH_KEYTYPE_ED25519; + } + break; #endif default: EVP_PKEY_free(pkey); @@ -927,13 +993,16 @@ ssh_key pki_private_key_from_base64(const char *b64_key, return key; fail: + EVP_PKEY_free(pkey); ssh_key_free(key); DSA_free(dsa); RSA_free(rsa); #ifdef HAVE_OPENSSL_ECC EC_KEY_free(ecdsa); #endif - +#ifdef HAVE_OPENSSL_ED25519 + SAFE_FREE(ed25519); +#endif return NULL; } diff --git a/tests/torture_key.c b/tests/torture_key.c index e0c7643f..58540268 100644 --- a/tests/torture_key.c +++ b/tests/torture_key.c @@ -602,7 +602,12 @@ static const char torture_ecdsa521_testkey_cert[] = * ED25519 KEYS ****************************************************************************/ -static const char torture_ed25519_private_testkey[] = +static const char torture_ed25519_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MC4CAQAwBQYDK2VwBCIEIGBhcqLe61tkqVjIHKEzwB3oINasSHWGbIWXQWcLPmGN\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_ed25519_private_openssh_testkey[] = "-----BEGIN OPENSSH PRIVATE KEY-----\n" "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" "QyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlwAAAJBzsDN1c7Az\n" @@ -611,16 +616,24 @@ static const char torture_ed25519_private_testkey[] = "lLV+klM+BU6Nh3PmAiqXAAAADGFyaXNAa2FsaXg4NgE=\n" "-----END OPENSSH PRIVATE KEY-----\n"; -static const char torture_ed25519_private_testkey_passphrase[] = +static const char torture_ed25519_private_openssh_testkey_passphrase[] = "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABB3FWpQcE\n" - "KHKq6PcjkxjmKzAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIOGFVuOyZBL0T+NR\n" - "C7qEV9qr6QiGhz2XSXrxuQoU84FgAAAAkBlOVfS5U7FxtBEtxfxQhZjrZAj2z9d4OfGRPl\n" - "ZfCnAJNEM3BZ3XCabsujhMkqEs9eptRfj41X6NA8aSFs5JYT+JFVfg470FKtpyUmAibMIo\n" - "JzI41zAncFd1x7bAgO5HBDe3xNsV159D+sXRkWB9Tzk0l4F8SZvInheIS7VSbqH7t1+yDB\n" - "Y3GsmYTDstmicanQ==\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDYuz+a8i\n" + "nb/BgGjQjQtvkUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYL\n" + "tkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAkOBxqvzvPSns3TbhjkCayvANI66100OELnpDOm\n" + "JBGgXr5q846NkAovH3pmJ4O7qzPLTQ/cm0+959VUODRhM1i96qBg5MTNtV33lf5Y57Klzu\n" + "JegbiexcqkHIzriH42K0XSOEpfW8f/rTH7ffjbE/7l8HRNwf7AmcnxLx/d8J8FTBr+8aU7\n" + "qMU3xAJ4ixnwhYFg==\n" "-----END OPENSSH PRIVATE KEY-----\n"; +static const char torture_ed25519_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAie1RBk/ub+EwICCAAw\n" + "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEECRLkPChQx/sZPYLdNJhxMUEQFLj\n" + "7nelAdOx3WXIBbCOfOqg3aAn8C5cXPtIQ+fiui1V8wlXXV8RBiuDCC97ScLs91D5\n" + "qQhQtw0vgfnq1um/izg=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + static const char torture_ed25519_public_testkey[] = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+" "BU6Nh3PmAiqX aris@kalix86"; @@ -733,16 +746,19 @@ static const char *torture_get_testkey_internal(enum ssh_keytypes_e type, return torture_ed25519_public_testkey; } else if (with_passphrase) { if (format == 1) { - return torture_ed25519_private_testkey_passphrase; + return torture_ed25519_private_openssh_testkey_passphrase; + } + if (format == 2) { + return torture_ed25519_private_pkcs8_testkey_passphrase; } /* ed25519 keys are not available in legacy PEM format */ return NULL; } if (format == 1) { - return torture_ed25519_private_testkey; + return torture_ed25519_private_openssh_testkey; } /* ed25519 keys are not available in legacy PEM format */ - return NULL; + return torture_ed25519_private_pkcs8_testkey; case SSH_KEYTYPE_DSS_CERT01: return torture_dsa_testkey_cert; case SSH_KEYTYPE_RSA_CERT01: diff --git a/tests/unittests/torture_pki_ed25519.c b/tests/unittests/torture_pki_ed25519.c index 3d486965..07ccfd67 100644 --- a/tests/unittests/torture_pki_ed25519.c +++ b/tests/unittests/torture_pki_ed25519.c @@ -643,14 +643,131 @@ static void torture_pki_ed25519_sign(void **state) assert_non_null(blob); assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); - assert_memory_equal(ssh_string_data(blob), ref_signature, sizeof(ref_signature)); - /* ssh_print_hexa("signature", ssh_string_data(blob), ssh_string_len(blob)); */ + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + ssh_signature_free(sig); SSH_KEY_FREE(privkey); SSH_STRING_FREE(blob); } +static void torture_pki_ed25519_sign_openssh_privkey_passphrase(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(keystring, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} + +#ifdef HAVE_OPENSSL_ED25519 +static void torture_pki_ed25519_sign_pkcs8_privkey(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} + +static void torture_pki_ed25519_sign_pkcs8_privkey_passphrase(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(keystring, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} +#endif /* HAVE_OPENSSL_ED25519 */ + static void torture_pki_ed25519_verify(void **state){ ssh_key pubkey = NULL; ssh_signature sig = NULL; @@ -895,6 +1012,11 @@ int torture_run_tests(void) { teardown), cmocka_unit_test(torture_pki_ed25519_import_privkey_base64_passphrase), cmocka_unit_test(torture_pki_ed25519_sign), + cmocka_unit_test(torture_pki_ed25519_sign_openssh_privkey_passphrase), +#ifdef HAVE_OPENSSL_ED25519 + cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey), + cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey_passphrase), +#endif cmocka_unit_test(torture_pki_ed25519_verify), cmocka_unit_test(torture_pki_ed25519_verify_bad), cmocka_unit_test(torture_pki_ed25519_privkey_dup), -- cgit v1.2.3