From 174c45b3d50ddfbf67c208d1b5b9bac42d70fcaf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com>
Date: Sun, 10 Nov 2024 18:22:52 -0500
Subject: [PATCH] tlsvalidator: add missing certificate details

Change-Id: I5094741acc1b395947bac34321cfc05b3e5328ae
---
 src/connectivity/security/tlsvalidator.cpp | 149 +++++++++++++++++----
 src/connectivity/security/tlsvalidator.h   |  12 ++
 src/enumclass_utils.h                      |   8 +-
 src/jami/security_const.h                  |   6 +
 4 files changed, 145 insertions(+), 30 deletions(-)

diff --git a/src/connectivity/security/tlsvalidator.cpp b/src/connectivity/security/tlsvalidator.cpp
index 09778035bf..93a1484607 100644
--- a/src/connectivity/security/tlsvalidator.cpp
+++ b/src/connectivity/security/tlsvalidator.cpp
@@ -27,6 +27,7 @@
 #include "config.h"
 #endif
 
+#include <opendht/infohash.h> // for toHex
 #include <dhtnet/certstore.h>
 
 #include "fileutils.h"
@@ -116,7 +117,9 @@ const CallbackMatrix1D<TlsValidator::CertificateDetails, TlsValidator, TlsValida
         /* SERIAL_NUMBER                */ &TlsValidator::getSerialNumber,
         /* ISSUER                       */ &TlsValidator::getIssuer,
         /* SUBJECT_KEY_ALGORITHM        */ &TlsValidator::getSubjectKeyAlgorithm,
+        /* SUBJECT_KEY                  */ &TlsValidator::getSubjectKey,
         /* CN                           */ &TlsValidator::getCN,
+        /* UID                          */ &TlsValidator::getUID,
         /* N                            */ &TlsValidator::getN,
         /* O                            */ &TlsValidator::getO,
         /* SIGNATURE_ALGORITHM          */ &TlsValidator::getSignatureAlgorithm,
@@ -124,6 +127,10 @@ const CallbackMatrix1D<TlsValidator::CertificateDetails, TlsValidator, TlsValida
         /* SHA1_FINGERPRINT             */ &TlsValidator::getSha1Fingerprint,
         /* PUBLIC_KEY_ID                */ &TlsValidator::getPublicKeyId,
         /* ISSUER_DN                    */ &TlsValidator::getIssuerDN,
+        /* ISSUER_CN                    */ &TlsValidator::getIssuerCN,
+        /* ISSUER_UID                   */ &TlsValidator::getIssuerUID,
+        /* ISSUER_N                     */ &TlsValidator::getIssuerN,
+        /* ISSUER_O                     */ &TlsValidator::getIssuerO,
         /* NEXT_EXPECTED_UPDATE_DATE    */ &TlsValidator::getIssuerDN, // TODO
         /* OUTGOING_SERVER              */ &TlsValidator::outgoingServer,
         /* IS_CA                        */ &TlsValidator::isCA,
@@ -189,7 +196,9 @@ const EnumClassNames<TlsValidator::CertificateDetails> TlsValidator::Certificate
     /* SERIAL_NUMBER                */ libjami::Certificate::DetailsNames::SERIAL_NUMBER,
     /* ISSUER                       */ libjami::Certificate::DetailsNames::ISSUER,
     /* SUBJECT_KEY_ALGORITHM        */ libjami::Certificate::DetailsNames::SUBJECT_KEY_ALGORITHM,
+    /* SUBJECT_KEY                  */ libjami::Certificate::DetailsNames::SUBJECT_KEY,
     /* CN                           */ libjami::Certificate::DetailsNames::CN,
+    /* UID                          */ libjami::Certificate::DetailsNames::UID,
     /* N                            */ libjami::Certificate::DetailsNames::N,
     /* O                            */ libjami::Certificate::DetailsNames::O,
     /* SIGNATURE_ALGORITHM          */ libjami::Certificate::DetailsNames::SIGNATURE_ALGORITHM,
