Bug 1434936 - Add method nsNSSCertList::GetRootCertificate r?keeler r?fkiefer draft
authorJ.C. Jones <jjones@mozilla.com>
Wed, 31 Jan 2018 17:14:40 -0700
changeset 752288 3d0bd98cafa4324ff07897f5d9a8b0ac8dc2ced5
parent 752153 65133e49fbfd5306632301f74be7cd15890bdf9f
child 752289 4b621fbd548dd938b372b14722223d188c792a1e
child 752574 d441476f7ef4e4e9850bd333b1c83dcfa6962537
push id98217
push userbmo:jjones@mozilla.com
push dateWed, 07 Feb 2018 21:08:43 +0000
reviewerskeeler, fkiefer
bugs1434936
milestone60.0a1
Bug 1434936 - Add method nsNSSCertList::GetRootCertificate r?keeler r?fkiefer This adds another utility method to nsNSSCertList to perform CERT_LIST_TAIL on the underlying certificate list and return the last entry -- e.g., the root. This is a convenience method to let other parts of the certificate verifier continue to work with the higher-level nsNSSCertificate objects instead of having to convert them. MozReview-Commit-ID: EEi9L5Iepc6
security/manager/ssl/nsNSSCertificate.cpp
security/manager/ssl/nsNSSCertificate.h
security/manager/ssl/tests/gtest/CertListTest.cpp
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -1270,16 +1270,38 @@ nsNSSCertList::SegmentCertificateChain(/
   if (!aRoot || !aEndEntity) {
     // No self-sigend (or empty) chains allowed
     return NS_ERROR_INVALID_ARG;
   }
 
   return NS_OK;
 }
 
+nsresult
+nsNSSCertList::GetRootCertificate(/* out */ nsCOMPtr<nsIX509Cert>& aRoot)
+{
+  if (aRoot) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  CERTCertListNode* rootNode = CERT_LIST_TAIL(mCertList);
+  if (!rootNode) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  if (CERT_LIST_END(rootNode, mCertList)) {
+    // Empty list, leave aRoot empty
+    return NS_OK;
+  }
+  // Duplicates the certificate
+  aRoot = nsNSSCertificate::Create(rootNode->cert);
+  if (!aRoot) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
 NS_IMPL_ISUPPORTS(nsNSSCertListEnumerator, nsISimpleEnumerator)
 
 nsNSSCertListEnumerator::nsNSSCertListEnumerator(
   const UniqueCERTCertList& certList)
 {
   MOZ_ASSERT(certList);
   mCertList = nsNSSCertList::DupCertList(certList);
 }
--- a/security/manager/ssl/nsNSSCertificate.h
+++ b/security/manager/ssl/nsNSSCertificate.h
@@ -101,16 +101,21 @@ public:
   // doubt.
   // Will return error if used on self-signed or empty chains.
   // This method requires that all arguments be empty, notably the list
   // `aIntermediates` must be empty.
   nsresult SegmentCertificateChain(/* out */ nsCOMPtr<nsIX509Cert>& aRoot,
                            /* out */ nsCOMPtr<nsIX509CertList>& aIntermediates,
                            /* out */ nsCOMPtr<nsIX509Cert>& aEndEntity);
 
+  // Obtain the root certificate of a certificate chain. This method does so
+  // blindly, as SegmentCertificateChain; the same restrictions apply. On an
+  // empty list, leaves aRoot empty and returns OK.
+  nsresult GetRootCertificate(/* out */ nsCOMPtr<nsIX509Cert>& aRoot);
+
 private:
    virtual ~nsNSSCertList() {}
 
    mozilla::UniqueCERTCertList mCertList;
 
    nsNSSCertList(const nsNSSCertList&) = delete;
    void operator=(const nsNSSCertList&) = delete;
 };
--- a/security/manager/ssl/tests/gtest/CertListTest.cpp
+++ b/security/manager/ssl/tests/gtest/CertListTest.cpp
@@ -369,17 +369,16 @@ TEST_F(psm_CertList, TestForEachStopEarl
       counter++;
       aContinue = false;
       return NS_OK;
     });
   ASSERT_EQ(counter, 1) << "There should have been only the one call";
   ASSERT_EQ(rv, NS_OK) << "Should complete OK.";
 }
 
