Bug 968817 - Only accept certs for server TLS which use EKU (and which assert the TLS Server Authentication EKU) r=keeler
authorRichard Barnes <rbarnes@mozilla.com>
Mon, 24 Nov 2014 20:33:50 -0500
changeset 217310 672742f81e517e7e84fa06dc27f367ce9ad5c413
parent 217309 25800dd796617b9b1f46b8c315df30347f98d77a
child 217311 f7705f553b85bc361857c72300bc279421cf9aa5
push idunknown
push userunknown
push dateunknown
reviewerskeeler
bugs968817
milestone36.0a1
Bug 968817 - Only accept certs for server TLS which use EKU (and which assert the TLS Server Authentication EKU) r=keeler
security/manager/ssl/src/SSLServerCertVerification.cpp
security/manager/ssl/src/ScopedNSSTypes.h
toolkit/components/telemetry/Histograms.json
--- a/security/manager/ssl/src/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/src/SSLServerCertVerification.cpp
@@ -122,17 +122,19 @@
 #include "nsServiceManagerUtils.h"
 #include "PSMRunnable.h"
 #include "RootCertificateTelemetryUtils.h"
 #include "SharedSSLState.h"
 #include "nsContentUtils.h"
 #include "nsURLHelper.h"
 
 #include "ssl.h"
+#include "cert.h"
 #include "secerr.h"
+#include "secoidt.h"
 #include "secport.h"
 #include "sslerr.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gPIPNSSLog;
 #endif
 
 using namespace mozilla::pkix;