@@ -197,6 +206,10 @@ const EnumClassNames<TlsValidator::CertificateDetails> TlsValidator::Certificate
     /* SHA1_FINGERPRINT             */ libjami::Certificate::DetailsNames::SHA1_FINGERPRINT,
     /* PUBLIC_KEY_ID                */ libjami::Certificate::DetailsNames::PUBLIC_KEY_ID,
     /* ISSUER_DN                    */ libjami::Certificate::DetailsNames::ISSUER_DN,
+    /* ISSUER_CN                    */ libjami::Certificate::DetailsNames::ISSUER_CN,
+    /* ISSUER_UID                   */ libjami::Certificate::DetailsNames::ISSUER_UID,
+    /* ISSUER_N                     */ libjami::Certificate::DetailsNames::ISSUER_N,
+    /* ISSUER_O                     */ libjami::Certificate::DetailsNames::ISSUER_O,
     /* NEXT_EXPECTED_UPDATE_DATE    */ libjami::Certificate::DetailsNames::NEXT_EXPECTED_UPDATE_DATE,
     /* OUTGOING_SERVER              */ libjami::Certificate::DetailsNames::OUTGOING_SERVER,
     /* IS_CA                        */ libjami::Certificate::DetailsNames::IS_CA,
@@ -311,7 +324,7 @@ TlsValidator::getStringValue(const TlsValidator::CertificateCheck check,
     case CheckValues::PASSED:
     case CheckValues::FAILED:
     case CheckValues::UNSUPPORTED:
-        return CheckValuesNames[result.first];
+        return std::string(CheckValuesNames[result.first]);
     case CheckValues::ISO_DATE:
         // TODO validate date
         // return CheckValues::FAILED;
@@ -322,7 +335,7 @@ TlsValidator::getStringValue(const TlsValidator::CertificateCheck check,
         return result.second;
     default:
         // Consider any other case (such as forced int->CheckValues casting) as failed
-        return CheckValuesNames[CheckValues::FAILED];
+        return std::string(CheckValuesNames[CheckValues::FAILED]);
     };
 }
 
@@ -339,7 +352,7 @@ TlsValidator::isValid(bool verbose)
         if (enforcedCheckType[check] == CheckValuesType::BOOLEAN) {
             if (((this->*(checkCallback[check]))()).first == CheckValues::FAILED) {
                 if (verbose)
-                    JAMI_WARN("Check failed: %s", CertificateCheckNames[check]);
+                    JAMI_WARNING("Check failed: {}", CertificateCheckNames[check]);
                 return false;
             }
         }
@@ -356,11 +369,11 @@ TlsValidator::getSerializedChecks()
     std::map<std::string, std::string> ret;
     if (not certificateFound_) {
         // Instead of checking `certificateFound` everywhere, handle it once
-        ret[CertificateCheckNames[CertificateCheck::EXIST]] = getStringValue(CertificateCheck::EXIST,
+        ret[std::string(CertificateCheckNames[CertificateCheck::EXIST])] = getStringValue(CertificateCheck::EXIST,
                                                                              exist());
     } else {
         for (const CertificateCheck check : Matrix0D<CertificateCheck>())
-            ret[CertificateCheckNames[check]] = getStringValue(check,
+            ret[std::string(CertificateCheckNames[check])] = getStringValue(check,
                                                                (this->*(checkCallback[check]))());
     }
 
@@ -375,7 +388,7 @@ TlsValidator::getSerializedDetails()
 {
     std::map<std::string, std::string> ret;
     if (certificateFound_) {
-        for (const CertificateDetails det : Matrix0D<CertificateDetails>()) {
+        for (const CertificateDetails& det : Matrix0D<CertificateDetails>()) {
             const CheckResult r = (this->*(getterCallback[det]))();
             std::string val;
             // TODO move this to a fuction
@@ -393,8 +406,8 @@ TlsValidator::getSerializedDetails()
             default:
                 val = r.second;
                 break;
-            };
-            ret[CertificateDetailsNames[det]] = val;
+            }
+            ret[std::string(CertificateDetailsNames[det])] = val;
         }
     }
     return ret;
@@ -414,21 +427,6 @@ checkError(int err, char* copy_buffer, size_t size)
                                                        : "");
 }
 
