/* -*- 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 "pkix/pkixtypes.h" #include "nsNSSComponent.h" #include "nsIWebProgressListener.h" #include "nsNSSCertificate.h" #include "nsIX509CertValidity.h" #include "nsIDateTimeFormat.h" #include "nsDateTimeFormatCID.h" #include "nsICertOverrideService.h" #include "nsIObjectInputStream.h" #include "nsIObjectOutputStream.h" #include "nsNSSCertHelper.h" #include "nsIArray.h" #include "nsComponentManagerUtils.h" #include "nsReadableUtils.h" #include "nsServiceManagerUtils.h" #include "nsXULAppAPI.h" #include "PSMRunnable.h" #include "mozilla/net/DNS.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(calledFromObject); } 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; } 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 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(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 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(std::numeric_limits::max())) { return NS_ERROR_UNEXPECTED; } mSubRequestsBrokenSecurity = subRequestsBrokenSecurity; uint32_t subRequestsNoSecurity; rv = stream->Read32(&subRequestsNoSecurity); if (NS_FAILED(rv)) { return rv; } if (subRequestsNoSecurity > static_cast(std::numeric_limits::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(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 supports; rv = NS_ReadOptionalObject(stream, true, getter_AddRefs(supports)); if (NS_FAILED(rv)) { return rv; } mSSLStatus = reinterpret_cast(supports.get()); nsCOMPtr 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 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 TRUE if SAN was used to produce names // return FALSE if nothing was produced // names => a single name or a list of names // multipleNames => whether multiple names were delivered static bool GetSubjectAltNames(CERTCertificate *nssCert, nsINSSComponent *component, nsString &allNames, uint32_t &nameCount) { allNames.Truncate(); nameCount = 0; SECItem altNameExtension = {siBuffer, nullptr, 0 }; CERTGeneralName *sanNameList = nullptr; SECStatus rv = CERT_FindCertExtension(nssCert, SEC_OID_X509_SUBJECT_ALT_NAME, &altNameExtension); if (rv != SECSuccess) { return false; } ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); if (!arena) { return false; } sanNameList = CERT_DecodeAltNameExtension(arena.get(), &altNameExtension); if (!sanNameList) { return false; } SECITEM_FreeItem(&altNameExtension, false); CERTGeneralName *current = sanNameList; do { nsAutoString name; switch (current->type) { case certDNSName: { nsDependentCSubstring nameFromCert(reinterpret_cast (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: { // According to DNS.h, this includes space for the null-terminator char buf[net::kNetAddrMaxCStrBufSize] = {0}; PRNetAddr addr; memset(&addr, 0, sizeof(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 true; } static void AppendErrorTextMismatch(const nsString &host, nsIX509Cert* ix509, nsINSSComponent *component, bool wantsHtml, nsString &returnedMessage) { const char16_t *params[1]; nsresult rv; ScopedCERTCertificate nssCert(ix509->GetCert()); if (!nssCert) { // We are unable to extract the valid names, say "not valid for name". params[0] = host.get(); nsString formattedString; rv = component->PIPBundleFormatStringFromName("certErrorMismatch", params, 1, formattedString); if (NS_SUCCEEDED(rv)) { returnedMessage.Append(formattedString); returnedMessage.Append('\n'); } return; } nsString allNames; uint32_t nameCount = 0; bool useSAN = false; if (nssCert) useSAN = GetSubjectAltNames(nssCert.get(), component, allNames, nameCount); if (!useSAN) { char *certName = CERT_GetCommonName(&nssCert->subject); if (certName) { nsDependentCSubstring commonName(certName, strlen(certName)); if (IsUTF8(commonName)) { // Bug 1024781 // We should actually check that the common name is a valid dns name or // ip address and not any string value before adding it to the display // list. ++nameCount; allNames.Assign(NS_ConvertUTF8toUTF16(commonName)); } PORT_Free(certName); } } if (nameCount > 1) { nsString message; rv = component->GetPIPNSSBundleString("certErrorMismatchMultiple", message); if (NS_SUCCEEDED(rv)) { returnedMessage.Append(message); returnedMessage.AppendLiteral("\n "); returnedMessage.Append(allNames); returnedMessage.AppendLiteral(" \n"); } } else if (nameCount == 1) { const char16_t *params[1]; params[0] = allNames.get(); const char *stringID; if (wantsHtml) stringID = "certErrorMismatchSingle2"; else stringID = "certErrorMismatchSinglePlain"; nsString formattedString; rv = component->PIPBundleFormatStringFromName(stringID, params, 1, formattedString); if (NS_SUCCEEDED(rv)) { returnedMessage.Append(formattedString); returnedMessage.Append('\n'); } } else { // nameCount == 0 nsString message; nsresult rv = component->GetPIPNSSBundleString("certErrorMismatchNoNames", message); if (NS_SUCCEEDED(rv)) { returnedMessage.Append(message); returnedMessage.Append('\n'); } } } static void GetDateBoundary(nsIX509Cert* ix509, nsString &formattedDate, nsString &nowDate, bool &trueExpired_falseNotYetValid) { trueExpired_falseNotYetValid = true; formattedDate.Truncate(); PRTime notAfter, notBefore, timeToUse; nsCOMPtr 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 dateTimeFormat(do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv)); if (NS_FAILED(rv)) 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 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 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) { AppendErrorTextMismatch(hostWithoutPort, ix509, component, wantsHtml, returnedMessage); } 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, nsNSSCertificate::ev_status_invalid); 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(ScopedCERTCertList& certList) { nsNSSShutDownPreventionLock lock; if (isAlreadyShutDown()) { return NS_ERROR_NOT_AVAILABLE; } nsCOMPtr comCertList; // nsNSSCertList takes ownership of certList mFailedCertChain = new nsNSSCertList(certList, lock); return NS_OK; } } } // namespace mozilla::psm