-
 TEST_F(psm_CertList, TestForEachStopOnError)
 {
   RefPtr<nsNSSCertList> certList = new nsNSSCertList();
 
   // Add two certificates
   nsresult rv = AddCertFromStringToList(kCaSecondIntermediatePem, certList);
   ASSERT_EQ(rv, NS_OK) << "Should have loaded OK";
   rv = AddCertFromStringToList(kEePem, certList);
@@ -388,9 +387,79 @@ TEST_F(psm_CertList, TestForEachStopOnEr
   int counter = 0;
   rv = certList->ForEachCertificateInChain(
     [&counter] (nsCOMPtr<nsIX509Cert> aCert, bool aHasMore, bool& aContinue) {
       counter++;
       return NS_ERROR_FAILURE;
     });
   ASSERT_EQ(counter, 1) << "There should have been only the one call";
   ASSERT_EQ(rv, NS_ERROR_FAILURE) << "Should propagate the error.";
-}
\ No newline at end of file
+}
+
+TEST_F(psm_CertList, TestGetRootCertificateChainTwo)
+{
+  RefPtr<nsNSSCertList> certList = new nsNSSCertList();
+
+  nsresult rv = AddCertFromStringToList(kCaIntermediatePem, certList);
+  ASSERT_EQ(NS_OK, rv) << "Should have loaded OK";
+  rv = AddCertFromStringToList(kCaPem, certList);
+  ASSERT_EQ(NS_OK, rv) << "Should have loaded OK";
+
+  nsCOMPtr<nsIX509Cert> rootCert;
+  rv = certList->GetRootCertificate(rootCert);
+  EXPECT_EQ(NS_OK, rv) << "Should have fetched the root OK";
+  ASSERT_TRUE(rootCert) << "Root cert should be filled in";
+
+  bool selfSigned;
+  EXPECT_TRUE(NS_SUCCEEDED(rootCert->GetIsSelfSigned(&selfSigned))) << "Getters should work.";
+  EXPECT_TRUE(selfSigned) << "Roots are self signed";
+
+  nsAutoString rootCn;
+  EXPECT_TRUE(NS_SUCCEEDED(rootCert->GetCommonName(rootCn))) << "Getters should work.";
+  EXPECT_TRUE(rootCn.EqualsLiteral("ca")) << "Root CN should match";
+
+  // Re-fetch and ensure we get the same certificate.
+  nsCOMPtr<nsIX509Cert> rootCertRepeat;
+  rv = certList->GetRootCertificate(rootCertRepeat);
+  EXPECT_EQ(NS_OK, rv) << "Should have fetched the root OK the second time";
+  ASSERT_TRUE(rootCertRepeat) << "Root cert should still be filled in";
+
+  nsAutoString rootRepeatCn;
+  EXPECT_TRUE(NS_SUCCEEDED(rootCertRepeat->GetCommonName(rootRepeatCn))) << "Getters should work.";
+  EXPECT_TRUE(rootRepeatCn.EqualsLiteral("ca")) << "Root CN should still match";
+}
+
+TEST_F(psm_CertList, TestGetRootCertificateChainFour)
+{
+  RefPtr<nsNSSCertList> certList = new nsNSSCertList();
+
+  nsresult rv = AddCertFromStringToList(kEePem, certList);
+  ASSERT_EQ(NS_OK, rv) << "Should have loaded OK";
+  rv = AddCertFromStringToList(kCaSecondIntermediatePem, certList);
+  ASSERT_EQ(NS_OK, rv) << "Should have loaded OK";
+  rv = AddCertFromStringToList(kCaIntermediatePem, certList);
+  ASSERT_EQ(NS_OK, rv) << "Should have loaded OK";
+  rv = AddCertFromStringToList(kCaPem, certList);
+  ASSERT_EQ(NS_OK, rv) << "Should have loaded OK";
+
+  nsCOMPtr<nsIX509Cert> rootCert;
+  rv = certList->GetRootCertificate(rootCert);
+  EXPECT_EQ(NS_OK, rv) << "Should have again fetched the root OK";
+  ASSERT_TRUE(rootCert) << "Root cert should be filled in";
+
+  bool selfSigned;
+  EXPECT_TRUE(NS_SUCCEEDED(rootCert->GetIsSelfSigned(&selfSigned))) << "Getters should work.";
+  EXPECT_TRUE(selfSigned) << "Roots are self signed";
+
+  nsAutoString rootCn;
+  EXPECT_TRUE(NS_SUCCEEDED(rootCert->GetCommonName(rootCn))) << "Getters should work.";
+  EXPECT_TRUE(rootCn.EqualsLiteral("ca")) << "Root CN should match";
+}
+
+TEST_F(psm_CertList, TestGetRootCertificateChainEmpty)
+{
+  RefPtr<nsNSSCertList> certList = new nsNSSCertList();
+
+  nsCOMPtr<nsIX509Cert> rootCert;
+  nsresult rv = certList->GetRootCertificate(rootCert);
+  EXPECT_EQ(NS_OK, rv) << "Should have again fetched the root OK";
+  EXPECT_FALSE(rootCert) << "Root cert should be empty";
+}