michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "TransportSecurityInfo.h" michael@0: michael@0: #include "pkix/pkixtypes.h" michael@0: #include "nsNSSComponent.h" michael@0: #include "nsIWebProgressListener.h" michael@0: #include "nsNSSCertificate.h" michael@0: #include "nsIX509CertValidity.h" michael@0: #include "nsIDateTimeFormat.h" michael@0: #include "nsDateTimeFormatCID.h" michael@0: #include "nsICertOverrideService.h" michael@0: #include "nsIObjectInputStream.h" michael@0: #include "nsIObjectOutputStream.h" michael@0: #include "nsNSSCertHelper.h" michael@0: #include "nsIProgrammingLanguage.h" michael@0: #include "nsIArray.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "PSMRunnable.h" michael@0: michael@0: #include "secerr.h" michael@0: michael@0: //#define DEBUG_SSL_VERBOSE //Enable this define to get minimal michael@0: //reports when doing SSL read/write michael@0: michael@0: //#define DUMP_BUFFER //Enable this define along with michael@0: //DEBUG_SSL_VERBOSE to dump SSL michael@0: //read/write buffer to a log. michael@0: //Uses PR_LOG except on Mac where michael@0: //we always write out to our own michael@0: //file. michael@0: michael@0: namespace mozilla { namespace psm { michael@0: michael@0: TransportSecurityInfo::TransportSecurityInfo() michael@0: : mMutex("TransportSecurityInfo::mMutex"), michael@0: mSecurityState(nsIWebProgressListener::STATE_IS_INSECURE), michael@0: mSubRequestsBrokenSecurity(0), michael@0: mSubRequestsNoSecurity(0), michael@0: mErrorCode(0), michael@0: mErrorMessageType(PlainErrorMessage), michael@0: mPort(0) michael@0: { michael@0: } michael@0: michael@0: TransportSecurityInfo::~TransportSecurityInfo() michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (isAlreadyShutDown()) michael@0: return; michael@0: michael@0: shutdown(calledFromObject); michael@0: } michael@0: michael@0: void michael@0: TransportSecurityInfo::virtualDestroyNSSReference() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(TransportSecurityInfo, michael@0: nsITransportSecurityInfo, michael@0: nsIInterfaceRequestor, michael@0: nsISSLStatusProvider, michael@0: nsIAssociatedContentSecurity, michael@0: nsISerializable, michael@0: nsIClassInfo) michael@0: michael@0: nsresult michael@0: TransportSecurityInfo::SetHostName(const char* host) michael@0: { michael@0: mHostName.Adopt(host ? NS_strdup(host) : 0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TransportSecurityInfo::GetHostName(char **host) michael@0: { michael@0: *host = (mHostName) ? NS_strdup(mHostName) : nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TransportSecurityInfo::SetPort(int32_t aPort) michael@0: { michael@0: mPort = aPort; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TransportSecurityInfo::GetPort(int32_t *aPort) michael@0: { michael@0: *aPort = mPort; michael@0: return NS_OK; michael@0: } michael@0: michael@0: PRErrorCode michael@0: TransportSecurityInfo::GetErrorCode() const michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: return mErrorCode; michael@0: } michael@0: michael@0: void michael@0: TransportSecurityInfo::SetCanceled(PRErrorCode errorCode, michael@0: SSLErrorMessageType errorMessageType) michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: mErrorCode = errorCode; michael@0: mErrorMessageType = errorMessageType; michael@0: mErrorMessageCached.Truncate(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetSecurityState(uint32_t* state) michael@0: { michael@0: *state = mSecurityState; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TransportSecurityInfo::SetSecurityState(uint32_t aState) michael@0: { michael@0: mSecurityState = aState; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* attribute unsigned long countSubRequestsBrokenSecurity; */ michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetCountSubRequestsBrokenSecurity( michael@0: int32_t *aSubRequestsBrokenSecurity) michael@0: { michael@0: *aSubRequestsBrokenSecurity = mSubRequestsBrokenSecurity; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::SetCountSubRequestsBrokenSecurity( michael@0: int32_t aSubRequestsBrokenSecurity) michael@0: { michael@0: mSubRequestsBrokenSecurity = aSubRequestsBrokenSecurity; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* attribute unsigned long countSubRequestsNoSecurity; */ michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetCountSubRequestsNoSecurity( michael@0: int32_t *aSubRequestsNoSecurity) michael@0: { michael@0: *aSubRequestsNoSecurity = mSubRequestsNoSecurity; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::SetCountSubRequestsNoSecurity( michael@0: int32_t aSubRequestsNoSecurity) michael@0: { michael@0: mSubRequestsNoSecurity = aSubRequestsNoSecurity; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::Flush() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetErrorMessage(char16_t** aText) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aText); michael@0: *aText = nullptr; michael@0: michael@0: if (!NS_IsMainThread()) { michael@0: NS_ERROR("nsNSSSocketInfo::GetErrorMessage called off the main thread"); michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: } michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: if (mErrorMessageCached.IsEmpty()) { michael@0: nsresult rv = formatErrorMessage(lock, michael@0: mErrorCode, mErrorMessageType, michael@0: true, true, mErrorMessageCached); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: *aText = ToNewUnicode(mErrorMessageCached); michael@0: return *aText ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: void michael@0: TransportSecurityInfo::GetErrorLogMessage(PRErrorCode errorCode, michael@0: SSLErrorMessageType errorMessageType, michael@0: nsString &result) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: NS_ERROR("nsNSSSocketInfo::GetErrorLogMessage called off the main thread"); michael@0: return; michael@0: } michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: (void) formatErrorMessage(lock, errorCode, errorMessageType, michael@0: false, false, result); michael@0: } michael@0: michael@0: static nsresult michael@0: formatPlainErrorMessage(nsXPIDLCString const & host, int32_t port, michael@0: PRErrorCode err, michael@0: bool suppressPort443, michael@0: nsString &returnedMessage); michael@0: michael@0: static nsresult michael@0: formatOverridableCertErrorMessage(nsISSLStatus & sslStatus, michael@0: PRErrorCode errorCodeToReport, michael@0: const nsXPIDLCString & host, int32_t port, michael@0: bool suppressPort443, michael@0: bool wantsHtml, michael@0: nsString & returnedMessage); michael@0: michael@0: // XXX: uses nsNSSComponent string bundles off the main thread when called by michael@0: // nsNSSSocketInfo::Write(). michael@0: nsresult michael@0: TransportSecurityInfo::formatErrorMessage(MutexAutoLock const & proofOfLock, michael@0: PRErrorCode errorCode, michael@0: SSLErrorMessageType errorMessageType, michael@0: bool wantsHtml, bool suppressPort443, michael@0: nsString &result) michael@0: { michael@0: if (errorCode == 0) { michael@0: result.Truncate(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv; michael@0: NS_ConvertASCIItoUTF16 hostNameU(mHostName); michael@0: NS_ASSERTION(errorMessageType != OverridableCertErrorMessage || michael@0: (mSSLStatus && mSSLStatus->mServerCert && michael@0: mSSLStatus->mHaveCertErrorBits), michael@0: "GetErrorLogMessage called for cert error without cert"); michael@0: if (errorMessageType == OverridableCertErrorMessage && michael@0: mSSLStatus && mSSLStatus->mServerCert) { michael@0: rv = formatOverridableCertErrorMessage(*mSSLStatus, errorCode, michael@0: mHostName, mPort, michael@0: suppressPort443, michael@0: wantsHtml, michael@0: result); michael@0: } else { michael@0: rv = formatPlainErrorMessage(mHostName, mPort, michael@0: errorCode, michael@0: suppressPort443, michael@0: result); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: result.Truncate(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* void getInterface (in nsIIDRef uuid, [iid_is (uuid), retval] out nsQIResult result); */ michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetInterface(const nsIID & uuid, void * *result) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: NS_ERROR("nsNSSSocketInfo::GetInterface called off the main thread"); michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: } michael@0: michael@0: nsresult rv; michael@0: if (!mCallbacks) { michael@0: nsCOMPtr ir = new PipUIContext(); michael@0: rv = ir->GetInterface(uuid, result); michael@0: } else { michael@0: rv = mCallbacks->GetInterface(uuid, result); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: // This is a new magic value. However, it re-uses the first 4 bytes michael@0: // of the previous value. This is so when older versions attempt to michael@0: // read a newer serialized TransportSecurityInfo, they will actually michael@0: // fail and return NS_ERROR_FAILURE instead of silently failing. michael@0: #define TRANSPORTSECURITYINFOMAGIC { 0xa9863a23, 0x28ea, 0x45d2, \ michael@0: { 0xa2, 0x5a, 0x35, 0x7c, 0xae, 0xfa, 0x7f, 0x82 } } michael@0: static NS_DEFINE_CID(kTransportSecurityInfoMagic, TRANSPORTSECURITYINFOMAGIC); michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::Write(nsIObjectOutputStream* stream) michael@0: { michael@0: nsresult rv = stream->WriteID(kTransportSecurityInfoMagic); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: rv = stream->Write32(mSecurityState); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: rv = stream->Write32(mSubRequestsBrokenSecurity); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: rv = stream->Write32(mSubRequestsNoSecurity); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: // XXX: uses nsNSSComponent string bundles off the main thread michael@0: rv = formatErrorMessage(lock, mErrorCode, mErrorMessageType, true, true, michael@0: mErrorMessageCached); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: rv = stream->WriteWStringZ(mErrorMessageCached.get()); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: nsCOMPtr serializable(mSSLStatus); michael@0: rv = stream->WriteCompoundObject(serializable, NS_GET_IID(nsISSLStatus), michael@0: true); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::Read(nsIObjectInputStream* stream) michael@0: { michael@0: nsID id; michael@0: nsresult rv = stream->ReadID(&id); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: if (!id.Equals(kTransportSecurityInfoMagic)) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: rv = stream->Read32(&mSecurityState); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: uint32_t subRequestsBrokenSecurity; michael@0: rv = stream->Read32(&subRequestsBrokenSecurity); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: if (subRequestsBrokenSecurity > michael@0: static_cast(std::numeric_limits::max())) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: mSubRequestsBrokenSecurity = subRequestsBrokenSecurity; michael@0: uint32_t subRequestsNoSecurity; michael@0: rv = stream->Read32(&subRequestsNoSecurity); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: if (subRequestsNoSecurity > michael@0: static_cast(std::numeric_limits::max())) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: mSubRequestsNoSecurity = subRequestsNoSecurity; michael@0: rv = stream->ReadString(mErrorMessageCached); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: mErrorCode = 0; michael@0: nsCOMPtr supports; michael@0: rv = stream->ReadObject(true, getter_AddRefs(supports)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: mSSLStatus = reinterpret_cast(supports.get()); michael@0: if (!mSSLStatus) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetInterfaces(uint32_t *count, nsIID * **array) michael@0: { michael@0: *count = 0; michael@0: *array = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetHelperForLanguage(uint32_t language, michael@0: nsISupports **_retval) michael@0: { michael@0: *_retval = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetContractID(char * *aContractID) michael@0: { michael@0: *aContractID = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetClassDescription(char * *aClassDescription) michael@0: { michael@0: *aClassDescription = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetClassID(nsCID * *aClassID) michael@0: { michael@0: *aClassID = (nsCID*) nsMemory::Alloc(sizeof(nsCID)); michael@0: if (!*aClassID) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: return GetClassIDNoAlloc(*aClassID); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetImplementationLanguage( michael@0: uint32_t *aImplementationLanguage) michael@0: { michael@0: *aImplementationLanguage = nsIProgrammingLanguage::CPLUSPLUS; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetFlags(uint32_t *aFlags) michael@0: { michael@0: *aFlags = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: static NS_DEFINE_CID(kNSSSocketInfoCID, TRANSPORTSECURITYINFO_CID); michael@0: michael@0: NS_IMETHODIMP michael@0: TransportSecurityInfo::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) michael@0: { michael@0: *aClassIDNoAlloc = kNSSSocketInfoCID; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TransportSecurityInfo::GetSSLStatus(nsISSLStatus** _result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(_result); michael@0: michael@0: *_result = mSSLStatus; michael@0: NS_IF_ADDREF(*_result); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TransportSecurityInfo::SetSSLStatus(nsSSLStatus *aSSLStatus) michael@0: { michael@0: mSSLStatus = aSSLStatus; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* Formats an error message for non-certificate-related SSL errors michael@0: * and non-overridable certificate errors (both are of type michael@0: * PlainErrormMessage). Use formatOverridableCertErrorMessage michael@0: * for overridable cert errors. michael@0: */ michael@0: static nsresult michael@0: formatPlainErrorMessage(const nsXPIDLCString &host, int32_t port, michael@0: PRErrorCode err, michael@0: bool suppressPort443, michael@0: nsString &returnedMessage) michael@0: { michael@0: static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); michael@0: michael@0: const char16_t *params[1]; michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr component = do_GetService(kNSSComponentCID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (host.Length()) michael@0: { michael@0: nsString hostWithPort; michael@0: michael@0: // For now, hide port when it's 443 and we're reporting the error. michael@0: // In the future a better mechanism should be used michael@0: // to make a decision about showing the port number, possibly by requiring michael@0: // the context object to implement a specific interface. michael@0: // The motivation is that Mozilla browser would like to hide the port number michael@0: // in error pages in the common case. michael@0: michael@0: hostWithPort.AssignASCII(host); michael@0: if (!suppressPort443 || port != 443) { michael@0: hostWithPort.AppendLiteral(":"); michael@0: hostWithPort.AppendInt(port); michael@0: } michael@0: params[0] = hostWithPort.get(); michael@0: michael@0: nsString formattedString; michael@0: rv = component->PIPBundleFormatStringFromName("SSLConnectionErrorPrefix", michael@0: params, 1, michael@0: formattedString); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: returnedMessage.Append(formattedString); michael@0: returnedMessage.Append(NS_LITERAL_STRING("\n\n")); michael@0: } michael@0: } michael@0: michael@0: nsString explanation; michael@0: rv = nsNSSErrors::getErrorMessageFromCode(err, component, explanation); michael@0: if (NS_SUCCEEDED(rv)) michael@0: returnedMessage.Append(explanation); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void michael@0: AppendErrorTextUntrusted(PRErrorCode errTrust, michael@0: const nsString &host, michael@0: nsIX509Cert* ix509, michael@0: nsINSSComponent *component, michael@0: nsString &returnedMessage) michael@0: { michael@0: const char *errorID = nullptr; michael@0: nsCOMPtr cert3 = do_QueryInterface(ix509); michael@0: if (cert3) { michael@0: bool isSelfSigned; michael@0: if (NS_SUCCEEDED(cert3->GetIsSelfSigned(&isSelfSigned)) michael@0: && isSelfSigned) { michael@0: errorID = "certErrorTrust_SelfSigned"; michael@0: } michael@0: } michael@0: michael@0: if (!errorID) { michael@0: switch (errTrust) { michael@0: case SEC_ERROR_UNKNOWN_ISSUER: michael@0: { michael@0: nsCOMPtr chain; michael@0: ix509->GetChain(getter_AddRefs(chain)); michael@0: uint32_t length = 0; michael@0: if (chain && NS_FAILED(chain->GetLength(&length))) michael@0: length = 0; michael@0: if (length == 1) michael@0: errorID = "certErrorTrust_MissingChain"; michael@0: else michael@0: errorID = "certErrorTrust_UnknownIssuer"; michael@0: break; michael@0: } michael@0: case SEC_ERROR_CA_CERT_INVALID: michael@0: errorID = "certErrorTrust_CaInvalid"; michael@0: break; michael@0: case SEC_ERROR_UNTRUSTED_ISSUER: michael@0: errorID = "certErrorTrust_Issuer"; michael@0: break; michael@0: case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED: michael@0: errorID = "certErrorTrust_SignatureAlgorithmDisabled"; michael@0: break; michael@0: case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: michael@0: errorID = "certErrorTrust_ExpiredIssuer"; michael@0: break; michael@0: case SEC_ERROR_UNTRUSTED_CERT: michael@0: default: michael@0: errorID = "certErrorTrust_Untrusted"; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: nsString formattedString; michael@0: nsresult rv = component->GetPIPNSSBundleString(errorID, michael@0: formattedString); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: returnedMessage.Append(formattedString); michael@0: returnedMessage.Append(NS_LITERAL_STRING("\n")); michael@0: } michael@0: } michael@0: michael@0: // returns TRUE if SAN was used to produce names michael@0: // return FALSE if nothing was produced michael@0: // names => a single name or a list of names michael@0: // multipleNames => whether multiple names were delivered michael@0: static bool michael@0: GetSubjectAltNames(CERTCertificate *nssCert, michael@0: nsINSSComponent *component, michael@0: nsString &allNames, michael@0: uint32_t &nameCount) michael@0: { michael@0: allNames.Truncate(); michael@0: nameCount = 0; michael@0: michael@0: PLArenaPool *san_arena = nullptr; michael@0: SECItem altNameExtension = {siBuffer, nullptr, 0 }; michael@0: CERTGeneralName *sanNameList = nullptr; michael@0: michael@0: SECStatus rv = CERT_FindCertExtension(nssCert, SEC_OID_X509_SUBJECT_ALT_NAME, michael@0: &altNameExtension); michael@0: if (rv != SECSuccess) michael@0: return false; michael@0: michael@0: san_arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); michael@0: if (!san_arena) michael@0: return false; michael@0: michael@0: sanNameList = CERT_DecodeAltNameExtension(san_arena, &altNameExtension); michael@0: if (!sanNameList) michael@0: return false; michael@0: michael@0: SECITEM_FreeItem(&altNameExtension, false); michael@0: michael@0: CERTGeneralName *current = sanNameList; michael@0: do { michael@0: nsAutoString name; michael@0: switch (current->type) { michael@0: case certDNSName: michael@0: { michael@0: nsDependentCSubstring nameFromCert(reinterpret_cast michael@0: (current->name.other.data), michael@0: current->name.other.len); michael@0: // dNSName fields are defined as type IA5String and thus should michael@0: // be limited to ASCII characters. michael@0: if (IsASCII(nameFromCert)) { michael@0: name.Assign(NS_ConvertASCIItoUTF16(nameFromCert)); michael@0: if (!allNames.IsEmpty()) { michael@0: allNames.Append(NS_LITERAL_STRING(", ")); michael@0: } michael@0: ++nameCount; michael@0: allNames.Append(name); michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case certIPAddress: michael@0: { michael@0: char buf[INET6_ADDRSTRLEN]; michael@0: PRNetAddr addr; michael@0: if (current->name.other.len == 4) { michael@0: addr.inet.family = PR_AF_INET; michael@0: memcpy(&addr.inet.ip, current->name.other.data, current->name.other.len); michael@0: PR_NetAddrToString(&addr, buf, sizeof(buf)); michael@0: name.AssignASCII(buf); michael@0: } else if (current->name.other.len == 16) { michael@0: addr.ipv6.family = PR_AF_INET6; michael@0: memcpy(&addr.ipv6.ip, current->name.other.data, current->name.other.len); michael@0: PR_NetAddrToString(&addr, buf, sizeof(buf)); michael@0: name.AssignASCII(buf); michael@0: } else { michael@0: /* invalid IP address */ michael@0: } michael@0: if (!name.IsEmpty()) { michael@0: if (!allNames.IsEmpty()) { michael@0: allNames.Append(NS_LITERAL_STRING(", ")); michael@0: } michael@0: ++nameCount; michael@0: allNames.Append(name); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: default: // all other types of names are ignored michael@0: break; michael@0: } michael@0: current = CERT_GetNextGeneralName(current); michael@0: } while (current != sanNameList); // double linked michael@0: michael@0: PORT_FreeArena(san_arena, false); michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: AppendErrorTextMismatch(const nsString &host, michael@0: nsIX509Cert* ix509, michael@0: nsINSSComponent *component, michael@0: bool wantsHtml, michael@0: nsString &returnedMessage) michael@0: { michael@0: const char16_t *params[1]; michael@0: nsresult rv; michael@0: michael@0: mozilla::pkix::ScopedCERTCertificate nssCert; michael@0: michael@0: nsCOMPtr cert2 = do_QueryInterface(ix509, &rv); michael@0: if (cert2) michael@0: nssCert = cert2->GetCert(); michael@0: michael@0: if (!nssCert) { michael@0: // We are unable to extract the valid names, say "not valid for name". michael@0: params[0] = host.get(); michael@0: nsString formattedString; michael@0: rv = component->PIPBundleFormatStringFromName("certErrorMismatch", michael@0: params, 1, michael@0: formattedString); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: returnedMessage.Append(formattedString); michael@0: returnedMessage.Append(NS_LITERAL_STRING("\n")); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: nsString allNames; michael@0: uint32_t nameCount = 0; michael@0: bool useSAN = false; michael@0: michael@0: if (nssCert) michael@0: useSAN = GetSubjectAltNames(nssCert.get(), component, allNames, nameCount); michael@0: michael@0: if (!useSAN) { michael@0: char *certName = nullptr; michael@0: // currently CERT_FindNSStringExtension is not being exported by NSS. michael@0: // If it gets exported, enable the following line. michael@0: // certName = CERT_FindNSStringExtension(nssCert, SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME); michael@0: // However, it has been discussed to treat the extension as obsolete and ignore it. michael@0: if (!certName) michael@0: certName = CERT_GetCommonName(&nssCert->subject); michael@0: if (certName) { michael@0: nsDependentCSubstring commonName(certName, strlen(certName)); michael@0: if (IsUTF8(commonName)) { michael@0: // Bug 1024781 michael@0: // We should actually check that the common name is a valid dns name or michael@0: // ip address and not any string value before adding it to the display michael@0: // list. michael@0: ++nameCount; michael@0: allNames.Assign(NS_ConvertUTF8toUTF16(commonName)); michael@0: } michael@0: PORT_Free(certName); michael@0: } michael@0: } michael@0: michael@0: if (nameCount > 1) { michael@0: nsString message; michael@0: rv = component->GetPIPNSSBundleString("certErrorMismatchMultiple", michael@0: message); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: returnedMessage.Append(message); michael@0: returnedMessage.Append(NS_LITERAL_STRING("\n ")); michael@0: returnedMessage.Append(allNames); michael@0: returnedMessage.Append(NS_LITERAL_STRING(" \n")); michael@0: } michael@0: } michael@0: else if (nameCount == 1) { michael@0: const char16_t *params[1]; michael@0: params[0] = allNames.get(); michael@0: michael@0: const char *stringID; michael@0: if (wantsHtml) michael@0: stringID = "certErrorMismatchSingle2"; michael@0: else michael@0: stringID = "certErrorMismatchSinglePlain"; michael@0: michael@0: nsString formattedString; michael@0: rv = component->PIPBundleFormatStringFromName(stringID, michael@0: params, 1, michael@0: formattedString); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: returnedMessage.Append(formattedString); michael@0: returnedMessage.Append(NS_LITERAL_STRING("\n")); michael@0: } michael@0: } michael@0: else { // nameCount == 0 michael@0: nsString message; michael@0: nsresult rv = component->GetPIPNSSBundleString("certErrorMismatchNoNames", michael@0: message); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: returnedMessage.Append(message); michael@0: returnedMessage.Append(NS_LITERAL_STRING("\n")); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void michael@0: GetDateBoundary(nsIX509Cert* ix509, michael@0: nsString &formattedDate, michael@0: nsString &nowDate, michael@0: bool &trueExpired_falseNotYetValid) michael@0: { michael@0: trueExpired_falseNotYetValid = true; michael@0: formattedDate.Truncate(); michael@0: michael@0: PRTime notAfter, notBefore, timeToUse; michael@0: nsCOMPtr validity; michael@0: nsresult rv; michael@0: michael@0: rv = ix509->GetValidity(getter_AddRefs(validity)); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: rv = validity->GetNotAfter(¬After); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: rv = validity->GetNotBefore(¬Before); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: PRTime now = PR_Now(); michael@0: if (now > notAfter) { michael@0: timeToUse = notAfter; michael@0: } else { michael@0: timeToUse = notBefore; michael@0: trueExpired_falseNotYetValid = false; michael@0: } michael@0: michael@0: nsCOMPtr dateTimeFormat(do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv)); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: dateTimeFormat->FormatPRTime(nullptr, kDateFormatShort, michael@0: kTimeFormatNoSeconds, timeToUse, michael@0: formattedDate); michael@0: dateTimeFormat->FormatPRTime(nullptr, kDateFormatShort, michael@0: kTimeFormatNoSeconds, now, michael@0: nowDate); michael@0: } michael@0: michael@0: static void michael@0: AppendErrorTextTime(nsIX509Cert* ix509, michael@0: nsINSSComponent *component, michael@0: nsString &returnedMessage) michael@0: { michael@0: nsAutoString formattedDate, nowDate; michael@0: bool trueExpired_falseNotYetValid; michael@0: GetDateBoundary(ix509, formattedDate, nowDate, trueExpired_falseNotYetValid); michael@0: michael@0: const char16_t *params[2]; michael@0: params[0] = formattedDate.get(); // might be empty, if helper function had a problem michael@0: params[1] = nowDate.get(); michael@0: michael@0: const char *key = trueExpired_falseNotYetValid ? michael@0: "certErrorExpiredNow" : "certErrorNotYetValidNow"; michael@0: nsresult rv; michael@0: nsString formattedString; michael@0: rv = component->PIPBundleFormatStringFromName( michael@0: key, michael@0: params, michael@0: ArrayLength(params), michael@0: formattedString); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: returnedMessage.Append(formattedString); michael@0: returnedMessage.Append(NS_LITERAL_STRING("\n")); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: AppendErrorTextCode(PRErrorCode errorCodeToReport, michael@0: nsINSSComponent *component, michael@0: nsString &returnedMessage) michael@0: { michael@0: const char *codeName = nsNSSErrors::getDefaultErrorStringName(errorCodeToReport); michael@0: if (codeName) michael@0: { michael@0: nsCString error_id(codeName); michael@0: ToLowerCase(error_id); michael@0: NS_ConvertASCIItoUTF16 idU(error_id); michael@0: michael@0: const char16_t *params[1]; michael@0: params[0] = idU.get(); michael@0: michael@0: nsString formattedString; michael@0: nsresult rv; michael@0: rv = component->PIPBundleFormatStringFromName("certErrorCodePrefix", michael@0: params, 1, michael@0: formattedString); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: returnedMessage.Append(NS_LITERAL_STRING("\n")); michael@0: returnedMessage.Append(formattedString); michael@0: returnedMessage.Append(NS_LITERAL_STRING("\n")); michael@0: } michael@0: else { michael@0: returnedMessage.Append(NS_LITERAL_STRING(" (")); michael@0: returnedMessage.Append(idU); michael@0: returnedMessage.Append(NS_LITERAL_STRING(")")); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Formats an error message for overridable certificate errors (of type michael@0: * OverridableCertErrorMessage). Use formatPlainErrorMessage to format michael@0: * non-overridable cert errors and non-cert-related errors. michael@0: */ michael@0: static nsresult michael@0: formatOverridableCertErrorMessage(nsISSLStatus & sslStatus, michael@0: PRErrorCode errorCodeToReport, michael@0: const nsXPIDLCString & host, int32_t port, michael@0: bool suppressPort443, michael@0: bool wantsHtml, michael@0: nsString & returnedMessage) michael@0: { michael@0: static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); michael@0: michael@0: const char16_t *params[1]; michael@0: nsresult rv; michael@0: nsAutoString hostWithPort; michael@0: nsAutoString hostWithoutPort; michael@0: michael@0: // For now, hide port when it's 443 and we're reporting the error. michael@0: // In the future a better mechanism should be used michael@0: // to make a decision about showing the port number, possibly by requiring michael@0: // the context object to implement a specific interface. michael@0: // The motivation is that Mozilla browser would like to hide the port number michael@0: // in error pages in the common case. michael@0: michael@0: hostWithoutPort.AppendASCII(host); michael@0: if (suppressPort443 && port == 443) { michael@0: params[0] = hostWithoutPort.get(); michael@0: } else { michael@0: hostWithPort.AppendASCII(host); michael@0: hostWithPort.Append(':'); michael@0: hostWithPort.AppendInt(port); michael@0: params[0] = hostWithPort.get(); michael@0: } michael@0: michael@0: nsCOMPtr component = do_GetService(kNSSComponentCID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: returnedMessage.Truncate(); michael@0: rv = component->PIPBundleFormatStringFromName("certErrorIntro", params, 1, michael@0: returnedMessage); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: returnedMessage.Append(NS_LITERAL_STRING("\n\n")); michael@0: michael@0: RefPtr ix509; michael@0: rv = sslStatus.GetServerCert(byRef(ix509)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool isUntrusted; michael@0: rv = sslStatus.GetIsUntrusted(&isUntrusted); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (isUntrusted) { michael@0: AppendErrorTextUntrusted(errorCodeToReport, hostWithoutPort, ix509, michael@0: component, returnedMessage); michael@0: } michael@0: michael@0: bool isDomainMismatch; michael@0: rv = sslStatus.GetIsDomainMismatch(&isDomainMismatch); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (isDomainMismatch) { michael@0: AppendErrorTextMismatch(hostWithoutPort, ix509, component, wantsHtml, returnedMessage); michael@0: } michael@0: michael@0: bool isNotValidAtThisTime; michael@0: rv = sslStatus.GetIsNotValidAtThisTime(&isNotValidAtThisTime); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (isNotValidAtThisTime) { michael@0: AppendErrorTextTime(ix509, component, returnedMessage); michael@0: } michael@0: michael@0: AppendErrorTextCode(errorCodeToReport, component, returnedMessage); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // RememberCertErrorsTable michael@0: michael@0: /*static*/ RememberCertErrorsTable* michael@0: RememberCertErrorsTable::sInstance = nullptr; michael@0: michael@0: RememberCertErrorsTable::RememberCertErrorsTable() michael@0: : mErrorHosts(16) michael@0: , mMutex("RememberCertErrorsTable::mMutex") michael@0: { michael@0: } michael@0: michael@0: static nsresult michael@0: GetHostPortKey(TransportSecurityInfo* infoObject, nsAutoCString &result) michael@0: { michael@0: nsresult rv; michael@0: michael@0: result.Truncate(); michael@0: michael@0: nsXPIDLCString hostName; michael@0: rv = infoObject->GetHostName(getter_Copies(hostName)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t port; michael@0: rv = infoObject->GetPort(&port); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: result.Assign(hostName); michael@0: result.Append(':'); michael@0: result.AppendInt(port); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: RememberCertErrorsTable::RememberCertHasError(TransportSecurityInfo* infoObject, michael@0: nsSSLStatus* status, michael@0: SECStatus certVerificationResult) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsAutoCString hostPortKey; michael@0: rv = GetHostPortKey(infoObject, hostPortKey); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: if (certVerificationResult != SECSuccess) { michael@0: NS_ASSERTION(status, michael@0: "Must have nsSSLStatus object when remembering flags"); michael@0: michael@0: if (!status) michael@0: return; michael@0: michael@0: CertStateBits bits; michael@0: bits.mIsDomainMismatch = status->mIsDomainMismatch; michael@0: bits.mIsNotValidAtThisTime = status->mIsNotValidAtThisTime; michael@0: bits.mIsUntrusted = status->mIsUntrusted; michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: mErrorHosts.Put(hostPortKey, bits); michael@0: } michael@0: else { michael@0: MutexAutoLock lock(mMutex); michael@0: mErrorHosts.Remove(hostPortKey); michael@0: } michael@0: } michael@0: michael@0: void michael@0: RememberCertErrorsTable::LookupCertErrorBits(TransportSecurityInfo* infoObject, michael@0: nsSSLStatus* status) michael@0: { michael@0: // Get remembered error bits from our cache, because of SSL session caching michael@0: // the NSS library potentially hasn't notified us for this socket. michael@0: if (status->mHaveCertErrorBits) michael@0: // Rather do not modify bits if already set earlier michael@0: return; michael@0: michael@0: nsresult rv; michael@0: michael@0: nsAutoCString hostPortKey; michael@0: rv = GetHostPortKey(infoObject, hostPortKey); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: CertStateBits bits; michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: if (!mErrorHosts.Get(hostPortKey, &bits)) michael@0: // No record was found, this host had no cert errors michael@0: return; michael@0: } michael@0: michael@0: // This host had cert errors, update the bits correctly michael@0: status->mHaveCertErrorBits = true; michael@0: status->mIsDomainMismatch = bits.mIsDomainMismatch; michael@0: status->mIsNotValidAtThisTime = bits.mIsNotValidAtThisTime; michael@0: status->mIsUntrusted = bits.mIsUntrusted; michael@0: } michael@0: michael@0: void michael@0: TransportSecurityInfo::SetStatusErrorBits(nsIX509Cert & cert, michael@0: uint32_t collected_errors) michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: if (!mSSLStatus) michael@0: mSSLStatus = new nsSSLStatus(); michael@0: michael@0: mSSLStatus->mServerCert = &cert; michael@0: michael@0: mSSLStatus->mHaveCertErrorBits = true; michael@0: mSSLStatus->mIsDomainMismatch = michael@0: collected_errors & nsICertOverrideService::ERROR_MISMATCH; michael@0: mSSLStatus->mIsNotValidAtThisTime = michael@0: collected_errors & nsICertOverrideService::ERROR_TIME; michael@0: mSSLStatus->mIsUntrusted = michael@0: collected_errors & nsICertOverrideService::ERROR_UNTRUSTED; michael@0: michael@0: RememberCertErrorsTable::GetInstance().RememberCertHasError(this, michael@0: mSSLStatus, michael@0: SECFailure); michael@0: } michael@0: michael@0: } } // namespace mozilla::psm