diff --git a/include/libssh/pki_priv.h b/include/libssh/pki_priv.h index be195b7d..e91c42bc 100644 --- a/include/libssh/pki_priv.h +++ b/include/libssh/pki_priv.h @@ -164,6 +164,11 @@ int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type); #endif /* WITH_PKCS11_URI */ bool ssh_key_size_allowed_rsa(int min_size, ssh_key key); + +/* Security Key Helper Functions */ +int pki_buffer_pack_sk_priv_data(ssh_buffer buffer, const ssh_key key); +int pki_buffer_unpack_sk_priv_data(ssh_buffer buffer, ssh_key key); + #ifdef __cplusplus } #endif diff --git a/src/pki.c b/src/pki.c index 39e74170..2d90f619 100644 --- a/src/pki.c +++ b/src/pki.c @@ -997,10 +997,15 @@ ssh_pki_export_privkey_base64_format(const ssh_key privkey, /* * For historic reasons, the Ed25519 keys are exported in OpenSSH file * format by default also when built with OpenSSL. + * + * The FIDO2/U2F security keys are an extension to the SSH protocol + * proposed by OpenSSH, and do not have any representation in PEM format. + * So, they are always exported in the OpenSSH file format. */ #ifdef HAVE_LIBCRYPTO if (format == SSH_FILE_FORMAT_DEFAULT && - privkey->type != SSH_KEYTYPE_ED25519) { + privkey->type != SSH_KEYTYPE_ED25519 && + !is_sk_key_type(privkey->type)) { format = SSH_FILE_FORMAT_PEM; } #endif /* HAVE_LIBCRYPTO */ @@ -1224,10 +1229,16 @@ ssh_pki_export_privkey_file_format(const ssh_key privkey, /* * For historic reasons, the Ed25519 keys are exported in OpenSSH file * format by default also when built with OpenSSL. + * + * The FIDO2/U2F security keys are an extension to the SSH protocol + * proposed by OpenSSH, and do not have any representation in PEM format. + * So, they are always exported in the OpenSSH file format. */ #ifdef HAVE_LIBCRYPTO if (format == SSH_FILE_FORMAT_DEFAULT && - privkey->type != SSH_KEYTYPE_ED25519) { + privkey->type != SSH_KEYTYPE_ED25519 && + !is_sk_key_type(privkey->type)) { + format = SSH_FILE_FORMAT_PEM; } #endif /* HAVE_LIBCRYPTO */ @@ -1453,6 +1464,38 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, } break; } + case SSH_KEYTYPE_SK_ECDSA: { + ssh_string type_str = NULL; + ssh_string pubkey = NULL; + int nid; + + rc = ssh_buffer_unpack(buffer, "SS", &type_str, &pubkey); + if (rc != SSH_OK) { + goto fail; + } + + rc = pki_buffer_unpack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + SSH_STRING_FREE(type_str); + SSH_STRING_FREE(pubkey); + goto fail; + } + + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(type_str)); + SSH_STRING_FREE(type_str); + + if (nid == -1) { + SSH_STRING_FREE(pubkey); + goto fail; + } + + rc = pki_pubkey_build_ecdsa(key, nid, pubkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + goto fail; + } + break; + } #endif /* HAVE_ECC */ case SSH_KEYTYPE_ED25519: { ssh_string pubkey = NULL, privkey = NULL; @@ -1478,14 +1521,38 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, } break; } + case SSH_KEYTYPE_SK_ED25519: { + ssh_string pubkey = NULL; + + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "Ed25519 keys not supported in FIPS mode"); + goto fail; + } + + rc = ssh_buffer_unpack(buffer, "S", &pubkey); + if (rc != SSH_OK) { + goto fail; + } + + rc = pki_buffer_unpack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + SSH_STRING_FREE(pubkey); + goto fail; + } + + rc = pki_pubkey_build_ed25519(key, pubkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + goto fail; + } + break; + } case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: case SSH_KEYTYPE_ECDSA_P521_CERT01: case SSH_KEYTYPE_ED25519_CERT01: - case SSH_KEYTYPE_SK_ECDSA: case SSH_KEYTYPE_SK_ECDSA_CERT01: - case SSH_KEYTYPE_SK_ED25519: case SSH_KEYTYPE_SK_ED25519_CERT01: case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: @@ -2232,6 +2299,64 @@ int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, return SSH_OK; } +/** + * @internal + * + * @brief Pack security key private data into a buffer. + * + * This function packs the common security key fields (application, flags, + * key handle, and reserved data) into a buffer. + * This is used for both ECDSA and Ed25519 security keys when exporting + * private key data. + * + * @param[in] buffer The buffer to pack the security key data into. + * + * @param[in] key The security key containing the data to pack. + * Must be a security key type (SK_ECDSA or SK_ED25519). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_buffer_pack() + */ +int pki_buffer_pack_sk_priv_data(ssh_buffer buffer, ssh_key key) +{ + return ssh_buffer_pack(buffer, + "SbSS", + key->sk_application, + key->sk_flags, + key->sk_key_handle, + key->sk_reserved); +} + +/** + * @internal + * + * @brief Unpack security key private data from a buffer. + * + * This function unpacks the common security key fields (application, flags, + * key handle, and reserved data) from a buffer. + * This is used for both ECDSA and Ed25519 security keys when importing + * private key data. + * + * @param[in] buffer The buffer to unpack the security key data from. + * + * @param[in] key The security key to store the unpacked data into. + * Must be a security key type (SK_ECDSA or SK_ED25519). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_buffer_unpack() + */ +int pki_buffer_unpack_sk_priv_data(ssh_buffer buffer, ssh_key key) +{ + return ssh_buffer_unpack(buffer, + "SbSS", + &key->sk_application, + &key->sk_flags, + &key->sk_key_handle, + &key->sk_reserved); +} + /** * @internal * diff --git a/src/pki_crypto.c b/src/pki_crypto.c index 32ad611b..73db42bf 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -1785,7 +1785,7 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) goto fail; } - if (type == SSH_KEY_PRIVATE) { + if (type == SSH_KEY_PRIVATE && key->type == SSH_KEYTYPE_ED25519) { key_len = 0; rc = EVP_PKEY_get_raw_private_key(key->key, NULL, &key_len); if (rc != 1) { @@ -1831,15 +1831,22 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) } explicit_bzero(ed25519_privkey, ED25519_KEY_LEN); SAFE_FREE(ed25519_privkey); - } else { + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ED25519) { + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ED25519) { /* public key can contain certificate sk information */ - if (key->type == SSH_KEYTYPE_SK_ED25519) { - rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); - if (rc < 0) { - goto fail; - } + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc != SSH_OK) { + goto fail; } } + SAFE_FREE(ed25519_pubkey); break; case SSH_KEYTYPE_ECDSA_P256: @@ -1944,7 +1951,8 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) ssh_string_burn(e); SSH_STRING_FREE(e); e = NULL; - if (type == SSH_KEY_PRIVATE) { + + if (type == SSH_KEY_PRIVATE && key->type != SSH_KEYTYPE_SK_ECDSA) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_PARAM_free(params); rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); @@ -1981,10 +1989,18 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) ssh_string_burn(d); SSH_STRING_FREE(d); d = NULL; - } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ECDSA) { + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ECDSA) { /* public key can contain certificate sk information */ rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); - if (rc < 0) { + if (rc != SSH_OK) { goto fail; } } diff --git a/src/pki_gcrypt.c b/src/pki_gcrypt.c index 1d31af06..6f1915fc 100644 --- a/src/pki_gcrypt.c +++ b/src/pki_gcrypt.c @@ -1600,9 +1600,21 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) } } } else { - rc = pki_ed25519_private_key_to_blob(buffer, key); - if (rc == SSH_ERROR) { - goto fail; + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } } } break; @@ -1640,7 +1652,7 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) SSH_STRING_FREE(e); e = NULL; - if (type == SSH_KEY_PRIVATE) { + if (type == SSH_KEY_PRIVATE && !is_sk_key_type(key->type)) { d = ssh_sexp_extract_mpi(key->ecdsa, "d", GCRYMPI_FMT_STD, @@ -1657,7 +1669,14 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) ssh_string_burn(d); SSH_STRING_FREE(d); d = NULL; - } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { + } else if (type == SSH_KEY_PRIVATE && is_sk_key_type(key->type)) { + /* Add security key private data for SK_ECDSA */ + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ECDSA) { /* public key can contain certificate sk information */ rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); if (rc < 0) { diff --git a/src/pki_mbedcrypto.c b/src/pki_mbedcrypto.c index cc5fb9c4..8ecfcf58 100644 --- a/src/pki_mbedcrypto.c +++ b/src/pki_mbedcrypto.c @@ -1066,7 +1066,7 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) goto out; } - if (type == SSH_KEY_PRIVATE) { + if (type == SSH_KEY_PRIVATE && key->type != SSH_KEYTYPE_SK_ECDSA) { d = ssh_make_bignum_string(&key->ecdsa->MBEDTLS_PRIVATE(d)); if (d == NULL) { @@ -1078,7 +1078,14 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) if (rc < 0) { goto out; } - } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ECDSA) { + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + goto out; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ECDSA) { /* public key can contain certificate sk information */ rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); if (rc < 0) { @@ -1101,9 +1108,21 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) } } } else { - rc = pki_ed25519_private_key_to_blob(buffer, key); - if (rc == SSH_ERROR) { - goto out; + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } } } break;