Implement X509_get_signature_info()
authortb <tb@openbsd.org>
Wed, 28 Aug 2024 07:15:04 +0000 (07:15 +0000)
committertb <tb@openbsd.org>
Wed, 28 Aug 2024 07:15:04 +0000 (07:15 +0000)
This is a slightly strange combination of OBJ_find_sigid_algs() and the
security level API necessary because OBJ_find_sigid_algs() on its own
isn't smart enough for the special needs of RSA-PSS and EdDSA.

The API extracts the hash's NID and the pubkey's NID from the certificate's
signatureAlgorithm and invokes special handlers for RSA-PSS and EdDSA
for retrieving the corresponding information. This isn't entirely free
for RSA-PSS, but for now we don't cache this information.

The security bits calculation is a bit hand-wavy, but that's something
that comes along with this sort of numerology.

ok jsing

lib/libcrypto/Makefile
lib/libcrypto/ec/ecx_methods.c
lib/libcrypto/evp/evp_local.h
lib/libcrypto/rsa/rsa_ameth.c
lib/libcrypto/x509/x509.h
lib/libcrypto/x509/x509_siginfo.c [new file with mode: 0644]

index 30c63be..b4407d5 100644 (file)
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.202 2024/08/10 06:41:49 tb Exp $
+# $OpenBSD: Makefile,v 1.203 2024/08/28 07:15:04 tb Exp $
 
 LIB=   crypto
 LIBREBUILD=y
@@ -589,6 +589,7 @@ SRCS+= x509_purp.c
 SRCS+= x509_r2x.c
 SRCS+= x509_req.c
 SRCS+= x509_set.c
+SRCS+= x509_siginfo.c
 SRCS+= x509_skey.c
 SRCS+= x509_trs.c
 SRCS+= x509_txt.c
index 70475e8..6b5759d 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ecx_methods.c,v 1.13 2024/04/02 04:04:07 tb Exp $ */
+/*     $OpenBSD: ecx_methods.c,v 1.14 2024/08/28 07:15:04 tb Exp $ */
 /*
  * Copyright (c) 2022 Joel Sing <jsing@openbsd.org>
  *
@@ -509,6 +509,24 @@ ecx_security_bits(const EVP_PKEY *pkey)
        return 0;
 }
 
+static int
+ecx_signature_info(const X509_ALGOR *algor, int *md_nid, int *pkey_nid,
+    int *security_bits, uint32_t *flags)
+{
+       const ASN1_OBJECT *aobj;
+
+       X509_ALGOR_get0(&aobj, NULL, NULL, algor);
+       if (OBJ_obj2nid(aobj) != EVP_PKEY_ED25519)
+               return 0;
+
+       *md_nid = NID_undef;
+       *pkey_nid = NID_ED25519;
+       *security_bits = ED25519_SECURITY_BITS;
+       *flags = X509_SIG_INFO_TLS | X509_SIG_INFO_VALID;
+
+       return 1;
+}
+
 static int
 ecx_param_cmp(const EVP_PKEY *pkey1, const EVP_PKEY *pkey2)
 {
@@ -929,6 +947,8 @@ const EVP_PKEY_ASN1_METHOD ed25519_asn1_meth = {
        .pkey_bits = ecx_bits,
        .pkey_security_bits = ecx_security_bits,
 
+       .signature_info = ecx_signature_info,
+
        .param_cmp = ecx_param_cmp,
 
        .pkey_free = ecx_free,
index 3e90e06..5d541ff 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: evp_local.h,v 1.23 2024/08/22 12:24:24 tb Exp $ */
+/* $OpenBSD: evp_local.h,v 1.24 2024/08/28 07:15:04 tb Exp $ */
 /* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL
  * project 2000.
  */