-/**
- * Some fields, such as the binary signature need to be converted to an
- * ASCII-hexadecimal representation before being sent to DBus as it will cause the
- * process to assert
- */
-static std::string
-binaryToHex(const uint8_t* input, size_t input_sz)
-{
-    std::ostringstream ret;
-    ret << std::hex;
-    for (size_t i = 0; i < input_sz; i++)
-        ret << std::setfill('0') << std::setw(2) << (unsigned) input[i];
-    return ret.str();
-}
-
 /**
  * Convert a time_t to an ISO date string
  */
@@ -451,7 +449,7 @@ checkBinaryError(int err, char* copy_buffer, size_t resultSize)
 {
     if (err == GNUTLS_E_SUCCESS)
         return TlsValidator::CheckResult(TlsValidator::CheckValues::CUSTOM,
-                                         binaryToHex(reinterpret_cast<uint8_t*>(copy_buffer),
+                                         dht::toHex(reinterpret_cast<uint8_t*>(copy_buffer),
                                                      resultSize));
     else
         return TlsValidator::CheckResult(TlsValidator::CheckValues::UNSUPPORTED, "");
@@ -1185,8 +1183,9 @@ TlsValidator::getIssuer()
 TlsValidator::CheckResult
 TlsValidator::getSubjectKeyAlgorithm()
 {
+    unsigned key_length = 0;
     gnutls_pk_algorithm_t algo = (gnutls_pk_algorithm_t)
-        gnutls_x509_crt_get_pk_algorithm(x509crt_->cert, nullptr);
+        gnutls_x509_crt_get_pk_algorithm(x509crt_->cert, &key_length);
 
     if (algo < 0)
         return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
@@ -1196,7 +1195,25 @@ TlsValidator::getSubjectKeyAlgorithm()
     if (!name)
         return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
 
-    return TlsValidator::CheckResult(CheckValues::CUSTOM, name);
+    if (key_length)
+        return TlsValidator::CheckResult(CheckValues::CUSTOM, fmt::format("{} ({} bits)", name, key_length));
+    else
+        return TlsValidator::CheckResult(CheckValues::CUSTOM, name);
+}
+
+/**
+ * The subject public key
+ */
+TlsValidator::CheckResult
+TlsValidator::getSubjectKey()
+{
+    try {
+        std::vector<uint8_t> data;
+        x509crt_->getPublicKey().pack(data);
+        return TlsValidator::CheckResult(CheckValues::CUSTOM, dht::toHex(data));
+    } catch (const dht::crypto::CryptoException& e) {
+        return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, e.what());
+    }
 }
 
 /**
@@ -1216,6 +1233,22 @@ TlsValidator::getCN()
     return checkError(err, copy_buffer, resultSize);
 }
 
+/**
+ * The 'UID' section of a DN (RFC4514)
+ */
+TlsValidator::CheckResult
+TlsValidator::getUID()
+{
+    size_t resultSize = sizeof(copy_buffer);
+    int err = gnutls_x509_crt_get_dn_by_oid(x509crt_->cert,
+                                            GNUTLS_OID_LDAP_UID,
+                                            0,
+                                            0,
+                                            copy_buffer,
+                                            &resultSize);
+    return checkError(err, copy_buffer, resultSize);
+}
+
 /**
  * The 'N' section of a DN (RFC4514)
  */
@@ -1328,6 +1361,70 @@ TlsValidator::getIssuerDN()
     return checkError(err, copy_buffer, resultSize);
 }
 
