diff options
Diffstat (limited to 'security/manager/ssl/TransportSecurityInfo.cpp')
-rw-r--r-- | security/manager/ssl/TransportSecurityInfo.cpp | 1100 |
1 files changed, 1100 insertions, 0 deletions
diff --git a/security/manager/ssl/TransportSecurityInfo.cpp b/security/manager/ssl/TransportSecurityInfo.cpp new file mode 100644 index 0000000000..101e2332c6 --- /dev/null +++ b/security/manager/ssl/TransportSecurityInfo.cpp @@ -0,0 +1,1100 @@ +/* -*- Mode: C++; 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/. */ + +#include "TransportSecurityInfo.h" + +#include "PSMRunnable.h" +#include "mozilla/Casting.h" +#include "nsComponentManagerUtils.h" +#include "nsIArray.h" +#include "nsICertOverrideService.h" +#include "nsIDateTimeFormat.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIWebProgressListener.h" +#include "nsIX509CertValidity.h" +#include "nsNSSCertHelper.h" +#include "nsNSSCertificate.h" +#include "nsNSSComponent.h" +#include "nsReadableUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsXULAppAPI.h" +#include "pkix/pkixtypes.h" +#include "secerr.h" + +//#define DEBUG_SSL_VERBOSE //Enable this define to get minimal + //reports when doing SSL read/write + +//#define DUMP_BUFFER //Enable this define along with + //DEBUG_SSL_VERBOSE to dump SSL + //read/write buffer to a log. + //Uses PR_LOG except on Mac where + //we always write out to our own + //file. + +namespace mozilla { namespace psm { + +TransportSecurityInfo::TransportSecurityInfo() + : mMutex("TransportSecurityInfo::mMutex"), + mSecurityState(nsIWebProgressListener::STATE_IS_INSECURE), + mSubRequestsBrokenSecurity(0), + mSubRequestsNoSecurity(0), + mErrorCode(0), + mErrorMessageType(PlainErrorMessage), + mPort(0) +{ +} + +TransportSecurityInfo::~TransportSecurityInfo() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return; + + shutdown(ShutdownCalledFrom::Object); +} + +void +TransportSecurityInfo::virtualDestroyNSSReference() +{ +} + +NS_IMPL_ISUPPORTS(TransportSecurityInfo, + nsITransportSecurityInfo, + nsIInterfaceRequestor, + nsISSLStatusProvider, + nsIAssociatedContentSecurity, + nsISerializable, + nsIClassInfo) + +nsresult +TransportSecurityInfo::SetHostName(const char* host) +{ + mHostName.Adopt(host ? NS_strdup(host) : 0); + return NS_OK; +} + +nsresult +TransportSecurityInfo::GetHostName(char **host) +{ + *host = (mHostName) ? NS_strdup(mHostName) : nullptr; + return NS_OK; +} + +nsresult +TransportSecurityInfo::SetPort(int32_t aPort) +{ + mPort = aPort; + return NS_OK; +} + +nsresult +TransportSecurityInfo::GetPort(int32_t *aPort) +{ + *aPort = mPort; + return NS_OK; +} + +nsresult +TransportSecurityInfo::SetOriginAttributes( + const NeckoOriginAttributes& aOriginAttributes) +{ + mOriginAttributes = aOriginAttributes; + return NS_OK; +} + +PRErrorCode +TransportSecurityInfo::GetErrorCode() const +{ + MutexAutoLock lock(mMutex); + + return mErrorCode; +} + +void +TransportSecurityInfo::SetCanceled(PRErrorCode errorCode, + SSLErrorMessageType errorMessageType) +{ + MutexAutoLock lock(mMutex); + + mErrorCode = errorCode; + mErrorMessageType = errorMessageType; + mErrorMessageCached.Truncate(); +} + +NS_IMETHODIMP +TransportSecurityInfo::GetSecurityState(uint32_t* state) +{ + *state = mSecurityState; + return NS_OK; +} + +nsresult +TransportSecurityInfo::SetSecurityState(uint32_t aState) +{ + mSecurityState = aState; + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::GetCountSubRequestsBrokenSecurity( + int32_t *aSubRequestsBrokenSecurity) +{ + *aSubRequestsBrokenSecurity = mSubRequestsBrokenSecurity; + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::SetCountSubRequestsBrokenSecurity( + int32_t aSubRequestsBrokenSecurity) +{ + mSubRequestsBrokenSecurity = aSubRequestsBrokenSecurity; + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::GetCountSubRequestsNoSecurity( + int32_t *aSubRequestsNoSecurity) +{ + *aSubRequestsNoSecurity = mSubRequestsNoSecurity; + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::SetCountSubRequestsNoSecurity( + int32_t aSubRequestsNoSecurity) +{ + mSubRequestsNoSecurity = aSubRequestsNoSecurity; + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::Flush() +{ + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::GetErrorMessage(char16_t** aText) +{ + NS_ENSURE_ARG_POINTER(aText); + *aText = nullptr; + + if (!NS_IsMainThread()) { + NS_ERROR("nsNSSSocketInfo::GetErrorMessage called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; + } + + MutexAutoLock lock(mMutex); + + if (mErrorMessageCached.IsEmpty()) { + nsresult rv = formatErrorMessage(lock, + mErrorCode, mErrorMessageType, + true, true, mErrorMessageCached); + NS_ENSURE_SUCCESS(rv, rv); + } + + *aText = ToNewUnicode(mErrorMessageCached); + return *aText ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +void +TransportSecurityInfo::GetErrorLogMessage(PRErrorCode errorCode, + SSLErrorMessageType errorMessageType, + nsString &result) +{ + if (!NS_IsMainThread()) { + NS_ERROR("nsNSSSocketInfo::GetErrorLogMessage called off the main thread"); + return; + } + + MutexAutoLock lock(mMutex); + (void) formatErrorMessage(lock, errorCode, errorMessageType, + false, false, result); +} + +static nsresult +formatPlainErrorMessage(nsXPIDLCString const & host, int32_t port, + PRErrorCode err, + bool suppressPort443, + nsString &returnedMessage); + +static nsresult +formatOverridableCertErrorMessage(nsISSLStatus & sslStatus, + PRErrorCode errorCodeToReport, + const nsXPIDLCString & host, int32_t port, + bool suppressPort443, + bool wantsHtml, + nsString & returnedMessage); + +// XXX: uses nsNSSComponent string bundles off the main thread when called by +// nsNSSSocketInfo::Write(). +nsresult +TransportSecurityInfo::formatErrorMessage(MutexAutoLock const & proofOfLock, + PRErrorCode errorCode, + SSLErrorMessageType errorMessageType, + bool wantsHtml, bool suppressPort443, + nsString &result) +{ + result.Truncate(); + if (errorCode == 0) { + return NS_OK; + } + + if (!XRE_IsParentProcess()) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv; + NS_ConvertASCIItoUTF16 hostNameU(mHostName); + NS_ASSERTION(errorMessageType != OverridableCertErrorMessage || + (mSSLStatus && mSSLStatus->HasServerCert() && + mSSLStatus->mHaveCertErrorBits), + "GetErrorLogMessage called for cert error without cert"); + if (errorMessageType == OverridableCertErrorMessage && + mSSLStatus && mSSLStatus->HasServerCert()) { + rv = formatOverridableCertErrorMessage(*mSSLStatus, errorCode, + mHostName, mPort, + suppressPort443, + wantsHtml, + result); + } else { + rv = formatPlainErrorMessage(mHostName, mPort, + errorCode, + suppressPort443, + result); + } + + if (NS_FAILED(rv)) { + result.Truncate(); + } + + return rv; +} + +NS_IMETHODIMP +TransportSecurityInfo::GetErrorCode(int32_t* state) +{ + *state = GetErrorCode(); + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::GetInterface(const nsIID & uuid, void * *result) +{ + if (!NS_IsMainThread()) { + NS_ERROR("nsNSSSocketInfo::GetInterface called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsresult rv; + if (!mCallbacks) { + nsCOMPtr<nsIInterfaceRequestor> ir = new PipUIContext(); + rv = ir->GetInterface(uuid, result); + } else { + rv = mCallbacks->GetInterface(uuid, result); + } + return rv; +} + +// This is a new magic value. However, it re-uses the first 4 bytes +// of the previous value. This is so when older versions attempt to +// read a newer serialized TransportSecurityInfo, they will actually +// fail and return NS_ERROR_FAILURE instead of silently failing. +#define TRANSPORTSECURITYINFOMAGIC { 0xa9863a23, 0x1faa, 0x4169, \ + { 0xb0, 0xd2, 0x81, 0x29, 0xec, 0x7c, 0xb1, 0xde } } +static NS_DEFINE_CID(kTransportSecurityInfoMagic, TRANSPORTSECURITYINFOMAGIC); + +NS_IMETHODIMP +TransportSecurityInfo::Write(nsIObjectOutputStream* stream) +{ + nsresult rv = stream->WriteID(kTransportSecurityInfoMagic); + if (NS_FAILED(rv)) { + return rv; + } + + MutexAutoLock lock(mMutex); + + rv = stream->Write32(mSecurityState); + if (NS_FAILED(rv)) { + return rv; + } + rv = stream->Write32(mSubRequestsBrokenSecurity); + if (NS_FAILED(rv)) { + return rv; + } + rv = stream->Write32(mSubRequestsNoSecurity); + if (NS_FAILED(rv)) { + return rv; + } + rv = stream->Write32(static_cast<uint32_t>(mErrorCode)); + if (NS_FAILED(rv)) { + return rv; + } + if (mErrorMessageCached.IsEmpty()) { + // XXX: uses nsNSSComponent string bundles off the main thread + rv = formatErrorMessage(lock, mErrorCode, mErrorMessageType, + true, true, mErrorMessageCached); + if (NS_FAILED(rv)) { + return rv; + } + } + rv = stream->WriteWStringZ(mErrorMessageCached.get()); + if (NS_FAILED(rv)) { + return rv; + } + + // For successful connections and for connections with overridable errors, + // mSSLStatus will be non-null. However, for connections with non-overridable + // errors, it will be null. + nsCOMPtr<nsISerializable> serializable(mSSLStatus); + rv = NS_WriteOptionalCompoundObject(stream, + serializable, + NS_GET_IID(nsISSLStatus), + true); + if (NS_FAILED(rv)) { + return rv; + } + + rv = NS_WriteOptionalCompoundObject(stream, + mFailedCertChain, + NS_GET_IID(nsIX509CertList), + true); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::Read(nsIObjectInputStream* stream) +{ + nsID id; + nsresult rv = stream->ReadID(&id); + if (NS_FAILED(rv)) { + return rv; + } + if (!id.Equals(kTransportSecurityInfoMagic)) { + return NS_ERROR_UNEXPECTED; + } + + MutexAutoLock lock(mMutex); + + rv = stream->Read32(&mSecurityState); + if (NS_FAILED(rv)) { + return rv; + } + uint32_t subRequestsBrokenSecurity; + rv = stream->Read32(&subRequestsBrokenSecurity); + if (NS_FAILED(rv)) { + return rv; + } + if (subRequestsBrokenSecurity > + static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) { + return NS_ERROR_UNEXPECTED; + } + mSubRequestsBrokenSecurity = subRequestsBrokenSecurity; + uint32_t subRequestsNoSecurity; + rv = stream->Read32(&subRequestsNoSecurity); + if (NS_FAILED(rv)) { + return rv; + } + if (subRequestsNoSecurity > + static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) { + return NS_ERROR_UNEXPECTED; + } + mSubRequestsNoSecurity = subRequestsNoSecurity; + uint32_t errorCode; + rv = stream->Read32(&errorCode); + if (NS_FAILED(rv)) { + return rv; + } + // PRErrorCode will be a negative value + mErrorCode = static_cast<PRErrorCode>(errorCode); + + rv = stream->ReadString(mErrorMessageCached); + if (NS_FAILED(rv)) { + return rv; + } + + // For successful connections and for connections with overridable errors, + // mSSLStatus will be non-null. For connections with non-overridable errors, + // it will be null. + nsCOMPtr<nsISupports> supports; + rv = NS_ReadOptionalObject(stream, true, getter_AddRefs(supports)); + if (NS_FAILED(rv)) { + return rv; + } + mSSLStatus = BitwiseCast<nsSSLStatus*, nsISupports*>(supports.get()); + + nsCOMPtr<nsISupports> failedCertChainSupports; + rv = NS_ReadOptionalObject(stream, true, getter_AddRefs(failedCertChainSupports)); + if (NS_FAILED(rv)) { + return rv; + } + mFailedCertChain = do_QueryInterface(failedCertChainSupports); + + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::GetInterfaces(uint32_t *count, nsIID * **array) +{ + *count = 0; + *array = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::GetScriptableHelper(nsIXPCScriptable **_retval) +{ + *_retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::GetClassDescription(char * *aClassDescription) +{ + *aClassDescription = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +TransportSecurityInfo::GetClassID(nsCID * *aClassID) +{ + *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID)); + if (!*aClassID) + return NS_ERROR_OUT_OF_MEMORY; + return GetClassIDNoAlloc(*aClassID); +} + +NS_IMETHODIMP +TransportSecurityInfo::GetFlags(uint32_t *aFlags) +{ + *aFlags = 0; + return NS_OK; +} + +static NS_DEFINE_CID(kNSSSocketInfoCID, TRANSPORTSECURITYINFO_CID); + +NS_IMETHODIMP +TransportSecurityInfo::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) +{ + *aClassIDNoAlloc = kNSSSocketInfoCID; + return NS_OK; +} + +nsresult +TransportSecurityInfo::GetSSLStatus(nsISSLStatus** _result) +{ + NS_ENSURE_ARG_POINTER(_result); + + *_result = mSSLStatus; + NS_IF_ADDREF(*_result); + + return NS_OK; +} + +nsresult +TransportSecurityInfo::SetSSLStatus(nsSSLStatus *aSSLStatus) +{ + mSSLStatus = aSSLStatus; + + return NS_OK; +} + +/* Formats an error message for non-certificate-related SSL errors + * and non-overridable certificate errors (both are of type + * PlainErrormMessage). Use formatOverridableCertErrorMessage + * for overridable cert errors. + */ +static nsresult +formatPlainErrorMessage(const nsXPIDLCString &host, int32_t port, + PRErrorCode err, + bool suppressPort443, + nsString &returnedMessage) +{ + static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); + + const char16_t *params[1]; + nsresult rv; + + nsCOMPtr<nsINSSComponent> component = do_GetService(kNSSComponentCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (host.Length()) + { + nsString hostWithPort; + + // For now, hide port when it's 443 and we're reporting the error. + // In the future a better mechanism should be used + // to make a decision about showing the port number, possibly by requiring + // the context object to implement a specific interface. + // The motivation is that Mozilla browser would like to hide the port number + // in error pages in the common case. + + hostWithPort.AssignASCII(host); + if (!suppressPort443 || port != 443) { + hostWithPort.Append(':'); + hostWithPort.AppendInt(port); + } + params[0] = hostWithPort.get(); + + nsString formattedString; + rv = component->PIPBundleFormatStringFromName("SSLConnectionErrorPrefix", + params, 1, + formattedString); + if (NS_SUCCEEDED(rv)) + { + returnedMessage.Append(formattedString); + returnedMessage.AppendLiteral("\n\n"); + } + } + + nsString explanation; + rv = nsNSSErrors::getErrorMessageFromCode(err, component, explanation); + if (NS_SUCCEEDED(rv)) + returnedMessage.Append(explanation); + + return NS_OK; +} + +static void +AppendErrorTextUntrusted(PRErrorCode errTrust, + const nsString &host, + nsIX509Cert* ix509, + nsINSSComponent *component, + nsString &returnedMessage) +{ + const char* errorID = nullptr; + const char* errorID2 = nullptr; + const char* errorID3 = nullptr; + bool isSelfSigned; + if (NS_SUCCEEDED(ix509->GetIsSelfSigned(&isSelfSigned)) && isSelfSigned) { + errorID = "certErrorTrust_SelfSigned"; + } + + if (!errorID) { + switch (errTrust) { + case SEC_ERROR_UNKNOWN_ISSUER: + errorID = "certErrorTrust_UnknownIssuer"; + errorID2 = "certErrorTrust_UnknownIssuer2"; + errorID3 = "certErrorTrust_UnknownIssuer3"; + break; + case SEC_ERROR_CA_CERT_INVALID: + errorID = "certErrorTrust_CaInvalid"; + break; + case SEC_ERROR_UNTRUSTED_ISSUER: + errorID = "certErrorTrust_Issuer"; + break; + case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED: + errorID = "certErrorTrust_SignatureAlgorithmDisabled"; + break; + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + errorID = "certErrorTrust_ExpiredIssuer"; + break; + case SEC_ERROR_UNTRUSTED_CERT: + default: + errorID = "certErrorTrust_Untrusted"; + break; + } + } + + const char* errorIDs[] = { errorID, errorID2, errorID3 }; + for (size_t i = 0; i < ArrayLength(errorIDs); i++) { + if (!errorIDs[i]) { + break; + } + + nsString formattedString; + nsresult rv = component->GetPIPNSSBundleString(errorIDs[i], formattedString); + if (NS_SUCCEEDED(rv)) { + returnedMessage.Append(formattedString); + returnedMessage.Append('\n'); + } + } +} + +// Returns the number of dNSName or iPAddress entries encountered in the +// subject alternative name extension of the certificate. +// Returns zero if the extension is not present, could not be decoded, or if it +// does not contain any dNSName or iPAddress entries. +static uint32_t +GetSubjectAltNames(CERTCertificate* nssCert, nsString& allNames) +{ + allNames.Truncate(); + + ScopedAutoSECItem altNameExtension; + SECStatus rv = CERT_FindCertExtension(nssCert, SEC_OID_X509_SUBJECT_ALT_NAME, + &altNameExtension); + if (rv != SECSuccess) { + return 0; + } + UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + if (!arena) { + return 0; + } + CERTGeneralName* sanNameList(CERT_DecodeAltNameExtension(arena.get(), + &altNameExtension)); + if (!sanNameList) { + return 0; + } + + uint32_t nameCount = 0; + CERTGeneralName* current = sanNameList; + do { + nsAutoString name; + switch (current->type) { + case certDNSName: + { + nsDependentCSubstring nameFromCert(BitwiseCast<char*, unsigned char*>( + current->name.other.data), + current->name.other.len); + // dNSName fields are defined as type IA5String and thus should + // be limited to ASCII characters. + if (IsASCII(nameFromCert)) { + name.Assign(NS_ConvertASCIItoUTF16(nameFromCert)); + if (!allNames.IsEmpty()) { + allNames.AppendLiteral(", "); + } + ++nameCount; + allNames.Append(name); + } + } + break; + + case certIPAddress: + { + char buf[INET6_ADDRSTRLEN]; + PRNetAddr addr; + if (current->name.other.len == 4) { + addr.inet.family = PR_AF_INET; + memcpy(&addr.inet.ip, current->name.other.data, current->name.other.len); + PR_NetAddrToString(&addr, buf, sizeof(buf)); + name.AssignASCII(buf); + } else if (current->name.other.len == 16) { + addr.ipv6.family = PR_AF_INET6; + memcpy(&addr.ipv6.ip, current->name.other.data, current->name.other.len); + PR_NetAddrToString(&addr, buf, sizeof(buf)); + name.AssignASCII(buf); + } else { + /* invalid IP address */ + } + if (!name.IsEmpty()) { + if (!allNames.IsEmpty()) { + allNames.AppendLiteral(", "); + } + ++nameCount; + allNames.Append(name); + } + break; + } + + default: // all other types of names are ignored + break; + } + current = CERT_GetNextGeneralName(current); + } while (current != sanNameList); // double linked + + return nameCount; +} + +static nsresult +AppendErrorTextMismatch(const nsString& host, nsIX509Cert* ix509, + nsINSSComponent* component, bool wantsHtml, + nsString& returnedMessage) +{ + // Prepare a default "not valid for <hostname>" string in case anything + // goes wrong (or in case the certificate is not valid for any hostnames). + nsAutoString notValidForHostnameString; + const char16_t* params[1]; + params[0] = host.get(); + nsresult rv = component->PIPBundleFormatStringFromName( + "certErrorMismatch", params, 1, notValidForHostnameString); + if (NS_FAILED(rv)) { + return rv; + } + notValidForHostnameString.Append('\n'); + + UniqueCERTCertificate nssCert(ix509->GetCert()); + if (!nssCert) { + returnedMessage.Append(notValidForHostnameString); + return NS_OK; + } + + nsAutoString allNames; + uint32_t nameCount = GetSubjectAltNames(nssCert.get(), allNames); + if (nameCount == 0) { + returnedMessage.Append(notValidForHostnameString); + } else if (nameCount > 1) { + nsString message; + rv = component->GetPIPNSSBundleString("certErrorMismatchMultiple", message); + if (NS_FAILED(rv)) { + return rv; + } + returnedMessage.Append(message); + returnedMessage.AppendLiteral("\n "); + returnedMessage.Append(allNames); + returnedMessage.AppendLiteral(" \n"); + } else if (nameCount == 1) { + params[0] = allNames.get(); + + const char* stringID = wantsHtml ? "certErrorMismatchSingle2" + : "certErrorMismatchSinglePlain"; + nsAutoString formattedString; + rv = component->PIPBundleFormatStringFromName(stringID, params, 1, + formattedString); + if (NS_FAILED(rv)) { + return rv; + } + returnedMessage.Append(formattedString); + returnedMessage.Append('\n'); + } + + return NS_OK; +} + +static void +GetDateBoundary(nsIX509Cert* ix509, + nsString &formattedDate, + nsString &nowDate, + bool &trueExpired_falseNotYetValid) +{ + trueExpired_falseNotYetValid = true; + formattedDate.Truncate(); + + PRTime notAfter, notBefore, timeToUse; + nsCOMPtr<nsIX509CertValidity> validity; + nsresult rv; + + rv = ix509->GetValidity(getter_AddRefs(validity)); + if (NS_FAILED(rv)) + return; + + rv = validity->GetNotAfter(¬After); + if (NS_FAILED(rv)) + return; + + rv = validity->GetNotBefore(¬Before); + if (NS_FAILED(rv)) + return; + + PRTime now = PR_Now(); + if (now > notAfter) { + timeToUse = notAfter; + } else { + timeToUse = notBefore; + trueExpired_falseNotYetValid = false; + } + + nsCOMPtr<nsIDateTimeFormat> dateTimeFormat = nsIDateTimeFormat::Create(); + if (!dateTimeFormat) { + return; + } + + dateTimeFormat->FormatPRTime(nullptr, kDateFormatLong, kTimeFormatNoSeconds, + timeToUse, formattedDate); + dateTimeFormat->FormatPRTime(nullptr, kDateFormatLong, kTimeFormatNoSeconds, + now, nowDate); +} + +static void +AppendErrorTextTime(nsIX509Cert* ix509, + nsINSSComponent *component, + nsString &returnedMessage) +{ + nsAutoString formattedDate, nowDate; + bool trueExpired_falseNotYetValid; + GetDateBoundary(ix509, formattedDate, nowDate, trueExpired_falseNotYetValid); + + const char16_t *params[2]; + params[0] = formattedDate.get(); // might be empty, if helper function had a problem + params[1] = nowDate.get(); + + const char *key = trueExpired_falseNotYetValid ? + "certErrorExpiredNow" : "certErrorNotYetValidNow"; + nsresult rv; + nsString formattedString; + rv = component->PIPBundleFormatStringFromName( + key, + params, + ArrayLength(params), + formattedString); + if (NS_SUCCEEDED(rv)) + { + returnedMessage.Append(formattedString); + returnedMessage.Append('\n'); + } +} + +static void +AppendErrorTextCode(PRErrorCode errorCodeToReport, + nsINSSComponent *component, + nsString &returnedMessage) +{ + const char *codeName = nsNSSErrors::getDefaultErrorStringName(errorCodeToReport); + if (codeName) + { + nsCString error_id(codeName); + NS_ConvertASCIItoUTF16 idU(error_id); + + const char16_t *params[1]; + params[0] = idU.get(); + + nsString formattedString; + nsresult rv; + rv = component->PIPBundleFormatStringFromName("certErrorCodePrefix2", + params, 1, + formattedString); + if (NS_SUCCEEDED(rv)) { + returnedMessage.Append('\n'); + returnedMessage.Append(formattedString); + returnedMessage.Append('\n'); + } + else { + returnedMessage.AppendLiteral(" ("); + returnedMessage.Append(idU); + returnedMessage.Append(')'); + } + } +} + +/* Formats an error message for overridable certificate errors (of type + * OverridableCertErrorMessage). Use formatPlainErrorMessage to format + * non-overridable cert errors and non-cert-related errors. + */ +static nsresult +formatOverridableCertErrorMessage(nsISSLStatus & sslStatus, + PRErrorCode errorCodeToReport, + const nsXPIDLCString & host, int32_t port, + bool suppressPort443, + bool wantsHtml, + nsString & returnedMessage) +{ + static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); + + const char16_t *params[1]; + nsresult rv; + nsAutoString hostWithPort; + nsAutoString hostWithoutPort; + + // For now, hide port when it's 443 and we're reporting the error. + // In the future a better mechanism should be used + // to make a decision about showing the port number, possibly by requiring + // the context object to implement a specific interface. + // The motivation is that Mozilla browser would like to hide the port number + // in error pages in the common case. + + hostWithoutPort.AppendASCII(host); + if (suppressPort443 && port == 443) { + params[0] = hostWithoutPort.get(); + } else { + hostWithPort.AppendASCII(host); + hostWithPort.Append(':'); + hostWithPort.AppendInt(port); + params[0] = hostWithPort.get(); + } + + nsCOMPtr<nsINSSComponent> component = do_GetService(kNSSComponentCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + returnedMessage.Truncate(); + rv = component->PIPBundleFormatStringFromName("certErrorIntro", params, 1, + returnedMessage); + NS_ENSURE_SUCCESS(rv, rv); + + returnedMessage.AppendLiteral("\n\n"); + + RefPtr<nsIX509Cert> ix509; + rv = sslStatus.GetServerCert(getter_AddRefs(ix509)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isUntrusted; + rv = sslStatus.GetIsUntrusted(&isUntrusted); + NS_ENSURE_SUCCESS(rv, rv); + if (isUntrusted) { + AppendErrorTextUntrusted(errorCodeToReport, hostWithoutPort, ix509, + component, returnedMessage); + } + + bool isDomainMismatch; + rv = sslStatus.GetIsDomainMismatch(&isDomainMismatch); + NS_ENSURE_SUCCESS(rv, rv); + if (isDomainMismatch) { + rv = AppendErrorTextMismatch(hostWithoutPort, ix509, component, wantsHtml, + returnedMessage); + NS_ENSURE_SUCCESS(rv, rv); + } + + bool isNotValidAtThisTime; + rv = sslStatus.GetIsNotValidAtThisTime(&isNotValidAtThisTime); + NS_ENSURE_SUCCESS(rv, rv); + if (isNotValidAtThisTime) { + AppendErrorTextTime(ix509, component, returnedMessage); + } + + AppendErrorTextCode(errorCodeToReport, component, returnedMessage); + + return NS_OK; +} + +// RememberCertErrorsTable + +/*static*/ RememberCertErrorsTable* +RememberCertErrorsTable::sInstance = nullptr; + +RememberCertErrorsTable::RememberCertErrorsTable() + : mErrorHosts() + , mMutex("RememberCertErrorsTable::mMutex") +{ +} + +static nsresult +GetHostPortKey(TransportSecurityInfo* infoObject, nsAutoCString &result) +{ + nsresult rv; + + result.Truncate(); + + nsXPIDLCString hostName; + rv = infoObject->GetHostName(getter_Copies(hostName)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t port; + rv = infoObject->GetPort(&port); + NS_ENSURE_SUCCESS(rv, rv); + + result.Assign(hostName); + result.Append(':'); + result.AppendInt(port); + + return NS_OK; +} + +void +RememberCertErrorsTable::RememberCertHasError(TransportSecurityInfo* infoObject, + nsSSLStatus* status, + SECStatus certVerificationResult) +{ + nsresult rv; + + nsAutoCString hostPortKey; + rv = GetHostPortKey(infoObject, hostPortKey); + if (NS_FAILED(rv)) + return; + + if (certVerificationResult != SECSuccess) { + NS_ASSERTION(status, + "Must have nsSSLStatus object when remembering flags"); + + if (!status) + return; + + CertStateBits bits; + bits.mIsDomainMismatch = status->mIsDomainMismatch; + bits.mIsNotValidAtThisTime = status->mIsNotValidAtThisTime; + bits.mIsUntrusted = status->mIsUntrusted; + + MutexAutoLock lock(mMutex); + mErrorHosts.Put(hostPortKey, bits); + } + else { + MutexAutoLock lock(mMutex); + mErrorHosts.Remove(hostPortKey); + } +} + +void +RememberCertErrorsTable::LookupCertErrorBits(TransportSecurityInfo* infoObject, + nsSSLStatus* status) +{ + // Get remembered error bits from our cache, because of SSL session caching + // the NSS library potentially hasn't notified us for this socket. + if (status->mHaveCertErrorBits) + // Rather do not modify bits if already set earlier + return; + + nsresult rv; + + nsAutoCString hostPortKey; + rv = GetHostPortKey(infoObject, hostPortKey); + if (NS_FAILED(rv)) + return; + + CertStateBits bits; + { + MutexAutoLock lock(mMutex); + if (!mErrorHosts.Get(hostPortKey, &bits)) + // No record was found, this host had no cert errors + return; + } + + // This host had cert errors, update the bits correctly + status->mHaveCertErrorBits = true; + status->mIsDomainMismatch = bits.mIsDomainMismatch; + status->mIsNotValidAtThisTime = bits.mIsNotValidAtThisTime; + status->mIsUntrusted = bits.mIsUntrusted; +} + +void +TransportSecurityInfo::SetStatusErrorBits(nsNSSCertificate* cert, + uint32_t collected_errors) +{ + MutexAutoLock lock(mMutex); + + if (!mSSLStatus) { + mSSLStatus = new nsSSLStatus(); + } + + mSSLStatus->SetServerCert(cert, EVStatus::NotEV); + + mSSLStatus->mHaveCertErrorBits = true; + mSSLStatus->mIsDomainMismatch = + collected_errors & nsICertOverrideService::ERROR_MISMATCH; + mSSLStatus->mIsNotValidAtThisTime = + collected_errors & nsICertOverrideService::ERROR_TIME; + mSSLStatus->mIsUntrusted = + collected_errors & nsICertOverrideService::ERROR_UNTRUSTED; + + RememberCertErrorsTable::GetInstance().RememberCertHasError(this, + mSSLStatus, + SECFailure); +} + +NS_IMETHODIMP +TransportSecurityInfo::GetFailedCertChain(nsIX509CertList** _result) +{ + NS_ASSERTION(_result, "non-NULL destination required"); + + *_result = mFailedCertChain; + NS_IF_ADDREF(*_result); + + return NS_OK; +} + +nsresult +TransportSecurityInfo::SetFailedCertChain(UniqueCERTCertList certList) +{ + nsNSSShutDownPreventionLock lock; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + // nsNSSCertList takes ownership of certList + mFailedCertChain = new nsNSSCertList(Move(certList), lock); + + return NS_OK; +} + +} } // namespace mozilla::psm |