From 68436f0a8964e911eb4f864bc8b31d7ca4d29585 Mon Sep 17 00:00:00 2001
From: Kurt Roeckx <kurt@roeckx.be>
Date: Thu, 2 Jan 2020 23:25:27 +0100
Subject: [PATCH] Stop accepting certificates signed using SHA1 at security
 level 1

Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
GH: #10786
(cherry picked from commit b744f915ca8bb37631909728dd2529289bda8438)
---
 CHANGES                       | 12 ++++++++++++
 NEWS                          |  5 ++++-
 crypto/rsa/rsa_ameth.c        | 20 +++++++++++++++++++-
 crypto/x509/x509_set.c        | 14 ++++++++++++++
 test/recipes/25-test_verify.t |  8 ++++----
 5 files changed, 53 insertions(+), 6 deletions(-)

diff --git a/CHANGES b/CHANGES
index e4d57bb6b5..34d09c5d3c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,6 +9,18 @@
 
  Changes between 1.1.1d and 1.1.1e [xx XXX xxxx]
 
+  *) X509 certificates signed using SHA1 are no longer allowed at security
+     level 1 and above.
+     In TLS/SSL the default security level is 1. It can be set either
+     using the cipher string with @SECLEVEL, or calling
+     SSL_CTX_set_security_level(). If the leaf certificate is signed with SHA-1,
+     a call to SSL_CTX_use_certificate() will fail if the security level is not
+     lowered first.
+     Outside TLS/SSL, the default security level is -1 (effectively 0). It can
+     be set using X509_VERIFY_PARAM_set_auth_level() or using the -auth_level
+     options of the apps.
+     [Kurt Roeckx]
+
   *) Corrected the documentation of the return values from the EVP_DigestSign*
      set of functions.  The documentation mentioned negative values for some
      errors, but this was never the case, so the mention of negative values
diff --git a/NEWS b/NEWS
index 4af390505d..11840cf05b 100644
--- a/NEWS
+++ b/NEWS
@@ -7,7 +7,10 @@
 
   Major changes between OpenSSL 1.1.1d and OpenSSL 1.1.1e [under development]
 
-      o
+      o X509 certificates signed using SHA1 are no longer allowed at security
+        level 1 or higher. The default security level for TLS is 1, so
+        certificates signed using SHA1 are by default no longer trusted to
+        authenticate servers or clients.
 
   Major changes between OpenSSL 1.1.1c and OpenSSL 1.1.1d [10 Sep 2019]
 
diff --git a/crypto/rsa/rsa_ameth.c b/crypto/rsa/rsa_ameth.c
index 6692a51ed8..d45d6b5ba3 100644
--- a/crypto/rsa/rsa_ameth.c
+++ b/crypto/rsa/rsa_ameth.c
@@ -855,6 +855,7 @@ static int rsa_sig_info_set(X509_SIG_INFO *siginf, const X509_ALGOR *sigalg,
     uint32_t flags;
     const EVP_MD *mgf1md = NULL, *md = NULL;
     RSA_PSS_PARAMS *pss;
+    int secbits;
 
     /* Sanity check: make sure it is PSS */
     if (OBJ_obj2nid(sigalg->algorithm) != EVP_PKEY_RSA_PSS)
@@ -874,7 +875,24 @@ static int rsa_sig_info_set(X509_SIG_INFO *siginf, const X509_ALGOR *sigalg,
     else
         flags = 0;
     /* Note: security bits half number of digest bits */
-    X509_SIG_INFO_set(siginf, mdnid, EVP_PKEY_RSA_PSS, EVP_MD_size(md) * 4,
+    secbits = EVP_MD_size(md) * 4;
+    /*
+     * SHA1 and MD5 are known to be broken. Reduce security bits so that
+     * they're no longer accepted at security level 1. The real values don't
+     * really matter as long as they're lower than 80, which is our security
+     * level 1.
+     * https://eprint.iacr.org/2020/014 puts a chosen-prefix attack for SHA1 at
+     * 2^63.4
+     * https://documents.epfl.ch/users/l/le/lenstra/public/papers/lat.pdf
+     * puts a chosen-prefix attack for MD5 at 2^39.
+     */
+    if (mdnid == NID_sha1)
+        secbits = 64;
+    else if (mdnid == NID_md5_sha1)
+        secbits = 68;
+    else if (mdnid == NID_md5)
+        secbits = 39;
+    X509_SIG_INFO_set(siginf, mdnid, EVP_PKEY_RSA_PSS, secbits,
                       flags);
     rv = 1;
     err:
diff --git a/crypto/x509/x509_set.c b/crypto/x509/x509_set.c
index 164b4e2be1..deb7722c18 100644
--- a/crypto/x509/x509_set.c
+++ b/crypto/x509/x509_set.c
@@ -222,6 +222,20 @@ static void x509_sig_info_init(X509_SIG_INFO *siginf, const X509_ALGOR *alg,
         return;
     /* Security bits: half number of bits in digest */
     siginf->secbits = EVP_MD_size(md) * 4;
+    /*
+     * SHA1 and MD5 are known to be broken. Reduce security bits so that
+     * they're no longer accepted at security level 1. The real values don't
+     * really matter as long as they're lower than 80, which is our security
+     * level 1.
+     * https://eprint.iacr.org/2020/014 puts a chosen-prefix attack for SHA1 at
+     * 2^63.4
+     * https://documents.epfl.ch/users/l/le/lenstra/public/papers/lat.pdf
+     * puts a chosen-prefix attack for MD5 at 2^39.
+     */
+    if (mdnid == NID_sha1)
+        siginf->secbits = 63;
+    else if (mdnid == NID_md5)
+        siginf->secbits = 39;
     switch (mdnid) {
         case NID_sha1:
         case NID_sha256:
diff --git a/test/recipes/25-test_verify.t b/test/recipes/25-test_verify.t
index b80a1cde3e..5e5bc9ef1e 100644
--- a/test/recipes/25-test_verify.t
+++ b/test/recipes/25-test_verify.t
@@ -336,14 +336,14 @@ ok(!verify("badalt9-cert", "sslserver", ["root-cert"], ["ncca1-cert", "ncca3-cer
 ok(!verify("badalt10-cert", "sslserver", ["root-cert"], ["ncca1-cert", "ncca3-cert"], ),
    "Name constraints nested DNS name excluded");
 
-ok(verify("ee-pss-sha1-cert", "sslserver", ["root-cert"], ["ca-cert"], ),
-    "Certificate PSS signature using SHA1");
+ok(verify("ee-pss-sha1-cert", "sslserver", ["root-cert"], ["ca-cert"], "-auth_level", "0"),
+    "Accept PSS signature using SHA1 at auth level 0");
 
 ok(verify("ee-pss-sha256-cert", "sslserver", ["root-cert"], ["ca-cert"], ),
     "CA with PSS signature using SHA256");
 
-ok(!verify("ee-pss-sha1-cert", "sslserver", ["root-cert"], ["ca-cert"], "-auth_level", "2"),
-    "Reject PSS signature using SHA1 and auth level 2");
+ok(!verify("ee-pss-sha1-cert", "sslserver", ["root-cert"], ["ca-cert"], "-auth_level", "1"),
+    "Reject PSS signature using SHA1 and auth level 1");
 
 ok(verify("ee-pss-sha256-cert", "sslserver", ["root-cert"], ["ca-cert"], "-auth_level", "2"),
     "PSS signature using SHA256 and auth level 2");
-- 
2.25.1