+/**
+ *  If the certificate is not self signed, return the issuer CN
+ */
+TlsValidator::CheckResult
+TlsValidator::getIssuerCN()
+{
+    size_t resultSize = sizeof(copy_buffer);
+    int err = gnutls_x509_crt_get_issuer_dn_by_oid(x509crt_->cert,
+                                                   GNUTLS_OID_X520_COMMON_NAME,
+                                                   0,
+                                                   0,
+                                                   copy_buffer,
+                                                   &resultSize);
+    return checkError(err, copy_buffer, resultSize);
+}
+
+/**
+ *  If the certificate is not self signed, return the issuer UID
+ */
+TlsValidator::CheckResult
+TlsValidator::getIssuerUID()
+{
+    size_t resultSize = sizeof(copy_buffer);
+    int err = gnutls_x509_crt_get_issuer_dn_by_oid(x509crt_->cert,
+                                                   GNUTLS_OID_LDAP_UID,
+                                                   0,
+                                                   0,
+                                                   copy_buffer,
+                                                   &resultSize);
+    return checkError(err, copy_buffer, resultSize);
+}
+
+/**
+ *  If the certificate is not self signed, return the issuer N
+ */
+TlsValidator::CheckResult
+TlsValidator::getIssuerN()
+{
+    size_t resultSize = sizeof(copy_buffer);
+    int err = gnutls_x509_crt_get_issuer_dn_by_oid(x509crt_->cert,
+                                                   GNUTLS_OID_X520_NAME,
+                                                   0,
+                                                   0,
+                                                   copy_buffer,
+                                                   &resultSize);
+    return checkError(err, copy_buffer, resultSize);
+}
+
+/**
+ *  If the certificate is not self signed, return the issuer O
+ */
+TlsValidator::CheckResult
+TlsValidator::getIssuerO()
+{
+    size_t resultSize = sizeof(copy_buffer);
+    int err = gnutls_x509_crt_get_issuer_dn_by_oid(x509crt_->cert,
+                                                   GNUTLS_OID_X520_ORGANIZATION_NAME,
+                                                   0,
+                                                   0,
+                                                   copy_buffer,
+                                                   &resultSize);
+    return checkError(err, copy_buffer, resultSize);
+}
+
 /**
  * Get the expiration date
  *
diff --git a/src/connectivity/security/tlsvalidator.h b/src/connectivity/security/tlsvalidator.h
index 7a1bfd0656..f2c89414fe 100644
--- a/src/connectivity/security/tlsvalidator.h
+++ b/src/connectivity/security/tlsvalidator.h
@@ -122,7 +122,9 @@ public:
         SERIAL_NUMBER,
         ISSUER,
         SUBJECT_KEY_ALGORITHM,
+        SUBJECT_KEY,
         CN,
+        UID,
         N,
         O,
         SIGNATURE_ALGORITHM,
@@ -130,6 +132,10 @@ public:
         SHA1_FINGERPRINT,
         PUBLIC_KEY_ID,
         ISSUER_DN,
+        ISSUER_CN,
+        ISSUER_UID,
+        ISSUER_N,
+        ISSUER_O,
         NEXT_EXPECTED_UPDATE_DATE,
         OUTGOING_SERVER, /** The hostname/outgoing server used for this certificate               */
         IS_CA,
@@ -233,7 +239,9 @@ public:
     CheckResult getSerialNumber();
     CheckResult getIssuer();
     CheckResult getSubjectKeyAlgorithm();
+    CheckResult getSubjectKey();
     CheckResult getCN();
+    CheckResult getUID();
     CheckResult getN();
     CheckResult getO();
     CheckResult getSignatureAlgorithm();
@@ -241,6 +249,10 @@ public:
     CheckResult getSha1Fingerprint();
     CheckResult getPublicKeyId();
     CheckResult getIssuerDN();
+    CheckResult getIssuerCN();
+    CheckResult getIssuerUID();
+    CheckResult getIssuerN();
+    CheckResult getIssuerO();
     CheckResult outgoingServer();
     CheckResult isCA();
 
