Add ERR_lib_symbol_name and ERR_reason_symbol_name

CPython needs this operation. See
https://github.com/openssl/openssl/issues/19848 and
https://discuss.python.org/t/error-tables-in-the-ssl-module/25431 for
details.

In principle, our functions already return the symbol names. The
differences are:

- Our library strings say "common libcrypto routines" instead of
  "CRYPTO".
- The global reason codes say "internal error" instead of
  "INTERNAL_ERROR". (We should consider changing this.)
- The library forwarding reason codes (ERR_R_BN_LIB) say the library
  string instead of "BN_LIB". (We should consider changing this.)
- errnos report strerror
- Unknown errors return "unknown error" because we've found that
  projects tend to crash when these APIs return NULL.

The new APIs consistently return the symbol name, when available. If
unavailable (ERR_LIB_SYS's errno reasons), it returns NULL because I
assume callers would rather be able to handle that case themselves.
Hopefully this will not be as common so callers can take on this one.

Change-Id: Idd9e4b1cb5a4f64513310d8066d6bf3970722c23
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/66807
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/err/err.c b/crypto/err/err.c
index e8cf114..d74144f 100644
--- a/crypto/err/err.c
+++ b/crypto/err/err.c
@@ -428,50 +428,52 @@
   return &string_data[(*result) & 0x7fff];
 }
 