@@ -808,18 +810,20 @@ TryMatchingWildcardSubjectAltName(const 
 // in Mozilla's Root CA program.
 // certList consists of a validated certificate chain. The end-entity
 // certificate is first and the root (trust anchor) is last.
 void
 GatherBaselineRequirementsTelemetry(const ScopedCERTCertList& certList)
 {
   CERTCertListNode* endEntityNode = CERT_LIST_HEAD(certList);
   CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
-  PR_ASSERT(endEntityNode && rootNode);
-  if (!endEntityNode || !rootNode) {
+  PR_ASSERT(!(CERT_LIST_END(endEntityNode, certList) ||
+              CERT_LIST_END(rootNode, certList)));
+  if (CERT_LIST_END(endEntityNode, certList) ||
+      CERT_LIST_END(rootNode, certList)) {
     return;
   }
   CERTCertificate* cert = endEntityNode->cert;
   mozilla::pkix::ScopedPtr<char, PORT_Free_string> commonName(
     CERT_GetCommonName(&cert->subject));
   // This only applies to certificates issued by authorities in our root
   // program.
   bool isBuiltIn = false;
@@ -955,16 +959,87 @@ GatherBaselineRequirementsTelemetry(cons
     // 0 means the extension is acceptable
     Telemetry::Accumulate(Telemetry::BR_9_2_1_SUBJECT_ALT_NAMES, 0);
   }
 
   AccumulateSubjectCommonNameTelemetry(commonName.get(),
                                        commonNameInSubjectAltNames);
 }
 
+// Gather telemetry on whether the end-entity cert for a server has the
+// required TLS Server Authentication EKU, or any others
+void
+GatherEKUTelemetry(const ScopedCERTCertList& certList)
+{
+  CERTCertListNode* endEntityNode = CERT_LIST_HEAD(certList);
+  CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
+  PR_ASSERT(!(CERT_LIST_END(endEntityNode, certList) ||
+              CERT_LIST_END(rootNode, certList)));
+  if (CERT_LIST_END(endEntityNode, certList) ||
+      CERT_LIST_END(rootNode, certList)) {
+    return;
+  }
+  CERTCertificate* endEntityCert = endEntityNode->cert;
+
+  // Only log telemetry if the root CA is built-in
+  bool isBuiltIn = false;
+  SECStatus rv = IsCertBuiltInRoot(rootNode->cert, isBuiltIn);
+  if (rv != SECSuccess || !isBuiltIn) {
+    return;
+  }
+
+  // Find the EKU extension, if present
+  bool foundEKU = false;
+  SECOidTag oidTag;
+  CERTCertExtension* ekuExtension = nullptr;
+  for (size_t i = 0; endEntityCert->extensions[i]; i++) {
+    oidTag = SECOID_FindOIDTag(&endEntityCert->extensions[i]->id);
+    if (oidTag == SEC_OID_X509_EXT_KEY_USAGE) {
+      foundEKU = true;
+      ekuExtension = endEntityCert->extensions[i];
+    }
+  }
+
+  if (!foundEKU) {
+    Telemetry::Accumulate(Telemetry::SSL_SERVER_AUTH_EKU, 0);
+    return;
+  }
+
+  // Parse the EKU extension
+  ScopedCERTOidSequence ekuSequence(
+    CERT_DecodeOidSequence(&ekuExtension->value));
+  if (!ekuSequence) {
+    return;
+  }
+
+  // Search through the available EKUs
+  bool foundServerAuth = false;
+  bool foundOther = false;
+  for (SECItem** oids = ekuSequence->oids; oids && *oids; oids++) {
+    oidTag = SECOID_FindOIDTag(*oids);
+    if (oidTag == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH) {
+      foundServerAuth = true;
+    } else {
+      foundOther = true;
+    }
+  }
+
+  // Cases 3 is included only for completeness.  It should never
+  // appear in these statistics, because CheckExtendedKeyUsage()
+  // should require the EKU extension, if present, to contain the
+  // value id_kp_serverAuth.
+  if (foundServerAuth && !foundOther) {
+    Telemetry::Accumulate(Telemetry::SSL_SERVER_AUTH_EKU, 1);
+  } else if (foundServerAuth && foundOther) {
+    Telemetry::Accumulate(Telemetry::SSL_SERVER_AUTH_EKU, 2);
+  } else if (!foundServerAuth) {
+    Telemetry::Accumulate(Telemetry::SSL_SERVER_AUTH_EKU, 3);
+  }
+}
+
 // Gathers telemetry on which CA is the root of a given cert chain.
 // If the root is a built-in root, then the telemetry makes a count
 // by root.  Roots that are not built-in are counted in one bin.
 void
 GatherRootCATelemetry(const ScopedCERTCertList& certList)
 {
   CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
   PR_ASSERT(rootNode);
@@ -980,16 +1055,17 @@ GatherRootCATelemetry(const ScopedCERTCe
 }
 
 // There are various things that we want to measure about certificate
 // chains that we accept.  This is a single entry point for all of them.
 void
 GatherSuccessfulValidationTelemetry(const ScopedCERTCertList& certList)
 {
   GatherBaselineRequirementsTelemetry(certList);
+  GatherEKUTelemetry(certList);
   GatherRootCATelemetry(certList);
 }
 
 SECStatus
 AuthCertificate(CertVerifier& certVerifier,
                 TransportSecurityInfo* infoObject,
                 CERTCertificate* cert,
                 ScopedCERTCertList& peerCertChain,
--- a/security/manager/ssl/src/ScopedNSSTypes.h
+++ b/security/manager/ssl/src/ScopedNSSTypes.h
@@ -87,16 +87,19 @@ MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLAT
                                           CERTCertificateRequest,
                                           CERT_DestroyCertificateRequest)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTCertList,
                                           CERTCertList,
                                           CERT_DestroyCertList)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTName,
                                           CERTName,
                                           CERT_DestroyName)
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTOidSequence,
+                                          CERTOidSequence,
+                                          CERT_DestroyOidSequence)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTCertNicknames,
                                           CERTCertNicknames,
                                           CERT_FreeNicknames)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTSubjectPublicKeyInfo,
                                           CERTSubjectPublicKeyInfo,
                                           SECKEY_DestroySubjectPublicKeyInfo)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTValidity,
                                           CERTValidity,
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -6557,16 +6557,22 @@
   "SSL_PERMANENT_CERT_ERROR_OVERRIDES": {
     "alert_emails": ["seceng@mozilla.org"],
     "expires_in_version": "42",
     "kind": "exponential",
     "high": 1024,
     "n_buckets": 10,
     "description": "How many permanent certificate overrides a user has stored."
   },
+  "SSL_SERVER_AUTH_EKU": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Presence of of the Server Authenticaton EKU in accepted SSL server certificates (0=No EKU, 1=EKU present and has id_kp_serverAuth, 2=EKU present and has id_kp_serverAuth as well as some other EKU, 3=EKU present but does not contain id_kp_serverAuth)"
+  },
   "TELEMETRY_TEST_EXPIRED": {
     "expires_in_version": "4.0a1",
     "kind": "flag",
     "description": "a testing histogram; not meant to be touched"
   },
   "CERT_OCSP_ENABLED": {
     "expires_in_version": "never",
     "kind": "boolean",