[openssh-commits] [openssh] 02/02: upstream: add signature malleability and pubkey validity checks to

git+noreply at mindrot.org git+noreply at mindrot.org
Thu Jun 4 14:41:15 AEST 2026


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit 7763a38bc2595be50baf1d72b19bf4720e41e8e3
Author: djm at openbsd.org <djm at openbsd.org>
AuthorDate: Thu Jun 4 04:26:51 2026 +0000

    upstream: add signature malleability and pubkey validity checks to
    
    ed25519 verification (SSH doesn't depend on these properties) Pointed out by
    Soatok Dreamseeker
    
    Add an explicit-seed variant of the keygen function.
    
    feedback / "looks fine" tb@
    
    OpenBSD-Commit-ID: 2a71926bfda24628cf34a88357f44a790e338d5d
---
 ed25519.c  | 36 +++++++++++++++++++++++++++---
 ed25519.sh | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 102 insertions(+), 8 deletions(-)

diff --git a/ed25519.c b/ed25519.c
index 2452dff0f..8f683a2c0 100644
--- a/ed25519.c
+++ b/ed25519.c
@@ -1,4 +1,4 @@
-/*  $OpenBSD: ed25519.c,v 1.4 2023/01/15 23:05:32 djm Exp $ */
+/*  $OpenBSD: ed25519.c,v 1.5 2026/06/04 04:26:51 djm Exp $ */
 
 /*
  * Public Domain, Authors:
@@ -1912,13 +1912,13 @@ static void ge25519_scalarmult_base(ge25519_p3 *r, const sc25519 *s)
 }
 /* from supercop-20221122/crypto_sign/ed25519/ref/keypair.c */
 
-int crypto_sign_ed25519_keypair(unsigned char *pk,unsigned char *sk)
+int crypto_sign_ed25519_keypair_from_seed(unsigned char *pk,unsigned char *sk, const unsigned char *seed)
 {
   unsigned char az[64];
   sc25519 scsk;
   ge25519 gepk;
 
-  randombytes(sk,32);
+  memcpy(sk, seed, 32);
   crypto_hash_sha512(az,sk,32);
   az[0] &= 248;
   az[31] &= 127;
@@ -1931,6 +1931,18 @@ int crypto_sign_ed25519_keypair(unsigned char *pk,unsigned char *sk)
   memmove(sk + 32,pk,32);
   return 0;
 }
+
+int
+crypto_sign_ed25519_keypair(unsigned char *pk, unsigned char *sk)
+{
+  unsigned char seed[32];
+  int r;
+
+  randombytes(seed, 32);
+  r = crypto_sign_ed25519_keypair_from_seed(pk, sk, seed);
+  explicit_bzero(seed, sizeof(seed));
+  return r;
+}
 /* from supercop-20221122/crypto_sign/ed25519/ref/sign.c */
 
 int crypto_sign_ed25519(
@@ -1987,6 +1999,22 @@ int crypto_sign_ed25519(
 }
 /* from supercop-20221122/crypto_sign/ed25519/ref/open.c */
 
+/*
+ * Local OpenSSH addition: check that S < group order L
+ * Where L = 2^{252} + 27742317777372353535851937790883648493
+ * This can be variable time as the signature is public.
+ */
+static inline int sc25519_inrange(const unsigned char *pk)
+{
+  int i;
+
+  for (i = 0; i < 32; i++) {
+    if (pk[31 - i] > sc25519_m[31 - i]) return -1;
+    if (pk[31 - i] < sc25519_m[31 - i]) return 0;
+  }
+  return -1;
+}
+
 int crypto_sign_ed25519_open(
     unsigned char *m,unsigned long long *mlen,
     const unsigned char *sm,unsigned long long smlen,
@@ -2002,7 +2030,9 @@ int crypto_sign_ed25519_open(
 
   if (smlen < 64) goto badsig;
   if (sm[63] & 224) goto badsig;
+  if (sc25519_inrange(sm+32)) goto badsig;
   if (ge25519_unpackneg_vartime(&get1,pk)) goto badsig;
+  if (ge25519_isneutral_vartime(&get1)) goto badsig;
 
   memmove(pkcopy,pk,32);
   memmove(rcopy,sm,32);
diff --git a/ed25519.sh b/ed25519.sh
index 9e6cbc9c3..d24cdc0fc 100644
--- a/ed25519.sh
+++ b/ed25519.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-#       $OpenBSD: ed25519.sh,v 1.2 2024/05/17 02:39:11 jsg Exp $
+#       $OpenBSD: ed25519.sh,v 1.3 2026/06/04 04:26:51 djm Exp $
 #       Placed in the Public Domain.
 #
 AUTHOR="supercop-20221122/crypto_sign/ed25519/ref/implementors"
@@ -16,11 +16,12 @@ FILES="
 	supercop-20221122/crypto_sign/ed25519/ref/open.c
 "
 ###
+PORTABLE=${PORTABLE:-0}
 
 DATA="supercop-20221122/crypto_sign/ed25519/ref/ge25519_base.data"
 
 set -e
-cd $1
+test -z "$1" || cd $1
 echo -n '/*  $'
 echo 'OpenBSD: $ */'
 echo
@@ -29,6 +30,12 @@ echo ' * Public Domain, Authors:'
 sed -e '/Alphabetical order:/d' -e 's/^/ * - /' < $AUTHOR
 echo ' */'
 echo
+if [ "$PORTABLE" -ne 0 ]; then
+	echo '#include "includes.h"'
+	echo
+	echo '#ifndef OPENSSL_HAS_ED25519'
+	echo
+fi
 echo '#include <string.h>'
 echo
 echo '#include "crypto_api.h"'
@@ -42,6 +49,32 @@ done
 echo
 for i in $FILES; do
 	echo "/* from $i */"
+
+	case "$i" in
+	*/crypto_sign/ed25519/ref/open.c)
+	# Include our malleability fix at the start if open.c
+	cat << _EOF
+
+/*
+ * Local OpenSSH addition: check that S < group order L
+ * Where L = 2^{252} + 27742317777372353535851937790883648493
+ * This can be variable time as the signature is public.
+ */
+static inline int sc25519_inrange(const unsigned char *pk)
+{
+  int i;
+
+  for (i = 0; i < 32; i++) {
+    if (pk[31 - i] > sc25519_m[31 - i]) return -1;
+    if (pk[31 - i] < sc25519_m[31 - i]) return 0;
+  }
+  return -1;
+}
+_EOF
+	;;
+	esac
+
+	nl=`echo`
 	# Changes to all files:
 	#  - inline ge25519_base.data where it is included
 	#  - expand CRYPTO_NAMESPACE() namespacing define
@@ -66,12 +99,17 @@ for i in $FILES; do
 	    sed -e "s/crypto_sign/crypto_sign_ed25519/g"
 	    ;;
 	*/crypto_sign/ed25519/ref/keypair.c)
-	    # rename key generation function to the name OpenSSH expects
-	    sed -e "s/crypto_sign_keypair/crypto_sign_ed25519_keypair/g"
+	    # provide an explicit-seed key generation function and rename
+	    # it to the name OpenSSH expects
+	    sed -e "s/crypto_sign_keypair(unsigned char \*pk,unsigned char \*sk)/crypto_sign_ed25519_keypair_from_seed(unsigned char *pk,unsigned char *sk, const unsigned char *seed)/g" \
+	        -e "s/randombytes(sk,32);/memcpy(sk, seed, 32);/g"
 	    ;;
 	*/crypto_sign/ed25519/ref/open.c)
 	    # rename verification function to the name OpenSSH expects
-	    sed -e "s/crypto_sign_open/crypto_sign_ed25519_open/g"
+	    # Insert malleability checks
+	    sed -e "s/crypto_sign_open/crypto_sign_ed25519_open/g" | \
+	    perl -0777 -pe 's/(.*if.*ge25519_unpackneg_vartime.*get1,pk.*)/  if (sc25519_inrange(sm+32)) goto badsig;\n\1\n  if (ge25519_isneutral_vartime(&get1)) goto badsig;/'
+	    #perl -0777 -pe 's/^(.*ge25519_unpackneg_vartime.*,pk.*)$/  if (sc25519_inrange(sm+32)) goto badsig;\n$1\n  if (ge25519_isneutral_vartime(&get1)) goto badsig;/'
 	    ;;
 	*/crypto_sign/ed25519/ref/fe25519.*)
 	    # avoid a couple of name collisions with other files
@@ -116,4 +154,30 @@ for i in $FILES; do
 	    ;;
 	esac | \
 	sed -e 's/[	 ]*$//'
+
+	# Include implicit-seed keygen function used for ssh-ed25519
+	case "$i" in
+	*/crypto_sign/ed25519/ref/keypair.c)
+	cat << _EOF
+
+int
+crypto_sign_ed25519_keypair(unsigned char *pk, unsigned char *sk)
+{
+  unsigned char seed[32];
+  int r;
+
+  randombytes(seed, 32);
+  r = crypto_sign_ed25519_keypair_from_seed(pk, sk, seed);
+  explicit_bzero(seed, sizeof(seed));
+  return r;
+}
+_EOF
+	;;
+	esac
+
 done
+
+if [ "$PORTABLE" -ne 0 ]; then
+       echo
+       echo '#endif /* OPENSSL_HAS_ED25519 */'
+fi

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list