Jeremy Allison
2025-Oct-29  19:21 UTC
Proposed patch to use openssl for ed25519 crypto (patch now inline)
(Sigh. Trying again with patch inline. Sorry, didn't realize text
attachments got stripped.)
Hi all,
Currently openssh uses its own implementation of ed25519.c for the
operations:
crypto_sign_ed25519()
crypto_sign_ed25519_open()
crypto_sign_ed25519_keypair()
but can use the openssl library for other crypto operations.
configure.ac already checks openssl for OPENSSL_HAS_ED25519,
so here is a patch that adds openssl-based implementations of
the three functions above in a new file ed25519-openssl.c
and uses these if OPENSSL_HAS_ED25519 was defined.
I created this as CIQ (in Rocky Linux) publishes FIPS certified
versions of openssl in this public git repo:
https://github.com/ciq-rocky-fips/openssl
and being able to use a FIPS certified openssl library for ed25519
crypto allows users to use ed25519 keys in openssh instead of
being forced to use RSA keys when the system is in FIPS mode.
Full disclosure, claude code was used to help create ed25519-openssl.c
and was edited and carefully reviewed by myself and another CIQ engineer to
make sure this is not "AI-slop". The code is simple enough that it
should
be clear and easy to review.
Thanks for considering this !
Jeremy Allison,
CIQ.
---------------------------------------------------------------------------------------------------
diff --git a/Makefile.in b/Makefile.in
index 66f159689..ae73e0f7e 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -102,7 +102,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
  smult_curve25519_ref.o \
  poly1305.o chacha.o cipher-chachapoly.o cipher-chachapoly-libcrypto.o \
  ssh-ed25519.o digest-openssl.o digest-libc.o \
- hmac.o ed25519.o hash.o \
+ hmac.o @ED25519_OBJS@ hash.o \
  kex.o kex-names.o kexdh.o kexgex.o kexecdh.o kexc25519.o \
  kexgexc.o kexgexs.o \
  kexsntrup761x25519.o kexmlkem768x25519.o sntrup761.o kexgen.o \
diff --git a/configure.ac b/configure.ac
index 86cf56689..fde9bc8cd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3367,11 +3367,14 @@ if test "x$openssl" = "xyes" ; then
  AC_MSG_RESULT([yes])
  AC_DEFINE([OPENSSL_HAS_ED25519], [1],
      [libcrypto has ed25519 support])
+ ED25519_OBJS="ed25519-openssl.o"
  ],
  [
  AC_MSG_RESULT([no])
+ ED25519_OBJS="ed25519.o"
  ]
  )
+ AC_SUBST([ED25519_OBJS])
 fi
 # PKCS11/U2F depend on OpenSSL and dlopen().
diff --git a/crypto_api.h b/crypto_api.h
index 693b67bbc..d9227cc5f 100644
--- a/crypto_api.h
+++ b/crypto_api.h
@@ -40,6 +40,12 @@ int crypto_sign_ed25519_open(unsigned char *,
unsigned long long *,
     const unsigned char *, unsigned long long, const unsigned char *);
 int crypto_sign_ed25519_keypair(unsigned char *, unsigned char *);
+/*
+ * When OPENSSL_HAS_ED25519 is defined, the above functions are provided
+ * by ed25519-openssl.c using OpenSSL's EVP API.
+ * Otherwise, they are provided by ed25519.c (SUPERCOP reference
implementation).
+ */
+
 #define crypto_kem_sntrup761_PUBLICKEYBYTES 1158
 #define crypto_kem_sntrup761_SECRETKEYBYTES 1763
 #define crypto_kem_sntrup761_CIPHERTEXTBYTES 1039
