"Fossies" - the Fresh Open Source Software Archive

Member "netxms-3.8.166/src/libnetxms/cert.cpp" (23 Feb 2021, 15039 Bytes) of package /linux/misc/netxms-3.8.166.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "cert.cpp" see the Fossies "Dox" file reference documentation.

    1 /*
    2 ** NetXMS - Network Management System
    3 ** NetXMS Foundation Library
    4 ** Copyright (C) 2003-2020 Victor Kirhenshtein
    5 **
    6 ** This program is free software; you can redistribute it and/or modify
    7 ** it under the terms of the GNU Lesser General Public License as published
    8 ** by the Free Software Foundation; either version 3 of the License, or
    9 ** (at your option) any later version.
   10 **
   11 ** This program is distributed in the hope that it will be useful,
   12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 ** GNU General Public License for more details.
   15 **
   16 ** You should have received a copy of the GNU Lesser General Public License
   17 ** along with this program; if not, write to the Free Software
   18 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   19 **
   20 ** File: cert.cpp
   21 **
   22 **/
   23 
   24 #include "libnetxms.h"
   25 #include <nxcrypto.h>
   26 #include <nxstat.h>
   27 #include <openssl/asn1t.h>
   28 #include <openssl/x509v3.h>
   29 
   30 #define DEBUG_TAG    _T("crypto.cert")
   31 
   32 #ifdef _WITH_ENCRYPTION
   33 
   34 #if OPENSSL_VERSION_NUMBER < 0x10100000L
   35 static inline const ASN1_TIME *X509_get0_notAfter(const X509 *x)
   36 {
   37    return X509_get_notAfter(x);
   38 }
   39 static inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *s)
   40 {
   41    return s->data;
   42 }
   43 #endif
   44 
   45 /**
   46  * Get field from X.509 name
   47  */
   48 static bool GetX509NameField(X509_NAME *name, int nid, TCHAR *buffer, size_t size)
   49 {
   50    int idx = X509_NAME_get_index_by_NID(name, nid, -1);
   51    if (idx == -1)
   52       return false;
   53 
   54    X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, idx);
   55    if (entry == nullptr)
   56       return false;
   57 
   58    ASN1_STRING *data = X509_NAME_ENTRY_get_data(entry);
   59    if (data == nullptr)
   60       return false;
   61 
   62    unsigned char *text;
   63    ASN1_STRING_to_UTF8(&text, data);
   64 #ifdef UNICODE
   65    MultiByteToWideChar(CP_UTF8, 0, (char *)text, -1, buffer, (int)size);
   66 #else
   67    utf8_to_mb((char *)text, -1, buffer, (int)size);
   68 #endif
   69    buffer[size - 1] = 0;
   70    OPENSSL_free(text);
   71    return true;
   72 }
   73 
   74 /**
   75  * Get single field from certificate subject
   76  */
   77 bool LIBNETXMS_EXPORTABLE GetCertificateSubjectField(const X509 *cert, int nid, TCHAR *buffer, size_t size)
   78 {
   79 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
   80    X509_NAME *subject = X509_get_subject_name(cert);
   81 #else
   82    X509_NAME *subject = X509_get_subject_name(const_cast<X509*>(cert));
   83 #endif
   84    return (subject != nullptr) ? GetX509NameField(subject, nid, buffer, size) : false;
   85 }
   86 
   87 /**
   88  * Get single field from certificate issuer
   89  */
   90 bool LIBNETXMS_EXPORTABLE GetCertificateIssuerField(const X509 *cert, int nid, TCHAR *buffer, size_t size)
   91 {
   92 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
   93    X509_NAME *issuer = X509_get_issuer_name(cert);
   94 #else
   95    X509_NAME *issuer = X509_get_issuer_name(const_cast<X509*>(cert));
   96 #endif
   97    return (issuer != nullptr) ? GetX509NameField(issuer, nid, buffer, size) : false;
   98 }
   99 
  100 /**
  101  * Get CN from certificate
  102  */
  103 bool LIBNETXMS_EXPORTABLE GetCertificateCN(const X509 *cert, TCHAR *buffer, size_t size)
  104 {
  105    return GetCertificateSubjectField(cert, NID_commonName, buffer, size);
  106 }
  107 
  108 /**
  109  * Get OU from certificate
  110  */
  111 bool LIBNETXMS_EXPORTABLE GetCertificateOU(const X509 *cert, TCHAR *buffer, size_t size)
  112 {
  113    return GetCertificateSubjectField(cert, NID_organizationalUnitName, buffer, size);
  114 }
  115 
  116 /**
  117  * Get subject/issuer/... string (C=XX,ST=state,L=locality,O=org,OU=unit,CN=cn) from certificate
  118  */
  119 template<bool (*GetField)(const X509*, int, TCHAR*, size_t)> static inline String GetCertificateNameString(const X509 *cert)
  120 {
  121    StringBuffer text;
  122    TCHAR buffer[256];
  123    if (GetField(cert, NID_countryName, buffer, 256))
  124    {
  125       text.append(_T("C="));
  126       text.append(buffer);
  127    }
  128    if (GetField(cert, NID_stateOrProvinceName, buffer, 256))
  129    {
  130       if (!text.isEmpty())
  131          text.append(_T(','));
  132       text.append(_T("ST="));
  133       text.append(buffer);
  134    }
  135    if (GetField(cert, NID_localityName, buffer, 256))
  136    {
  137       if (!text.isEmpty())
  138          text.append(_T(','));
  139       text.append(_T("L="));
  140       text.append(buffer);
  141    }
  142    if (GetField(cert, NID_organizationName, buffer, 256))
  143    {
  144       if (!text.isEmpty())
  145          text.append(_T(','));
  146       text.append(_T("O="));
  147       text.append(buffer);
  148    }
  149    if (GetField(cert, NID_organizationalUnitName, buffer, 256))
  150    {
  151       if (!text.isEmpty())
  152          text.append(_T(','));
  153       text.append(_T("OU="));
  154       text.append(buffer);
  155    }
  156    if (GetField(cert, NID_commonName, buffer, 256))
  157    {
  158       if (!text.isEmpty())
  159          text.append(_T(','));
  160       text.append(_T("CN="));
  161       text.append(buffer);
  162    }
  163    return text;
  164 }
  165 
  166 /**
  167  * Get subject string (C=XX,ST=state,L=locality,O=org,OU=unit,CN=cn) from certificate
  168  */
  169 String LIBNETXMS_EXPORTABLE GetCertificateSubjectString(const X509 *cert)
  170 {
  171    return GetCertificateNameString<GetCertificateSubjectField>(cert);
  172 }
  173 
  174 /**
  175  * Get issuer string (C=XX,ST=state,L=locality,O=org,OU=unit,CN=cn) from certificate
  176  */
  177 String LIBNETXMS_EXPORTABLE GetCertificateIssuerString(const X509 *cert)
  178 {
  179    return GetCertificateNameString<GetCertificateIssuerField>(cert);
  180 }
  181 
  182 /**
  183  * Get certificate expiration time
  184  */
  185 time_t LIBNETXMS_EXPORTABLE GetCertificateExpirationTime(const X509 *cert)
  186 {
  187 #if OPENSSL_VERSION_NUMBER >= 0x10101000L
  188    struct tm expTime;
  189    ASN1_TIME_to_tm(X509_get0_notAfter(cert), &expTime);
  190    return timegm(&expTime);
  191 #elif OPENSSL_VERSION_NUMBER >= 0x10100000L
  192    ASN1_TIME *epoch = ASN1_TIME_set(nullptr, static_cast<time_t>(0));
  193    int days, seconds;
  194    ASN1_TIME_diff(&days, &seconds, epoch, X509_get0_notAfter(cert));
  195    ASN1_TIME_free(epoch);
  196    return static_cast<time_t>(days) * 86400 + seconds;
  197 #else
  198    struct tm expTime;
  199    memset(&expTime, 0, sizeof(expTime));
  200 
  201    const ASN1_TIME *atime = X509_get0_notAfter(cert);
  202 
  203    size_t i = 0;
  204    const char *s = reinterpret_cast<const char*>(atime->data);
  205    if (atime->type == V_ASN1_UTCTIME) /* two digit year */
  206    {
  207       if (atime->length < 12)
  208          return 0;  // Invalid format
  209       expTime.tm_year = (s[i] - '0') * 10 + (s[i + 1] - '0');
  210       if (expTime.tm_year < 70)
  211          expTime.tm_year += 100;
  212       i += 2;
  213    }
  214    else if (atime->type == V_ASN1_GENERALIZEDTIME) /* four digit year */
  215    {
  216       if (atime->length < 14)
  217          return 0;  // Invalid format
  218       expTime.tm_year = (s[i] - '0') * 1000 + (s[i + 1] - '0') * 100 + (s[i + 2] - '0') * 10 + (s[i + 3] - '0');
  219       expTime.tm_year -= 1900;
  220       i += 4;
  221    }
  222    else
  223    {
  224       return 0;  // Unknown type
  225    }
  226    expTime.tm_mon = (s[i] - '0') * 10 + (s[i + 1] - '0') - 1; // -1 since January is 0 not 1.
  227    i += 2;
  228    expTime.tm_mday = (s[i] - '0') * 10 + (s[i + 1] - '0');
  229    i += 2;
  230    expTime.tm_hour = (s[i] - '0') * 10 + (s[i + 1] - '0');
  231    i += 2;
  232    expTime.tm_min  = (s[i] - '0') * 10 + (s[i + 1] - '0');
  233    i += 2;
  234    expTime.tm_sec  = (s[i] - '0') * 10 + (s[i + 1] - '0');
  235    return timegm(&expTime);
  236 #endif
  237 }
  238 
  239 /**
  240  * Certificate template extension structure as described in Microsoft documentation
  241  * https://docs.microsoft.com/en-us/windows/win32/api/certenroll/nn-certenroll-ix509extensiontemplate
  242  *
  243  * ----------------------------------------------------------------------
  244  * -- CertificateTemplate
  245  * -- XCN_OID_CERTIFICATE_TEMPLATE (1.3.6.1.4.1.311.21.7)
  246  * ----------------------------------------------------------------------
  247  *
  248  * CertificateTemplate ::= SEQUENCE
  249  * {
  250  * templateID              EncodedObjectID,
  251  * templateMajorVersion    TemplateVersion,
  252  * templateMinorVersion    TemplateVersion OPTIONAL
  253  * }
  254  */
  255 struct CERTIFICATE_TEMPLATE
  256 {
  257    ASN1_OBJECT *templateId;
  258    ASN1_INTEGER *templateMajorVersion;
  259    ASN1_INTEGER *templateMinorVersion;
  260 };
  261 
  262 /**
  263  * Define ASN.1 sequence for CERTIFICATE_TEMPLATE
  264  */
  265 ASN1_NDEF_SEQUENCE(CERTIFICATE_TEMPLATE) =
  266 {
  267    ASN1_SIMPLE(CERTIFICATE_TEMPLATE, templateId, ASN1_OBJECT),
  268    ASN1_SIMPLE(CERTIFICATE_TEMPLATE, templateMajorVersion, ASN1_INTEGER),
  269    ASN1_SIMPLE(CERTIFICATE_TEMPLATE, templateMinorVersion, ASN1_INTEGER)
  270 } ASN1_NDEF_SEQUENCE_END(CERTIFICATE_TEMPLATE)
  271 
  272 /**
  273  * Function implementations for CERTIFICATE_TEMPLATE structure
  274  */
  275 IMPLEMENT_ASN1_FUNCTIONS(CERTIFICATE_TEMPLATE)
  276 
  277 /**
  278  * Get certificate's template ID (extension 1.3.6.1.4.1.311.21.7)
  279  */
  280 String LIBNETXMS_EXPORTABLE GetCertificateTemplateId(const X509 *cert)
  281 {
  282    ASN1_OBJECT *oid = OBJ_txt2obj("1.3.6.1.4.1.311.21.7", 1);
  283 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
  284    int index = X509_get_ext_by_OBJ(cert, oid, -1);
  285 #else
  286    int index = X509_get_ext_by_OBJ(const_cast<X509*>(cert), oid, -1);
  287 #endif
  288    ASN1_OBJECT_free(oid);
  289    if (index == -1)
  290       return String();
  291 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
  292    X509_EXTENSION *ext = X509_get_ext(cert, index);
  293 #else
  294    X509_EXTENSION *ext = X509_get_ext(const_cast<X509*>(cert), index);
  295 #endif
  296    if (ext == nullptr)
  297       return String();
  298    ASN1_STRING *value = X509_EXTENSION_get_data(ext);
  299    if (value == nullptr)
  300       return String();
  301    const unsigned char *bytes = ASN1_STRING_get0_data(value);
  302    CERTIFICATE_TEMPLATE *t = d2i_CERTIFICATE_TEMPLATE(nullptr, &bytes, ASN1_STRING_length(value));
  303    if (t == nullptr)
  304       return String();
  305    char oidA[256];
  306    OBJ_obj2txt(oidA, 256, t->templateId, 1);
  307    CERTIFICATE_TEMPLATE_free(t);
  308 #ifdef UNICODE
  309    WCHAR oidW[256];
  310    mb_to_wchar(oidA, -1, oidW, 256);
  311    return String(oidW);
  312 #else
  313    return String(oidA);
  314 #endif
  315 }
  316 
  317 /**
  318  * Get HTTP/HTTPS CRL distribution point from certificate
  319  */
  320 String LIBNETXMS_EXPORTABLE GetCertificateCRLDistributionPoint(const X509 *cert)
  321 {
  322 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
  323    STACK_OF(DIST_POINT) *dps = static_cast<STACK_OF(DIST_POINT)*>(X509_get_ext_d2i(cert, NID_crl_distribution_points, nullptr, nullptr));
  324 #else
  325    STACK_OF(DIST_POINT) *dps = static_cast<STACK_OF(DIST_POINT)*>(X509_get_ext_d2i(const_cast<X509*>(cert), NID_crl_distribution_points, nullptr, nullptr));
  326 #endif
  327    if (dps == nullptr)
  328       return String();
  329 
  330    StringBuffer result;
  331    for (int i = 0; i < sk_DIST_POINT_num(dps); i++)
  332    {
  333       DIST_POINT *dp = sk_DIST_POINT_value(dps, i);
  334       if (dp->distpoint->type == 0)
  335       {
  336          GENERAL_NAMES *names = dp->distpoint->name.fullname;
  337          for (int j = 0; j < sk_GENERAL_NAME_num(names); j++)
  338          {
  339             GENERAL_NAME *n = sk_GENERAL_NAME_value(names, j);
  340             if (n->type == GEN_URI)
  341             {
  342                ASN1_IA5STRING *uri = n->d.uniformResourceIdentifier;
  343                int l = ASN1_STRING_length(uri);
  344                if (l > 7)
  345                {
  346                   const unsigned char *data = ASN1_STRING_get0_data(uri);
  347                   if (!memcmp(data, "http:", 5) || !memcmp(data, "https:", 6))
  348                   {
  349                      result.appendMBString(reinterpret_cast<const char*>(data), l, CP_UTF8);
  350                      break;
  351                   }
  352                }
  353             }
  354          }
  355       }
  356    }
  357    sk_DIST_POINT_free(dps);
  358    return result;
  359 }
  360 
  361 /**
  362  * Create default certificate store with trusted certificates
  363  */
  364 X509_STORE LIBNETXMS_EXPORTABLE *CreateTrustedCertificatesStore(const StringSet& trustedCertificates, bool useSystemStore)
  365 {
  366    X509_STORE *store = X509_STORE_new();
  367    if (store == nullptr)
  368    {
  369       nxlog_debug_tag(DEBUG_TAG, 3, _T("CreateTrustedCertificatesStore: cannot create certificate store"));
  370       return nullptr;
  371    }
  372 
  373    X509_LOOKUP *dirLookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
  374    X509_LOOKUP *fileLookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
  375 
  376    if (!trustedCertificates.isEmpty())
  377    {
  378       auto it = trustedCertificates.constIterator();
  379       while(it->hasNext())
  380       {
  381          const TCHAR *trustedRoot = it->next();
  382          NX_STAT_STRUCT st;
  383          if (CALL_STAT(trustedRoot, &st) != 0)
  384             continue;
  385 
  386 #ifdef UNICODE
  387          char mbTrustedRoot[MAX_PATH];
  388          WideCharToMultiByteSysLocale(trustedRoot, mbTrustedRoot, MAX_PATH);
  389 #else
  390          const char *mbTrustedRoot = trustedRoot;
  391 #endif
  392          int added = S_ISDIR(st.st_mode) ?
  393                   X509_LOOKUP_add_dir(dirLookup, mbTrustedRoot, X509_FILETYPE_PEM) :
  394                   X509_LOOKUP_load_file(fileLookup, mbTrustedRoot, X509_FILETYPE_PEM);
  395          if (added)
  396             nxlog_debug_tag(DEBUG_TAG, 6, _T("CreateTrustedCertificatesStore: trusted %s \"%s\" added"), S_ISDIR(st.st_mode) ? _T("certificate directory") : _T("certificate"), trustedRoot);
  397       }
  398       delete it;
  399    }
  400 
  401    if (useSystemStore)
  402    {
  403 #ifdef _WIN32
  404       HCERTSTORE hStore = CertOpenSystemStore(0, L"ROOT");
  405       if (hStore != nullptr)
  406       {
  407          PCCERT_CONTEXT context = nullptr;
  408          while ((context = CertEnumCertificatesInStore(hStore, context)) != nullptr)
  409          {
  410             TCHAR certName[1024];
  411             CertGetNameString(context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, certName, 1024);
  412 
  413             const unsigned char *in = context->pbCertEncoded;
  414             X509 *cert = d2i_X509(nullptr, &in, context->cbCertEncoded);
  415             if (cert != nullptr)
  416             {
  417                if (X509_STORE_add_cert(store, cert))
  418                   nxlog_debug_tag(DEBUG_TAG, 6, _T("CreateTrustedCertificatesStore: added trusted root certificate \"%s\" from system store"), certName);
  419                else
  420                   nxlog_debug_tag(DEBUG_TAG, 6, _T("CreateTrustedCertificatesStore: cannot add trusted root certificate \"%s\" from system store"), certName);
  421                X509_free(cert);
  422             }
  423             else
  424             {
  425                nxlog_debug_tag(DEBUG_TAG, 4, _T("CreateTrustedCertificatesStore: cannot convert certificate \"%s\""), certName);
  426             }
  427          }
  428          CertCloseStore(hStore, CERT_CLOSE_STORE_FORCE_FLAG);
  429       }
  430       else
  431       {
  432          TCHAR buffer[1024];
  433          nxlog_debug_tag(DEBUG_TAG, 4, _T("CreateTrustedCertificatesStore: cannot open certificate store \"ROOT\" (%s)"), GetSystemErrorText(GetLastError(), buffer, 1024));
  434       }
  435 #else    /* _WIN32 */
  436       static const char *defaultStoreLocations[] = {
  437          "/etc/ssl/certs",               // Ubuntu, Debian, and many other Linux distros
  438          "/usr/local/share/certs",       // FreeBSD
  439          "/etc/pki/tls/certs",           // Fedora/RHEL
  440          "/etc/openssl/certs",           // NetBSD
  441          "/var/ssl/certs",               // AIX
  442          nullptr
  443       };
  444       for(int i = 0; defaultStoreLocations[i] != nullptr; i++)
  445       {
  446          NX_STAT_STRUCT st;
  447          if (NX_STAT(defaultStoreLocations[i], &st) == 0)
  448          {
  449             int added = S_ISDIR(st.st_mode) ?
  450                      X509_LOOKUP_add_dir(dirLookup, defaultStoreLocations[i], X509_FILETYPE_PEM) :
  451                      X509_LOOKUP_load_file(fileLookup, defaultStoreLocations[i], X509_FILETYPE_PEM);
  452             if (added)
  453             {
  454                nxlog_debug_tag(DEBUG_TAG, 6, _T("CreateTrustedCertificatesStore: added system certificate store at \"%hs\""), defaultStoreLocations[i]);
  455                break;
  456             }
  457          }
  458       }
  459 #endif   /* _WIN32 */
  460    }
  461 
  462    return store;
  463 }
  464 
  465 #endif   /* _WITH_ENCRYPTION */