@@ -112,6 +112,9 @@ struct evp_pkey_asn1_method_st {
        int (*pkey_bits)(const EVP_PKEY *pk);
        int (*pkey_security_bits)(const EVP_PKEY *pk);
 
+       int (*signature_info)(const X509_ALGOR *sig_alg, int *out_md_nid,
+           int *out_pkey_nid, int *out_security_bits, uint32_t *out_flags);
+
        int (*param_decode)(EVP_PKEY *pkey, const unsigned char **pder,
            int derlen);
        int (*param_encode)(const EVP_PKEY *pkey, unsigned char **pder);
index c722188..d7ce931 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: rsa_ameth.c,v 1.58 2024/03/17 07:10:00 tb Exp $ */
+/* $OpenBSD: rsa_ameth.c,v 1.59 2024/08/28 07:15:04 tb Exp $ */
 /* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL
  * project 2006.
  */
@@ -845,6 +845,58 @@ rsa_pss_get_param(const RSA_PSS_PARAMS *pss, const EVP_MD **pmd,
        return 1;
 }
 
+static int
+rsa_pss_signature_info(const X509_ALGOR *alg, int *out_md_nid,
+    int *out_pkey_nid, int *out_security_bits, uint32_t *out_flags)
+{
+       RSA_PSS_PARAMS *pss = NULL;
+       const ASN1_OBJECT *aobj;
+       const EVP_MD *md, *mgf1md;
+       int md_len, salt_len;
+       int md_nid = NID_undef, pkey_nid = NID_undef;
+       int security_bits = -1;
+       uint32_t flags = 0;
+
+       X509_ALGOR_get0(&aobj, NULL, NULL, alg);
+       if (OBJ_obj2nid(aobj) != EVP_PKEY_RSA_PSS)
+               goto err;
+
+       if ((pss = rsa_pss_decode(alg)) == NULL)
+               goto err;
+       if (!rsa_pss_get_param(pss, &md, &mgf1md, &salt_len))
+               goto err;
+
+       if ((md_nid = EVP_MD_type(md)) == NID_undef)
+               goto err;
+       if ((md_len = EVP_MD_size(md)) <= 0)
+               goto err;
+
+       /*
+        * RFC 8446, section 4.2.3 - restricts the digest algorithm:
+        * - it must be one of SHA256, SHA384, and SHA512;
+        * - the same digest must be used in the mask generation function;
+        * - the salt length must match the output length of the digest.
+        * XXX - consider separate flags for these checks.
+        */
+       if (md_nid == NID_sha256 || md_nid == NID_sha384 || md_nid == NID_sha512) {
+               if (md_nid == EVP_MD_type(mgf1md) && salt_len == md_len)
+                       flags |= X509_SIG_INFO_TLS;
+       }
+
+       security_bits = md_len * 4;
+       flags |= X509_SIG_INFO_VALID;
+
+       *out_md_nid = md_nid;
+       *out_pkey_nid = pkey_nid;
+       *out_security_bits = security_bits;
+       *out_flags = flags;
+
+ err:
+       RSA_PSS_PARAMS_free(pss);
+
+       return (flags & X509_SIG_INFO_VALID) != 0;
+}
+
 #ifndef OPENSSL_NO_CMS
 static int
 rsa_cms_verify(CMS_SignerInfo *si)
@@ -1216,6 +1268,8 @@ const EVP_PKEY_ASN1_METHOD rsa_pss_asn1_meth = {
        .pkey_bits = rsa_bits,
        .pkey_security_bits = rsa_security_bits,
 
+       .signature_info = rsa_pss_signature_info,
+
        .sig_print = rsa_sig_print,
 
        .pkey_free = rsa_free,
index 87bc6db..856ad19 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: x509.h,v 1.112 2024/06/12 03:55:46 tb Exp $ */
+/* $OpenBSD: x509.h,v 1.113 2024/08/28 07:15:04 tb Exp $ */
 /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
  * All rights reserved.
  *
@@ -622,6 +622,14 @@ X509 *             d2i_X509_AUX(X509 **a,const unsigned char **pp,long length);
 
 int i2d_re_X509_tbs(X509 *x, unsigned char **pp);
 
+#if defined(LIBRESSL_INTERNAL) || defined(LIBRESSL_NEXT_API)
+/* Flags returned by X509_get_signature_info(): valid and suitable for TLS. */
+#define X509_SIG_INFO_VALID    1
+#define X509_SIG_INFO_TLS      2
+int X509_get_signature_info(X509 *x, int *mdnid, int *pknid, int *secbits,
+    uint32_t *flags);
+#endif
+
 void X509_get0_signature(const ASN1_BIT_STRING **psig,
     const X509_ALGOR **palg, const X509 *x);
 int X509_get_signature_nid(const X509 *x);
diff --git a/lib/libcrypto/x509/x509_siginfo.c b/lib/libcrypto/x509/x509_siginfo.c
new file mode 100644 (file)
index 0000000..9bbb133
--- /dev/null
@@ -0,0 +1,113 @@
+/*     $OpenBSD: x509_siginfo.c,v 1.1 2024/08/28 07:15:04 tb Exp $ */
+
+/*
+ * Copyright (c) 2024 Theo Buehler <tb@openbsd.org>
+ *
+ * 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 <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/x509.h>
+
+#include "evp_local.h"
+
+#include "x509_internal.h"
+
+static int
+x509_find_sigid_algs(const X509 *x509, int *out_md_nid, int *out_pkey_nid)
+{
+       const ASN1_OBJECT *aobj;
+       int nid;
+
+       *out_md_nid = NID_undef;
+       *out_pkey_nid = NID_undef;
+
+       X509_ALGOR_get0(&aobj, NULL, NULL, x509->sig_alg);
+       if ((nid = OBJ_obj2nid(aobj)) == NID_undef)
+               return 0;
+
+       return OBJ_find_sigid_algs(nid, out_md_nid, out_pkey_nid);
+}
+
+int
+X509_get_signature_info(X509 *x509, int *out_md_nid, int *out_pkey_nid,
+    int *out_security_bits, uint32_t *out_flags)
+{
+       const EVP_MD *md;
+       int md_nid = NID_undef, pkey_nid = NID_undef, security_bits = -1;
+       uint32_t flags = 0;
+
+       if (out_md_nid != NULL)
+               *out_md_nid = md_nid;
+       if (out_pkey_nid != NULL)
+               *out_pkey_nid = pkey_nid;
+       if (out_security_bits != NULL)
+               *out_security_bits = security_bits;
+       if (out_flags != NULL)
+               *out_flags = flags;
+
+       if (!x509v3_cache_extensions(x509))
+               goto err;
+
+       if (!x509_find_sigid_algs(x509, &md_nid, &pkey_nid))
+               goto err;
+
+       /*
+        * If md_nid == NID_undef, this means we need to consult the ameth.
+        * Handlers are available for EdDSA and RSA-PSS. No other signature
+        * algorithm with NID_undef should appear in a certificate.
+        */
+       if (md_nid == NID_undef) {
+               const EVP_PKEY_ASN1_METHOD *ameth;
+
+               if ((ameth = EVP_PKEY_asn1_find(NULL, pkey_nid)) == NULL ||
+                   ameth->signature_info == NULL)
+                       goto err;
+
+               if (!ameth->signature_info(x509->sig_alg, &md_nid, &pkey_nid,
+                   &security_bits, &flags))
+                       goto err;
+
+               goto done;
+       }
+
+       /* XXX - OpenSSL 3 special cases SHA-1 (63 bits) and MD5 (39 bits). */
+       if ((md = EVP_get_digestbynid(md_nid)) == NULL)
+               goto err;
+
+       /* Assume 4 bits of collision resistance per octet. */
+       if ((security_bits = EVP_MD_size(md)) <= 0)
+               goto err;
+       security_bits *= 4;
+
+       if (md_nid == NID_sha1 || md_nid == NID_sha256 ||
+           md_nid == NID_sha384 || md_nid == NID_sha512)
+               flags |= X509_SIG_INFO_TLS;
+
+       flags |= X509_SIG_INFO_VALID;
+
+ done:
+       if (out_md_nid != NULL)
+               *out_md_nid = md_nid;
+       if (out_pkey_nid != NULL)
+               *out_pkey_nid = pkey_nid;
+       if (out_security_bits != NULL)
+               *out_security_bits = security_bits;
+       if (out_flags != NULL)
+               *out_flags = flags;
+
+ err:
+       return (flags & X509_SIG_INFO_VALID) != 0;
+}
+LCRYPTO_ALIAS(X509_get_signature_info);