diff --git a/ed25519-openssl.c b/ed25519-openssl.c
new file mode 100644
index 000000000..842256f03
--- /dev/null
+++ b/ed25519-openssl.c
@@ -0,0 +1,212 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2025 OpenSSH Portable
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#ifdef OPENSSL_HAS_ED25519
+
+#include <sys/types.h>
+#include <string.h>
+#include <stdint.h>
+#include <limits.h>
+
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+
+#include "crypto_api.h"
+#include "log.h"
+
+/*
+ * OpenSSL-based implementation of Ed25519 crypto_sign API
+ * This replaces the internal SUPERCOP-based implementation
+ */
+
+int
+crypto_sign_ed25519_keypair(unsigned char *pk, unsigned char *sk)
+{
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *pkey = NULL;
+ size_t pklen, sklen;
+ int ret = -1;
+
+ if ((ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL)) == NULL) {
+ debug3_f("EVP_PKEY_CTX_new_id failed");
+ goto out;
+ }
+ if (EVP_PKEY_keygen_init(ctx) <= 0) {
+ debug3_f("EVP_PKEY_keygen_init failed");
+ goto out;
+ }
+ if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
+ debug3_f("EVP_PKEY_keygen failed");
+ goto out;
+ }
+
+ /* Extract public key */
+ pklen = crypto_sign_ed25519_PUBLICKEYBYTES;
+ if (!EVP_PKEY_get_raw_public_key(pkey, pk, &pklen)) {
+ debug3_f("EVP_PKEY_get_raw_public_key failed");
+ goto out;
+ }
+ if (pklen != crypto_sign_ed25519_PUBLICKEYBYTES) {
+ debug3_f("public key length mismatch: %zu", pklen);
+ goto out;
+ }
+
+ /* Check for underflow in subtraction */
+ if (crypto_sign_ed25519_SECRETKEYBYTES <
crypto_sign_ed25519_PUBLICKEYBYTES) {
+ debug3_f("invalid key size: secret=%u < public=%u",
+     crypto_sign_ed25519_SECRETKEYBYTES,
+     crypto_sign_ed25519_PUBLICKEYBYTES);
+ goto out;
+ }
+ sklen = crypto_sign_ed25519_SECRETKEYBYTES -
crypto_sign_ed25519_PUBLICKEYBYTES;
+ /* Extract private key (32 bytes seed) */
+ if (!EVP_PKEY_get_raw_private_key(pkey, sk, &sklen)) {
+ debug3_f("EVP_PKEY_get_raw_private_key failed");
+ goto out;
+ }
+ if (sklen != crypto_sign_ed25519_SECRETKEYBYTES -
crypto_sign_ed25519_PUBLICKEYBYTES) {
+ debug3_f("private key length mismatch: %zu", sklen);
+ goto out;
+ }
+
+ /* Append public key to secret key (SUPERCOP format compatibility) */
+ memcpy(sk + sklen, pk, crypto_sign_ed25519_PUBLICKEYBYTES);
+
+ ret = 0;
+out:
+ EVP_PKEY_free(pkey);
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+}
+
+int
+crypto_sign_ed25519(unsigned char *sm, unsigned long long *smlen,
+    const unsigned char *m, unsigned long long mlen,
+    const unsigned char *sk)
+{
+ EVP_PKEY *pkey = NULL;
+ EVP_MD_CTX *mdctx = NULL;
+ size_t siglen;
+ int ret = -1;
+
+ /* Check for underflow in subtraction */
+ if (crypto_sign_ed25519_SECRETKEYBYTES <
crypto_sign_ed25519_PUBLICKEYBYTES) {
+ debug3_f("invalid key size: secret=%u < public=%u",
+     crypto_sign_ed25519_SECRETKEYBYTES,
+     crypto_sign_ed25519_PUBLICKEYBYTES);
+ goto out;
+ }
+
+ /* Create EVP_PKEY from secret key (first 32 bytes are the seed) */
+ if ((pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
+     sk, crypto_sign_ed25519_SECRETKEYBYTES -
crypto_sign_ed25519_PUBLICKEYBYTES)) == NULL) {
+ debug3_f("EVP_PKEY_new_raw_private_key failed");
+ goto out;
+ }
+
+ /* Sign the message */
+ if ((mdctx = EVP_MD_CTX_new()) == NULL) {
+ debug3_f("EVP_MD_CTX_new failed");
+ goto out;
+ }
+ if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) <= 0) {
+ debug3_f("EVP_DigestSignInit failed");
+ goto out;
+ }
+
+ /* Get signature (stored at beginning of sm buffer) */
+ siglen = crypto_sign_ed25519_BYTES;
+ if (EVP_DigestSign(mdctx, sm, &siglen, m, mlen) <= 0) {
+ debug3_f("EVP_DigestSign failed");
+ goto out;
+ }
+ if (siglen != crypto_sign_ed25519_BYTES) {
+ debug3_f("signature length mismatch: %zu", siglen);
+ goto out;
+ }
+
+ /* Append message after signature (SUPERCOP format) */
+ /* Check for overflow in addition */
+ if (siglen + mlen > ULLONG_MAX) {
+ debug3_f("message length overflow: siglen=%zu mlen=%llu",
+     siglen, mlen);
+ goto out;
+ }
+ memmove(sm + siglen, m, mlen);
+ *smlen = siglen + mlen;
+
+ ret = 0;
+out:
+ EVP_MD_CTX_free(mdctx);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+int
+crypto_sign_ed25519_open(unsigned char *m, unsigned long long *mlen,
+    const unsigned char *sm, unsigned long long smlen,
+    const unsigned char *pk)
+{
+ EVP_PKEY *pkey = NULL;
+ EVP_MD_CTX *mdctx = NULL;
+ int ret = -1;
+
+ if (smlen < crypto_sign_ed25519_BYTES) {
+ debug3_f("signed message too short: %llu", smlen);
+ return -1;
+ }
+
+ /* Create EVP_PKEY from public key */
+ if ((pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
+     pk, crypto_sign_ed25519_PUBLICKEYBYTES)) == NULL) {
+ debug3_f("EVP_PKEY_new_raw_public_key failed");
+ goto out;
+ }
+
+ if ((mdctx = EVP_MD_CTX_new()) == NULL) {
+ debug3_f("EVP_MD_CTX_new failed");
+ goto out;
+ }
+ if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) <= 0) {
+ debug3_f("EVP_DigestVerifyInit failed");
+ goto out;
+ }
+
+ /* Signature is first crypto_sign_ed25519_BYTES, message follows */
+ /* Note: smlen < crypto_sign_ed25519_BYTES already checked above */
+ /* Verify the signature */
+ if (EVP_DigestVerify(mdctx, sm, crypto_sign_ed25519_BYTES,
+     sm + crypto_sign_ed25519_BYTES, smlen - crypto_sign_ed25519_BYTES) <=
0) {
+ debug3_f("EVP_DigestVerify failed");
+ goto out;
+ }
+
+ /* Copy message out */
+ *mlen = smlen - crypto_sign_ed25519_BYTES;
+ memmove(m, sm + crypto_sign_ed25519_BYTES, *mlen);
+
+ ret = 0;
+out:
+ EVP_MD_CTX_free(mdctx);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+#endif /* OPENSSL_HAS_ED25519 */
Damien Miller
2025-Oct-29  22:34 UTC
Proposed patch to use openssl for ed25519 crypto (patch now inline)
On Wed, 29 Oct 2025, Jeremy Allison via openssh-unix-dev wrote:> (Sigh. Trying again with patch inline. Sorry, didn't realize text > attachments got stripped.)They don't though, e.g. the attachment is available on the 3rd-party archive at marc.info: https://marc.info/?l=openssh-unix-dev&m=176176508612432&w=2 OTOH your mailer messed up this patch. Anyway, I'll take a look. First impression is that the code seems fine but the underflow checks are probably not necessary; the size of ED25519 public and private keys are constants that will never be adjusted... -d