diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 3422e67a..43df7fdd 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -226,6 +226,7 @@ if (GCRYPT_FOUND) endif (GCRYPT_VERSION VERSION_GREATER "1.4.6") if (NOT GCRYPT_VERSION VERSION_LESS "1.7.0") set(HAVE_GCRYPT_CHACHA_POLY 1) + set(HAVE_GCRYPT_CURVE25519 1) endif (NOT GCRYPT_VERSION VERSION_LESS "1.7.0") endif (GCRYPT_FOUND) diff --git a/config.h.cmake b/config.h.cmake index dac315eb..3f27983a 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -102,6 +102,9 @@ /* Define to 1 if you have gcrypt with ChaCha20/Poly1305 support */ #cmakedefine HAVE_GCRYPT_CHACHA_POLY 1 +/* Define to 1 if you have gcrypt with curve25519 support */ +#cmakedefine HAVE_GCRYPT_CURVE25519 + /*************************** FUNCTIONS ***************************/ /* Define to 1 if you have the `EVP_chacha20' function. */ diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h index 67200ac6..7dccb6d3 100644 --- a/include/libssh/crypto.h +++ b/include/libssh/crypto.h @@ -130,6 +130,8 @@ struct ssh_crypto_struct { #ifdef HAVE_CURVE25519 #ifdef HAVE_LIBCRYPTO EVP_PKEY *curve25519_privkey; +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_sexp_t curve25519_privkey; #else ssh_curve25519_privkey curve25519_privkey; #endif diff --git a/src/curve25519.c b/src/curve25519.c index fa81ce63..562ccd7b 100644 --- a/src/curve25519.c +++ b/src/curve25519.c @@ -45,6 +45,8 @@ #include "mbedcrypto-compat.h" #include #include +#elif defined(HAVE_GCRYPT_CURVE25519) +#include #endif static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply); @@ -62,21 +64,31 @@ static struct ssh_packet_callbacks_struct ssh_curve25519_client_callbacks = { int ssh_curve25519_init(ssh_session session) { - int rc; ssh_curve25519_pubkey *pubkey_loc = NULL; #ifdef HAVE_LIBCRYPTO + int rc; EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *pkey = NULL; size_t pubkey_len = CURVE25519_PUBKEY_SIZE; #elif defined(HAVE_MBEDTLS_CURVE25519) + int rc; mbedtls_ecdh_context ecdh_ctx; mbedtls_ctr_drbg_context *ctr_drbg = NULL; char error_buf[128]; int ret = SSH_ERROR; +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_error_t gcry_err; + gcry_sexp_t param = NULL, keypair_sexp = NULL; + ssh_string privkey = NULL, pubkey = NULL; + char *pubkey_data = NULL; + int ret = SSH_ERROR; +#else + int rc; #endif + if (session->server) { pubkey_loc = &session->next_crypto->curve25519_server_pubkey; } else { @@ -205,6 +217,64 @@ out: mbedtls_ecdh_free(&ecdh_ctx); return ret; +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_err = + gcry_sexp_build(¶m, NULL, "(genkey (ecdh (curve Curve25519)))"); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create keypair sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + gcry_err = gcry_pk_genkey(&keypair_sexp, param); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate keypair: %s", + gcry_strerror(gcry_err)); + goto out; + } + + /* Extract the public key */ + pubkey = ssh_sexp_extract_mpi(keypair_sexp, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to extract public key: %s", + gcry_strerror(gcry_err)); + goto out; + } + + /* Store the public key in the session */ + /* The first byte should be 0x40 indicating that the point is compressed, so + * we skip storing it */ + pubkey_data = (char *)ssh_string_data(pubkey); + if (ssh_string_len(pubkey) != CURVE25519_PUBKEY_SIZE + 1 || + pubkey_data[0] != 0x40) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid public key with length: %zu", + ssh_string_len(pubkey)); + goto out; + } + + memcpy(*pubkey_loc, pubkey_data + 1, CURVE25519_PUBKEY_SIZE); + + /* Store the private key */ + session->next_crypto->curve25519_privkey = keypair_sexp; + keypair_sexp = NULL; + ret = SSH_OK; + +out: + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + ssh_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + gcry_sexp_release(param); + gcry_sexp_release(keypair_sexp); + return ret; + #else rc = ssh_get_random(session->next_crypto->curve25519_privkey, CURVE25519_PRIVKEY_SIZE, 1); @@ -273,6 +343,14 @@ int ssh_curve25519_create_k(ssh_session session, ssh_curve25519_pubkey k) mbedtls_ctr_drbg_context *ctr_drbg = NULL; char error_buf[128]; +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_error_t gcry_err; + gcry_sexp_t pubkey_sexp = NULL, privkey_data_sexp = NULL, + result_sexp = NULL; + ssh_string shared_secret = NULL, privkey = NULL; + char *shared_secret_data = NULL; + int ret = SSH_ERROR; + #endif if (session->server) { peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; @@ -454,6 +532,88 @@ out: return ret; } +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_err = gcry_sexp_build( + &pubkey_sexp, + NULL, + "(key-data(public-key (ecdh (curve Curve25519) (q %b))))", + CURVE25519_PUBKEY_SIZE, + *peer_pubkey_loc); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create peer public key sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + privkey = ssh_sexp_extract_mpi(session->next_crypto->curve25519_privkey, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (privkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to extract private key"); + goto out; + } + + gcry_err = gcry_sexp_build(&privkey_data_sexp, + NULL, + "(data(flags raw)(value %b))", + ssh_string_len(privkey), + ssh_string_data(privkey)); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create private key sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + gcry_err = gcry_pk_encrypt(&result_sexp, privkey_data_sexp, pubkey_sexp); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to compute shared secret: %s", + gcry_strerror(gcry_err)); + goto out; + } + + shared_secret = ssh_sexp_extract_mpi(result_sexp, + "s", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_USG); + if (shared_secret == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to extract shared secret"); + goto out; + } + + /* Copy the shared secret to the output buffer */ + /* The first byte should be 0x40 indicating that it is a compressed point, + * so we skip it */ + shared_secret_data = (char *)ssh_string_data(shared_secret); + if (ssh_string_len(shared_secret) != CURVE25519_PUBKEY_SIZE + 1 || + shared_secret_data[0] != 0x40) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid shared secret with length: %zu", + ssh_string_len(shared_secret)); + goto out; + } + + memcpy(k, shared_secret_data + 1, CURVE25519_PUBKEY_SIZE); + + ret = SSH_OK; + gcry_sexp_release(session->next_crypto->curve25519_privkey); + session->next_crypto->curve25519_privkey = NULL; + +out: + ssh_string_burn(shared_secret); + SSH_STRING_FREE(shared_secret); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + gcry_sexp_release(privkey_data_sexp); + gcry_sexp_release(pubkey_sexp); + gcry_sexp_release(result_sexp); + if (ret == SSH_ERROR) { + return ret; + } + #else crypto_scalarmult(k, session->next_crypto->curve25519_privkey, diff --git a/tests/external_override/CMakeLists.txt b/tests/external_override/CMakeLists.txt index cf8cd800..f339739b 100644 --- a/tests/external_override/CMakeLists.txt +++ b/tests/external_override/CMakeLists.txt @@ -98,7 +98,7 @@ if (WITH_MBEDTLS) else () list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") endif () - + if(HAVE_MBEDTLS_CURVE25519) list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") else () @@ -113,8 +113,14 @@ elseif (WITH_GCRYPT) else () list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") endif () + + if(HAVE_GCRYPT_CURVE25519) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") + else() + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") + endif() + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=1") - list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_SNTRUP761=0") else () if (HAVE_OPENSSL_EVP_CHACHA20)