-static const char *const kLibraryNames[ERR_NUM_LIBS] = {
-    "invalid library (0)",
-    "unknown library",              // ERR_LIB_NONE
-    "system library",               // ERR_LIB_SYS
-    "bignum routines",              // ERR_LIB_BN
-    "RSA routines",                 // ERR_LIB_RSA
-    "Diffie-Hellman routines",      // ERR_LIB_DH
-    "public key routines",          // ERR_LIB_EVP
-    "memory buffer routines",       // ERR_LIB_BUF
-    "object identifier routines",   // ERR_LIB_OBJ
-    "PEM routines",                 // ERR_LIB_PEM
-    "DSA routines",                 // ERR_LIB_DSA
-    "X.509 certificate routines",   // ERR_LIB_X509
-    "ASN.1 encoding routines",      // ERR_LIB_ASN1
-    "configuration file routines",  // ERR_LIB_CONF
-    "common libcrypto routines",    // ERR_LIB_CRYPTO
-    "elliptic curve routines",      // ERR_LIB_EC
-    "SSL routines",                 // ERR_LIB_SSL
-    "BIO routines",                 // ERR_LIB_BIO
-    "PKCS7 routines",               // ERR_LIB_PKCS7
-    "PKCS8 routines",               // ERR_LIB_PKCS8
-    "X509 V3 routines",             // ERR_LIB_X509V3
-    "random number generator",      // ERR_LIB_RAND
-    "ENGINE routines",              // ERR_LIB_ENGINE
-    "OCSP routines",                // ERR_LIB_OCSP
-    "UI routines",                  // ERR_LIB_UI
-    "COMP routines",                // ERR_LIB_COMP
-    "ECDSA routines",               // ERR_LIB_ECDSA
-    "ECDH routines",                // ERR_LIB_ECDH
-    "HMAC routines",                // ERR_LIB_HMAC
-    "Digest functions",             // ERR_LIB_DIGEST
-    "Cipher functions",             // ERR_LIB_CIPHER
-    "HKDF functions",               // ERR_LIB_HKDF
-    "Trust Token functions",        // ERR_LIB_TRUST_TOKEN
-    "User defined functions",       // ERR_LIB_USER
+typedef struct library_name_st {
+  const char *str;
+  const char *symbol;
+  const char *reason_symbol;
+} LIBRARY_NAME;
+
+static const LIBRARY_NAME kLibraryNames[ERR_NUM_LIBS] = {
+    {"invalid library (0)", NULL, NULL},
+    {"unknown library", "NONE", "NONE_LIB"},
+    {"system library", "SYS", "SYS_LIB"},
+    {"bignum routines", "BN", "BN_LIB"},
+    {"RSA routines", "RSA", "RSA_LIB"},
+    {"Diffie-Hellman routines", "DH", "DH_LIB"},
+    {"public key routines", "EVP", "EVP_LIB"},
+    {"memory buffer routines", "BUF", "BUF_LIB"},
+    {"object identifier routines", "OBJ", "OBJ_LIB"},
+    {"PEM routines", "PEM", "PEM_LIB"},
+    {"DSA routines", "DSA", "DSA_LIB"},
+    {"X.509 certificate routines", "X509", "X509_LIB"},
+    {"ASN.1 encoding routines", "ASN1", "ASN1_LIB"},
+    {"configuration file routines", "CONF", "CONF_LIB"},
+    {"common libcrypto routines", "CRYPTO", "CRYPTO_LIB"},
+    {"elliptic curve routines", "EC", "EC_LIB"},
+    {"SSL routines", "SSL", "SSL_LIB"},
+    {"BIO routines", "BIO", "BIO_LIB"},
+    {"PKCS7 routines", "PKCS7", "PKCS7_LIB"},
+    {"PKCS8 routines", "PKCS8", "PKCS8_LIB"},
+    {"X509 V3 routines", "X509V3", "X509V3_LIB"},
+    {"random number generator", "RAND", "RAND_LIB"},
+    {"ENGINE routines", "ENGINE", "ENGINE_LIB"},
+    {"OCSP routines", "OCSP", "OCSP_LIB"},
+    {"UI routines", "UI", "UI_LIB"},
+    {"COMP routines", "COMP", "COMP_LIB"},
+    {"ECDSA routines", "ECDSA", "ECDSA_LIB"},
+    {"ECDH routines", "ECDH", "ECDH_LIB"},
+    {"HMAC routines", "HMAC", "HMAC_LIB"},
+    {"Digest functions", "DIGEST", "DIGEST_LIB"},
+    {"Cipher functions", "CIPHER", "CIPHER_LIB"},
+    {"HKDF functions", "HKDF", "HKDF_LIB"},
+    {"Trust Token functions", "TRUST_TOKEN", "TRUST_TOKEN_LIB"},
+    {"User defined functions", "USER", "USER_LIB"},
 };
 
 static const char *err_lib_error_string(uint32_t packed_error) {
   const uint32_t lib = ERR_GET_LIB(packed_error);
-
-  if (lib >= ERR_NUM_LIBS) {
-    return NULL;
-  }
-  return kLibraryNames[lib];
+  return lib >= ERR_NUM_LIBS ? NULL : kLibraryNames[lib].str;
 }
 
 const char *ERR_lib_error_string(uint32_t packed_error) {
@@ -479,51 +481,67 @@
   return ret == NULL ? "unknown library" : ret;
 }
 
+const char *ERR_lib_symbol_name(uint32_t packed_error) {
+  const uint32_t lib = ERR_GET_LIB(packed_error);
+  return lib >= ERR_NUM_LIBS ? NULL : kLibraryNames[lib].symbol;
+}
+
 const char *ERR_func_error_string(uint32_t packed_error) {
   return "OPENSSL_internal";
 }
 
-static const char *err_reason_error_string(uint32_t packed_error) {
+static const char *err_reason_error_string(uint32_t packed_error, int symbol) {
   const uint32_t lib = ERR_GET_LIB(packed_error);
   const uint32_t reason = ERR_GET_REASON(packed_error);
 
   if (lib == ERR_LIB_SYS) {
-    if (reason < 127) {
+    if (!symbol && reason < 127) {
       return strerror(reason);
     }
     return NULL;
   }
 
   if (reason < ERR_NUM_LIBS) {
-    return kLibraryNames[reason];
+    return symbol ? kLibraryNames[reason].reason_symbol
+                  : kLibraryNames[reason].str;
   }
 
   if (reason < 100) {
+    // TODO(davidben): All our other reason strings match the symbol name. Only
+    // the common ones differ. Should we just consistently return the symbol
+    // name?
     switch (reason) {
       case ERR_R_MALLOC_FAILURE:
-        return "malloc failure";
+        return symbol ? "MALLOC_FAILURE" : "malloc failure";
       case ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED:
-        return "function should not have been called";
+        return symbol ? "SHOULD_NOT_HAVE_BEEN_CALLED"
+                      : "function should not have been called";
       case ERR_R_PASSED_NULL_PARAMETER:
-        return "passed a null parameter";
+        return symbol ? "PASSED_NULL_PARAMETER" : "passed a null parameter";
       case ERR_R_INTERNAL_ERROR:
-        return "internal error";
+        return symbol ? "INTERNAL_ERROR" : "internal error";
       case ERR_R_OVERFLOW:
-        return "overflow";
+        return symbol ? "OVERFLOW" : "overflow";
       default:
         return NULL;
     }
   }
 
+  // Unlike OpenSSL, BoringSSL's reason strings already match symbol name, so we
+  // do not need to check |symbol|.
   return err_string_lookup(lib, reason, kOpenSSLReasonValues,
                            kOpenSSLReasonValuesLen, kOpenSSLReasonStringData);
 }
 
 const char *ERR_reason_error_string(uint32_t packed_error) {
-  const char *ret = err_reason_error_string(packed_error);
+  const char *ret = err_reason_error_string(packed_error, /*symbol=*/0);
   return ret == NULL ? "unknown error" : ret;
 }
 
+const char *ERR_reason_symbol_name(uint32_t packed_error) {
+  return err_reason_error_string(packed_error, /*symbol=*/1);
+}
+
 char *ERR_error_string(uint32_t packed_error, char *ret) {
   static char buf[ERR_ERROR_STRING_BUF_LEN];
 
@@ -550,7 +568,7 @@
   unsigned reason = ERR_GET_REASON(packed_error);
 
   const char *lib_str = err_lib_error_string(packed_error);
-  const char *reason_str = err_reason_error_string(packed_error);
+  const char *reason_str = err_reason_error_string(packed_error, /*symbol=*/0);
 
   char lib_buf[32], reason_buf[32];
   if (lib_str == NULL) {
diff --git a/crypto/err/err_test.cc b/crypto/err/err_test.cc
index 8e9f03c..75270e4 100644
--- a/crypto/err/err_test.cc
+++ b/crypto/err/err_test.cc
@@ -12,6 +12,7 @@
  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
 
+#include <errno.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -19,6 +20,7 @@
 
 #include <openssl/crypto.h>
 #include <openssl/err.h>
+#include <openssl/evp.h>
 #include <openssl/mem.h>
 
 #include "./internal.h"
@@ -249,7 +251,7 @@
 
 TEST(ErrTest, String) {
   char buf[128];
-  const uint32_t err = ERR_PACK(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+  uint32_t err = ERR_PACK(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
 
   EXPECT_STREQ(
       "error:0e000044:common libcrypto routines:OPENSSL_internal:internal "
@@ -293,6 +295,34 @@
 
   // A buffer length of zero should not touch the buffer.
   ERR_error_string_n(err, nullptr, 0);
+
+  EXPECT_STREQ(ERR_lib_error_string(err), "common libcrypto routines");
+  EXPECT_STREQ(ERR_lib_symbol_name(err), "CRYPTO");
+  EXPECT_STREQ(ERR_reason_error_string(err), "internal error");
+  EXPECT_STREQ(ERR_reason_symbol_name(err), "INTERNAL_ERROR");
+
+  // Check a normal error.
+  err = ERR_PACK(ERR_LIB_EVP, EVP_R_DECODE_ERROR);
+  EXPECT_STREQ(ERR_lib_error_string(err), "public key routines");
+  EXPECT_STREQ(ERR_lib_symbol_name(err), "EVP");
+  EXPECT_STREQ(ERR_reason_error_string(err), "DECODE_ERROR");
+  EXPECT_STREQ(ERR_reason_symbol_name(err), "DECODE_ERROR");
+
+  // Check an error that forwards to another library.
+  err = ERR_PACK(ERR_LIB_EVP, ERR_R_BN_LIB);
+  EXPECT_STREQ(ERR_lib_error_string(err), "public key routines");
+  EXPECT_STREQ(ERR_lib_symbol_name(err), "EVP");
+  EXPECT_STREQ(ERR_reason_error_string(err), "bignum routines");
+  EXPECT_STREQ(ERR_reason_symbol_name(err), "BN_LIB");
+
+  // Errors in |ERR_LIB_SYS| are |errno| values, so we don't have their symbolic
+  // names. Their human-readable strings are OS- and even locale-dependent.
+  err = ERR_PACK(ERR_LIB_SYS, ERANGE);
+  EXPECT_STREQ(ERR_lib_error_string(err), "system library");
+  EXPECT_STREQ(ERR_lib_symbol_name(err), "SYS");
+  EXPECT_NE(ERR_reason_error_string(err), nullptr);
+  EXPECT_STRNE(ERR_reason_error_string(err), "unknown error");
+  EXPECT_EQ(ERR_reason_symbol_name(err), nullptr);
 }
 
 // Error-printing functions should return something with unknown errors.
diff --git a/include/openssl/err.h b/include/openssl/err.h
index 0ec71b1..ab181f9 100644
--- a/include/openssl/err.h
+++ b/include/openssl/err.h
@@ -244,6 +244,19 @@
 // |packed_error|, or a placeholder string if the reason is unrecognized.
 OPENSSL_EXPORT const char *ERR_reason_error_string(uint32_t packed_error);
 
+// ERR_lib_symbol_name returns the symbol name of library that generated
+// |packed_error|, or NULL if unrecognized. For example, an error from
+// |ERR_LIB_EVP| would return "EVP".
+OPENSSL_EXPORT const char *ERR_lib_symbol_name(uint32_t packed_error);
+
+// ERR_reason_symbol_name returns the symbol name of the reason for
+// |packed_error|, or NULL if unrecognized. For example, |ERR_R_INTERNAL_ERROR|
+// would return "INTERNAL_ERROR".
+//
+// Errors from the |ERR_LIB_SYS| library are typically |errno| values and will
+// return NULL. User-defined errors will also return NULL.
+OPENSSL_EXPORT const char *ERR_reason_symbol_name(uint32_t packed_error);
+
 // ERR_print_errors_callback_t is the type of a function used by
 // |ERR_print_errors_cb|. It takes a pointer to a human readable string (and
 // its length) that describes an entry in the error queue. The |ctx| argument