Bug 1406856 - Re-plumb nsISSLStatus.idl to carry with it the whole nsIX509CertList r?keeler
MozReview-Commit-ID: 2YDmCzqdm26
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -1463,37 +1463,37 @@ AuthCertificate(CertVerifier& certVerifi
// a client cert. Let's provide a minimal SSLStatus
// to the caller that contains at least the cert and its status.
RefPtr<nsSSLStatus> status(infoObject->SSLStatus());
if (!status) {
status = new nsSSLStatus();
infoObject->SetSSLStatus(status);
}
- if (!status->HasServerCert()) {
- EVStatus evStatus;
- if (evOidPolicy == SEC_OID_UNKNOWN) {
- evStatus = EVStatus::NotEV;
- } else {
- evStatus = EVStatus::EV;
- }
+ EVStatus evStatus;
+ if (evOidPolicy == SEC_OID_UNKNOWN) {
+ evStatus = EVStatus::NotEV;
+ } else {
+ evStatus = EVStatus::EV;
+ }
- RefPtr<nsNSSCertificate> nsc = nsNSSCertificate::Create(cert.get());
- status->SetServerCert(nsc, evStatus);
- MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
- ("AuthCertificate setting NEW cert %p", nsc.get()));
- }
+ RefPtr<nsNSSCertificate> nsc = nsNSSCertificate::Create(cert.get());
+ status->SetServerCert(nsc, evStatus);
+
+ status->SetSucceededCertChain(Move(certList));
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("AuthCertificate setting NEW cert %p", nsc.get()));
status->SetCertificateTransparencyInfo(certificateTransparencyInfo);
}
if (rv != Success) {
// Certificate validation failed; store the peer certificate chain on
// infoObject so it can be used for error reporting.
- infoObject->SetFailedCertChain(Move(peerCertChain));
+ infoObject->SetFailedCertChain(Move(certList));
PR_SetError(MapResultToPRErrorCode(rv), 0);
}
return rv == Success ? SECSuccess : SECFailure;
}
/*static*/ SECStatus
SSLServerCertVerificationJob::Dispatch(
--- a/security/manager/ssl/TransportSecurityInfo.cpp
+++ b/security/manager/ssl/TransportSecurityInfo.cpp
@@ -1007,16 +1007,17 @@ TransportSecurityInfo::SetStatusErrorBit
{
MutexAutoLock lock(mMutex);
if (!mSSLStatus) {
mSSLStatus = new nsSSLStatus();
}
mSSLStatus->SetServerCert(cert, EVStatus::NotEV);
+ mSSLStatus->SetFailedCertChain(mFailedCertChain);
mSSLStatus->mHaveCertErrorBits = true;
mSSLStatus->mIsDomainMismatch =
collected_errors & nsICertOverrideService::ERROR_MISMATCH;
mSSLStatus->mIsNotValidAtThisTime =
collected_errors & nsICertOverrideService::ERROR_TIME;
mSSLStatus->mIsUntrusted =
collected_errors & nsICertOverrideService::ERROR_UNTRUSTED;
--- a/security/manager/ssl/nsISSLStatus.idl
+++ b/security/manager/ssl/nsISSLStatus.idl
@@ -2,20 +2,23 @@
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIX509Cert;
+interface nsIX509CertList;
[scriptable, uuid(fa9ba95b-ca3b-498a-b889-7c79cf28fee8)]
interface nsISSLStatus : nsISupports {
readonly attribute nsIX509Cert serverCert;
+ readonly attribute nsIX509CertList failedCertChain;
+ readonly attribute nsIX509CertList succeededCertChain;
[must_use]
readonly attribute ACString cipherName;
[must_use]
readonly attribute unsigned long keyLength;
[must_use]
readonly attribute unsigned long secretKeyLength;
[must_use]
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -1216,26 +1216,26 @@ DetermineEVAndCTStatusAndSetNewCert(RefP
mozilla::psm::CertVerifier::FLAG_MUST_BE_EV;
if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
!infoObject->SharedState().IsOCSPMustStapleEnabled()) {
flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
}
SECOidTag evOidPolicy;
CertificateTransparencyInfo certificateTransparencyInfo;
- UniqueCERTCertList unusedBuiltChain;
+ UniqueCERTCertList builtChain;
const bool saveIntermediates = false;
mozilla::pkix::Result rv = certVerifier->VerifySSLServerCert(
cert,
stapledOCSPResponse,
sctsFromTLSExtension,
mozilla::pkix::Now(),
infoObject,
infoObject->GetHostName(),
- unusedBuiltChain,
+ builtChain,
saveIntermediates,
flags,
infoObject->GetOriginAttributes(),
&evOidPolicy,
nullptr, // OCSP stapling telemetry
nullptr, // key size telemetry
nullptr, // SHA-1 telemetry
nullptr, // pinning telemetry
@@ -1250,16 +1250,17 @@ DetermineEVAndCTStatusAndSetNewCert(RefP
} else {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("HandshakeCallback using NEW cert %p (is not EV)", nssc.get()));
sslStatus->SetServerCert(nssc, EVStatus::NotEV);
}
if (rv == Success) {
sslStatus->SetCertificateTransparencyInfo(certificateTransparencyInfo);
+ sslStatus->SetSucceededCertChain(Move(builtChain));
}
}
void HandshakeCallback(PRFileDesc* fd, void* client_data) {
nsNSSShutDownPreventionLock locker;
SECStatus rv;
nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret;
--- a/security/manager/ssl/nsSSLStatus.cpp
+++ b/security/manager/ssl/nsSSLStatus.cpp
@@ -6,18 +6,25 @@
#include "CTVerifyResult.h"
#include "mozilla/Casting.h"
#include "nsSSLStatus.h"
#include "nsIClassInfoImpl.h"
#include "nsIObjectOutputStream.h"
#include "nsIObjectInputStream.h"
#include "nsNSSCertificate.h"
+#include "nsNSSShutDown.h"
#include "ssl.h"
+
+void
+nsSSLStatus::virtualDestroyNSSReference()
+{
+}
+
NS_IMETHODIMP
nsSSLStatus::GetServerCert(nsIX509Cert** aServerCert)
{
NS_ENSURE_ARG_POINTER(aServerCert);
nsCOMPtr<nsIX509Cert> cert = mServerCert;
cert.forget(aServerCert);
return NS_OK;
@@ -220,24 +227,41 @@ nsSSLStatus::Read(nsIObjectInputStream*
if (streamFormatVersion >= 2) {
rv = aStream->ReadCString(mKeaGroup);
NS_ENSURE_SUCCESS(rv, rv);
rv = aStream->ReadCString(mSignatureSchemeName);
NS_ENSURE_SUCCESS(rv, rv);
}
+ // Added in version 3 (see bug 1406856).
+ if (streamFormatVersion >= 3) {
+ nsCOMPtr<nsISupports> succeededCertChainSupports;
+ rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(succeededCertChainSupports));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mSucceededCertChain = do_QueryInterface(succeededCertChainSupports);
+
+ nsCOMPtr<nsISupports> failedCertChainSupports;
+ rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(failedCertChainSupports));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mFailedCertChain = do_QueryInterface(failedCertChainSupports);
+ }
+
return NS_OK;
}
NS_IMETHODIMP
nsSSLStatus::Write(nsIObjectOutputStream* aStream)
{
// The current version of the binary stream format.
- const uint8_t STREAM_FORMAT_VERSION = 2;
+ const uint8_t STREAM_FORMAT_VERSION = 3;
nsresult rv = aStream->WriteCompoundObject(mServerCert,
NS_GET_IID(nsIX509Cert),
true);
NS_ENSURE_SUCCESS(rv, rv);
rv = aStream->Write16(mCipherSuite);
NS_ENSURE_SUCCESS(rv, rv);
@@ -270,16 +294,33 @@ nsSSLStatus::Write(nsIObjectOutputStream
// Added in version 2.
rv = aStream->WriteStringZ(mKeaGroup.get());
NS_ENSURE_SUCCESS(rv, rv);
rv = aStream->WriteStringZ(mSignatureSchemeName.get());
NS_ENSURE_SUCCESS(rv, rv);
+ // Added in version 3.
+ rv = NS_WriteOptionalCompoundObject(aStream,
+ mSucceededCertChain,
+ NS_GET_IID(nsIX509CertList),
+ true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = NS_WriteOptionalCompoundObject(aStream,
+ mFailedCertChain,
+ NS_GET_IID(nsIX509CertList),
+ true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
return NS_OK;
}
NS_IMETHODIMP
nsSSLStatus::GetInterfaces(uint32_t* aCount, nsIID*** aArray)
{
*aCount = 0;
*aArray = nullptr;
@@ -349,28 +390,75 @@ nsSSLStatus::nsSSLStatus()
, mHaveCertErrorBits(false)
{
}
NS_IMPL_ISUPPORTS(nsSSLStatus, nsISSLStatus, nsISerializable, nsIClassInfo)
nsSSLStatus::~nsSSLStatus()
{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ shutdown(ShutdownCalledFrom::Object);
}
void
nsSSLStatus::SetServerCert(nsNSSCertificate* aServerCert, EVStatus aEVStatus)
{
MOZ_ASSERT(aServerCert);
mServerCert = aServerCert;
mIsEV = (aEVStatus == EVStatus::EV);
mHasIsEVStatus = true;
}
+nsresult
+nsSSLStatus::SetSucceededCertChain(UniqueCERTCertList aCertList)
+{
+ nsNSSShutDownPreventionLock lock;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // nsNSSCertList takes ownership of certList
+ mSucceededCertChain = new nsNSSCertList(Move(aCertList), lock);
+
+ return NS_OK;
+}
+
+void
+nsSSLStatus::SetFailedCertChain(nsIX509CertList* aX509CertList)
+{
+ mFailedCertChain = aX509CertList;
+}
+
+NS_IMETHODIMP
+nsSSLStatus::GetSucceededCertChain(nsIX509CertList** _result)
+{
+ NS_ENSURE_ARG_POINTER(_result);
+
+ nsCOMPtr<nsIX509CertList> tmpList = mSucceededCertChain;
+ tmpList.forget(_result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSSLStatus::GetFailedCertChain(nsIX509CertList** _result)
+{
+ NS_ENSURE_ARG_POINTER(_result);
+
+ nsCOMPtr<nsIX509CertList> tmpList = mFailedCertChain;
+ tmpList.forget(_result);
+
+ return NS_OK;
+}
+
void
nsSSLStatus::SetCertificateTransparencyInfo(
const mozilla::psm::CertificateTransparencyInfo& info)
{
using mozilla::ct::CTPolicyCompliance;
mCertificateTransparencyStatus =
nsISSLStatus::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
--- a/security/manager/ssl/nsSSLStatus.h
+++ b/security/manager/ssl/nsSSLStatus.h
@@ -7,50 +7,59 @@
#ifndef _NSSSLSTATUS_H
#define _NSSSLSTATUS_H
#include "CertVerifier.h" // For CertificateTransparencyInfo
#include "nsISSLStatus.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIX509Cert.h"
+#include "nsIX509CertList.h"
#include "nsISerializable.h"
#include "nsIClassInfo.h"
+#include "nsNSSCertificate.h"
+#include "ScopedNSSTypes.h"
class nsNSSCertificate;
enum class EVStatus {
NotEV = 0,
EV = 1,
};
class nsSSLStatus final
: public nsISSLStatus
, public nsISerializable
, public nsIClassInfo
+ , public nsNSSShutDownObject
{
protected:
virtual ~nsSSLStatus();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSISSLSTATUS
NS_DECL_NSISERIALIZABLE
NS_DECL_NSICLASSINFO
nsSSLStatus();
void SetServerCert(nsNSSCertificate* aServerCert, EVStatus aEVStatus);
+ nsresult SetSucceededCertChain(mozilla::UniqueCERTCertList certList);
+ void SetFailedCertChain(nsIX509CertList* x509CertList);
+
bool HasServerCert() {
return mServerCert != nullptr;
}
void SetCertificateTransparencyInfo(
const mozilla::psm::CertificateTransparencyInfo& info);
+ virtual void virtualDestroyNSSReference() override;
+
/* public for initilization in this file */
uint16_t mCipherSuite;
uint16_t mProtocolVersion;
uint16_t mCertificateTransparencyStatus;
nsCString mKeaGroup;
nsCString mSignatureSchemeName;
bool mIsDomainMismatch;
@@ -62,15 +71,17 @@ public:
bool mHaveCipherSuiteAndProtocol;
/* mHaveCertErrrorBits is relied on to determine whether or not a SPDY
connection is eligible for joining in nsNSSSocketInfo::JoinConnection() */
bool mHaveCertErrorBits;
private:
nsCOMPtr<nsIX509Cert> mServerCert;
+ nsCOMPtr<nsIX509CertList> mSucceededCertChain;
+ nsCOMPtr<nsIX509CertList> mFailedCertChain;
};
#define NS_SSLSTATUS_CID \
{ 0xe2f14826, 0x9e70, 0x4647, \
{ 0xb2, 0x3f, 0x10, 0x10, 0xf5, 0x12, 0x46, 0x28 } }
#endif
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -116,16 +116,26 @@ const NO_FLAGS = 0;
// with no newlines or BEGIN/END headers. This is a helper function to convert
// PEM to the format that nsIX509CertDB requires.
function pemToBase64(pem) {
return pem.replace(/-----BEGIN CERTIFICATE-----/, "")
.replace(/-----END CERTIFICATE-----/, "")
.replace(/[\r\n]/g, "");
}
+function build_cert_chain(certNames) {
+ let certList = Cc["@mozilla.org/security/x509certlist;1"]
+ .createInstance(Ci.nsIX509CertList);
+ certNames.forEach(function(certName) {
+ let cert = constructCertFromFile("bad_certs/" + certName + ".pem");
+ certList.addCert(cert);
+ });
+ return certList;
+}
+
function readFile(file) {
let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
fstream.init(file, -1, 0, 0);
let data = NetUtil.readInputStreamToString(fstream, fstream.available());
fstream.close();
return data;
}
@@ -697,39 +707,48 @@ function add_cert_override(aHost, aExpec
"Actual error message should match expected error regexp");
}
let sslstatus = aSecurityInfo.QueryInterface(Ci.nsISSLStatusProvider)
.SSLStatus;
let bits =
(sslstatus.isUntrusted ? Ci.nsICertOverrideService.ERROR_UNTRUSTED : 0) |
(sslstatus.isDomainMismatch ? Ci.nsICertOverrideService.ERROR_MISMATCH : 0) |
(sslstatus.isNotValidAtThisTime ? Ci.nsICertOverrideService.ERROR_TIME : 0);
+
Assert.equal(bits, aExpectedBits,
"Actual and expected override bits should match");
let cert = sslstatus.serverCert;
let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
.getService(Ci.nsICertOverrideService);
certOverrideService.rememberValidityOverride(aHost, 8443, cert, aExpectedBits,
true);
}
// Given a host, expected error bits (see nsICertOverrideService.idl), an
// expected error code, and optionally a regular expression that the resulting
// error message must match, tests that an initial connection to the host fails
// with the expected errors and that adding an override results in a subsequent
// connection succeeding.
function add_cert_override_test(aHost, aExpectedBits, aExpectedError,
- aExpectedErrorRegexp = undefined) {
+ aExpectedErrorRegexp = undefined,
+ aExpectedSSLStatus = undefined) {
add_connection_test(aHost, aExpectedError, null,
add_cert_override.bind(this, aHost, aExpectedBits,
aExpectedErrorRegexp));
add_connection_test(aHost, PRErrorCodeSuccess, null, aSecurityInfo => {
Assert.ok(aSecurityInfo.securityState &
Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN,
"Cert override flag should be set on the security state");
+ if (aExpectedSSLStatus) {
+ let sslstatus = aSecurityInfo.QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus;
+ if (aExpectedSSLStatus.failedCertChain) {
+ ok(aExpectedSSLStatus.failedCertChain.equals(sslstatus.failedCertChain));
+ }
+ }
});
}
// Helper function for add_prevented_cert_override_test. This is much like
// add_cert_override except it may not be the case that the connection has an
// SSLStatus set on it. In this case, the error was not overridable anyway, so
// we consider it a success.
function attempt_adding_cert_override(aHost, aExpectedBits, aSecurityInfo) {
--- a/security/manager/ssl/tests/unit/test_cert_chains.js
+++ b/security/manager/ssl/tests/unit/test_cert_chains.js
@@ -1,25 +1,15 @@
// -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
"use strict";
-function build_cert_chain(certNames) {
- let certList = Cc["@mozilla.org/security/x509certlist;1"]
- .createInstance(Ci.nsIX509CertList);
- certNames.forEach(function(certName) {
- let cert = constructCertFromFile("bad_certs/" + certName + ".pem");
- certList.addCert(cert);
- });
- return certList;
-}
-
function test_cert_equals() {
let certA = constructCertFromFile("bad_certs/default-ee.pem");
let certB = constructCertFromFile("bad_certs/default-ee.pem");
let certC = constructCertFromFile("bad_certs/expired-ee.pem");
ok(certA != certB,
"Cert objects constructed from the same file should not be equal" +
" according to the equality operators");
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_ssl_status.js
@@ -0,0 +1,55 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+"use strict";
+
+do_get_profile();
+
+function run_test() {
+ Services.prefs.setIntPref("security.OCSP.enabled", 1);
+ add_tls_server_setup("BadCertServer", "bad_certs");
+
+ let fakeOCSPResponder = new HttpServer();
+ fakeOCSPResponder.registerPrefixHandler("/", function (request, response) {
+ response.setStatusLine(request.httpVersion, 500, "Internal Server Error");
+ });
+ fakeOCSPResponder.start(8888);
+
+ // Test successful connection (failedCertChain should be null,
+ // succeededCertChain should be set as expected)
+ add_connection_test(
+ "good.include-subdomains.pinning.example.com", PRErrorCodeSuccess, null,
+ function withSecurityInfo(aSSLStatus) {
+ let sslstatus = aSSLStatus.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
+ equal(sslstatus.failedCertChain, null,
+ "failedCertChain for a successful connection should be null");
+ ok(sslstatus.succeededCertChain.equals(build_cert_chain(["default-ee", "test-ca"])),
+ "succeededCertChain for a successful connection should be as expected");
+ }
+ );
+
+ // Test failed connection (failedCertChain should be set as expected,
+ // succeededCertChain should be null)
+ add_connection_test(
+ "expired.example.com", SEC_ERROR_EXPIRED_CERTIFICATE, null,
+ function withSecurityInfo(aSSLStatus) {
+ let sslstatus = aSSLStatus.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
+ equal(sslstatus.succeededCertChain, null,
+ "succeededCertChain for a failed connection should be null");
+ ok(sslstatus.failedCertChain.equals(build_cert_chain(["expired-ee", "test-ca"])),
+ "failedCertChain for a failed connection should be as expected");
+ }
+ );
+
+ // Ensure the correct failed cert chain is set on cert override
+ let overrideStatus = {
+ failedCertChain: build_cert_chain(["expired-ee", "test-ca"])
+ };
+ add_cert_override_test("expired.example.com",
+ Ci.nsICertOverrideService.ERROR_TIME,
+ SEC_ERROR_EXPIRED_CERTIFICATE, undefined,
+ overrideStatus);
+
+ run_next_test();
+}
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -146,16 +146,17 @@ requesttimeoutfactor = 2
[test_sdr_preexisting_with_password.js]
# Not relevant to Android. See the comment in the test.
skip-if = toolkit == 'android'
[test_session_resumption.js]
run-sequentially = hardcoded ports
[test_signed_apps.js]
[test_signed_dir.js]
tags = addons psm
+[test_ssl_status.js]
[test_sss_enumerate.js]
[test_sss_eviction.js]
[test_sss_originAttributes.js]
[test_sss_readstate.js]
[test_sss_readstate_child.js]
support-files = sss_readstate_child_worker.js
# bug 1124289 - run_test_in_child violates the sandbox on android
skip-if = toolkit == 'android'