diff --git a/src/enumclass_utils.h b/src/enumclass_utils.h
index 76418bf7cc..02fc4ac60a 100644
--- a/src/enumclass_utils.h
+++ b/src/enumclass_utils.h
@@ -51,7 +51,7 @@ enum_class_size()
 template<class Row, typename Value, typename A = Value>
 struct Matrix1D
 {
-    Matrix1D(std::initializer_list<std::initializer_list<Value>> s);
+    constexpr Matrix1D(std::initializer_list<std::initializer_list<Value>> s);
 
     // Row is a built-in type ("int" by default)
     Value operator[](Row v);
@@ -136,7 +136,7 @@ struct Matrix0D
  * A helper to type to match serializable string to enum elements
  */
 template<class Row>
-using EnumClassNames = Matrix1D<Row, const char*>;
+using EnumClassNames = Matrix1D<Row, std::string_view>;
 
 /**
  * Create a matrix type with 2 enum class dimensions M[I,J] = V
@@ -171,14 +171,14 @@ using CallbackMatrix2D = Matrix2D<Row, Column, void (Class::*)(Args... args)>;
  */
 
 template<class Row, typename Value, typename Accessor>
-Matrix1D<Row, Value, Accessor>::Matrix1D(std::initializer_list<std::initializer_list<Value>> s)
+constexpr Matrix1D<Row, Value, Accessor>::Matrix1D(std::initializer_list<std::initializer_list<Value>> s)
     : data_(*std::begin(s))
 {
     static_assert(std::is_enum<Row>(), "Row has to be an enum class");
     static_assert((int) Row::COUNT__ > 0, "Row need a COUNT__ element");
 
     // FIXME C++14, use static_assert and make the ctor constexpr
-    assert(std::begin(s)->size()
+    assert(s.begin()->size()
            == enum_class_size<Row>()); //,"Matrix row have to match the enum class size");
 }
 
diff --git a/src/jami/security_const.h b/src/jami/security_const.h
index 28a52ee2df..618aa8ec6a 100644
--- a/src/jami/security_const.h
+++ b/src/jami/security_const.h
@@ -76,7 +76,9 @@ constexpr static char VERSION_NUMBER[] = "VERSION_NUMBER";
 constexpr static char SERIAL_NUMBER[] = "SERIAL_NUMBER";
 constexpr static char ISSUER[] = "ISSUER";
 constexpr static char SUBJECT_KEY_ALGORITHM[] = "SUBJECT_KEY_ALGORITHM";
+constexpr static char SUBJECT_KEY[] = "SUBJECT_KEY";
 constexpr static char CN[] = "CN";
+constexpr static char UID[] = "UID";
 constexpr static char N[] = "N";
 constexpr static char O[] = "O";
 constexpr static char SIGNATURE_ALGORITHM[] = "SIGNATURE_ALGORITHM";
@@ -84,6 +86,10 @@ constexpr static char MD5_FINGERPRINT[] = "MD5_FINGERPRINT";
 constexpr static char SHA1_FINGERPRINT[] = "SHA1_FINGERPRINT";
 constexpr static char PUBLIC_KEY_ID[] = "PUBLIC_KEY_ID";
 constexpr static char ISSUER_DN[] = "ISSUER_DN";
+constexpr static char ISSUER_CN[] = "ISSUER_CN";
+constexpr static char ISSUER_UID[] = "ISSUER_UID";
+constexpr static char ISSUER_N[] = "ISSUER_N";
+constexpr static char ISSUER_O[] = "ISSUER_O";
 constexpr static char NEXT_EXPECTED_UPDATE_DATE[] = "NEXT_EXPECTED_UPDATE_DATE";
 constexpr static char OUTGOING_SERVER[] = "OUTGOING_SERVER";
 constexpr static char IS_CA[] = "IS_CA";
-- 
GitLab