/* -*- 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 "nsNSSCertificate.h" #include "CertVerifier.h" #include "ExtendedValidation.h" #include "NSSCertDBTrustDomain.h" #include "X509CertValidity.h" #include "certdb.h" #include "ipc/IPCMessageUtils.h" #include "mozilla/Assertions.h" #include "mozilla/Base64.h" #include "mozilla/Casting.h" #include "mozilla/NotNull.h" #include "mozilla/Span.h" #include "mozilla/TextUtils.h" #include "mozilla/Unused.h" #include "mozilla/ipc/TransportSecurityInfoUtils.h" #include "mozilla/ipc/IPDLParamTraits.h" #include "mozilla/net/DNS.h" #include "mozpkix/Result.h" #include "mozpkix/pkixnss.h" #include "mozpkix/pkixtypes.h" #include "mozpkix/pkixutil.h" #include "nsArray.h" #include "nsCOMPtr.h" #include "nsIClassInfoImpl.h" #include "nsIObjectInputStream.h" #include "nsIObjectOutputStream.h" #include "nsIX509Cert.h" #include "nsNSSCertHelper.h" #include "nsNSSCertTrust.h" #include "nsPK11TokenDB.h" #include "nsPKCS12Blob.h" #include "nsProxyRelease.h" #include "nsReadableUtils.h" #include "nsString.h" #include "nsThreadUtils.h" #include "nsUnicharUtils.h" #include "nspr.h" #include "prerror.h" #include "secasn1.h" #include "secder.h" #include "secerr.h" #include "ssl.h" #ifdef XP_WIN # include // for htonl #endif using namespace mozilla; using namespace mozilla::psm; extern LazyLogModule gPIPNSSLog; NS_IMPL_ISUPPORTS(nsNSSCertificate, nsIX509Cert, nsISerializable, nsIClassInfo) nsNSSCertificate::nsNSSCertificate() : mCert("nsNSSCertificate::mCert") {} nsNSSCertificate::nsNSSCertificate(CERTCertificate* cert) : mCert("nsNSSCertificate::mCert") { if (cert) { mDER.AppendElements(cert->derCert.data, cert->derCert.len); auto lock = mCert.Lock(); auto& maybeCert = lock.ref(); maybeCert.emplace(UniqueCERTCertificate(CERT_DupCertificate(cert))); } } nsNSSCertificate::nsNSSCertificate(nsTArray&& der) : mDER(std::move(der)), mCert("nsNSSCertificate::mCert") {} UniqueCERTCertificate nsNSSCertificate::GetOrInstantiateCert() { auto lock = mCert.Lock(); auto& maybeCert = lock.ref(); if (maybeCert.isSome()) { return UniqueCERTCertificate(CERT_DupCertificate((*maybeCert).get())); } if (!EnsureNSSInitializedChromeOrContent()) { return nullptr; } SECItem derItem = {siBuffer, mDER.Elements(), static_cast(mDER.Length())}; UniqueCERTCertificate cert(CERT_NewTempCertificate( CERT_GetDefaultCertDB(), &derItem, nullptr, false, true)); if (!cert) { return nullptr; } maybeCert.emplace(std::move(cert)); return UniqueCERTCertificate(CERT_DupCertificate((*maybeCert).get())); } nsresult nsNSSCertificate::GetCertType(uint32_t* aCertType) { UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } CERTCertTrust certTrust{0, 0, 0}; // If there is no stored trust information, CERT_GetCertTrust will return // SECFailure. This isn't a failure. In this case, all trust bits will remain // unset. Unused << CERT_GetCertTrust(cert.get(), &certTrust); nsNSSCertTrust trust(&certTrust); if (cert->nickname && trust.HasAnyUser()) { *aCertType = nsIX509Cert::USER_CERT; return NS_OK; } if (trust.HasAnyCA()) { *aCertType = nsIX509Cert::CA_CERT; return NS_OK; } if (trust.HasPeer(true, false)) { *aCertType = nsIX509Cert::SERVER_CERT; return NS_OK; } if (trust.HasPeer(false, true) && cert->emailAddr) { *aCertType = nsIX509Cert::EMAIL_CERT; return NS_OK; } if (CERT_IsCACert(cert.get(), nullptr)) { *aCertType = nsIX509Cert::CA_CERT; return NS_OK; } if (cert->emailAddr) { *aCertType = nsIX509Cert::EMAIL_CERT; return NS_OK; } *aCertType = nsIX509Cert::UNKNOWN_CERT; return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetIsBuiltInRoot(bool* aIsBuiltInRoot) { NS_ENSURE_ARG(aIsBuiltInRoot); pkix::Input certInput; pkix::Result rv = certInput.Init(mDER.Elements(), mDER.Length()); if (rv != pkix::Result::Success) { return NS_ERROR_FAILURE; } rv = IsCertBuiltInRoot(certInput, *aIsBuiltInRoot); if (rv != pkix::Result::Success) { return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetDbKey(nsACString& aDbKey) { static_assert(sizeof(uint64_t) == 8, "type size consistency check"); static_assert(sizeof(uint32_t) == 4, "type size consistency check"); pkix::Input certInput; pkix::Result result = certInput.Init(mDER.Elements(), mDER.Length()); if (result != pkix::Result::Success) { return NS_ERROR_INVALID_ARG; } // NB: since we're not building a trust path, the endEntityOrCA parameter is // irrelevant. pkix::BackCert cert(certInput, pkix::EndEntityOrCA::MustBeEndEntity, nullptr); result = cert.Init(); if (result != pkix::Result::Success) { return NS_ERROR_INVALID_ARG; } // The format of the key is the base64 encoding of the following: // 4 bytes: {0, 0, 0, 0} (this was intended to be the module ID, but it was // never implemented) // 4 bytes: {0, 0, 0, 0} (this was intended to be the slot ID, but it was // never implemented) // 4 bytes: // 4 bytes: // n bytes: // m bytes: nsAutoCString buf; const char leadingZeroes[] = {0, 0, 0, 0, 0, 0, 0, 0}; buf.Append(leadingZeroes, sizeof(leadingZeroes)); uint32_t serialNumberLen = htonl(cert.GetSerialNumber().GetLength()); buf.Append(BitwiseCast(&serialNumberLen), sizeof(uint32_t)); uint32_t issuerLen = htonl(cert.GetIssuer().GetLength()); buf.Append(BitwiseCast(&issuerLen), sizeof(uint32_t)); buf.Append(BitwiseCast( cert.GetSerialNumber().UnsafeGetData()), cert.GetSerialNumber().GetLength()); buf.Append(BitwiseCast( cert.GetIssuer().UnsafeGetData()), cert.GetIssuer().GetLength()); return Base64Encode(buf, aDbKey); } NS_IMETHODIMP nsNSSCertificate::GetDisplayName(nsAString& aDisplayName) { aDisplayName.Truncate(); UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } UniquePORTString commonName(CERT_GetCommonName(&cert->subject)); UniquePORTString organizationalUnitName(CERT_GetOrgUnitName(&cert->subject)); UniquePORTString organizationName(CERT_GetOrgName(&cert->subject)); bool isBuiltInRoot; nsresult rv = GetIsBuiltInRoot(&isBuiltInRoot); if (NS_FAILED(rv)) { return rv; } // Only use the nickname for built-in roots where we already have a hard-coded // reasonable display name (unfortunately we have to strip off the leading // slot identifier followed by a ':'). Otherwise, attempt to use the following // in order: // - the common name, if present // - an organizational unit name, if present // - an organization name, if present // - the entire subject distinguished name, if non-empty // - an email address, if one can be found // In the unlikely event that none of these fields are present and non-empty // (the subject really shouldn't be empty), an empty string is returned. nsAutoCString builtInRootNickname; if (isBuiltInRoot) { nsAutoCString fullNickname(cert->nickname); int32_t index = fullNickname.Find(":"); if (index != kNotFound) { // Substring will gracefully handle the case where index is the last // character in the string (that is, if the nickname is just // "Builtin Object Token:"). In that case, we'll get an empty string. builtInRootNickname = Substring(fullNickname, AssertedCast(index + 1)); } } const char* nameOptions[] = {builtInRootNickname.get(), commonName.get(), organizationalUnitName.get(), organizationName.get(), cert->subjectName, cert->emailAddr}; for (auto nameOption : nameOptions) { if (nameOption) { size_t len = strlen(nameOption); if (len > 0) { LossyUTF8ToUTF16(nameOption, len, aDisplayName); return NS_OK; } } } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetEmailAddress(nsAString& aEmailAddress) { UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } if (cert->emailAddr) { CopyUTF8toUTF16(MakeStringSpan(cert->emailAddr), aEmailAddress); } else { GetPIPNSSBundleString("CertNoEmailAddress", aEmailAddress); } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetEmailAddresses(nsTArray& aAddresses) { UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } uint32_t length = 0; for (const char* aAddr = CERT_GetFirstEmailAddress(cert.get()); aAddr; aAddr = CERT_GetNextEmailAddress(cert.get(), aAddr)) { ++(length); } aAddresses.SetCapacity(length); for (const char* aAddr = CERT_GetFirstEmailAddress(cert.get()); aAddr; aAddr = CERT_GetNextEmailAddress(cert.get(), aAddr)) { CopyASCIItoUTF16(MakeStringSpan(aAddr), *aAddresses.AppendElement()); } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::ContainsEmailAddress(const nsAString& aEmailAddress, bool* result) { NS_ENSURE_ARG(result); *result = false; UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } for (const char* aAddr = CERT_GetFirstEmailAddress(cert.get()); aAddr; aAddr = CERT_GetNextEmailAddress(cert.get(), aAddr)) { nsAutoString certAddr; LossyUTF8ToUTF16(aAddr, strlen(aAddr), certAddr); ToLowerCase(certAddr); nsAutoString testAddr(aEmailAddress); ToLowerCase(testAddr); if (certAddr == testAddr) { *result = true; break; } } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetCommonName(nsAString& aCommonName) { aCommonName.Truncate(); UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } UniquePORTString commonName(CERT_GetCommonName(&cert->subject)); if (commonName) { LossyUTF8ToUTF16(commonName.get(), strlen(commonName.get()), aCommonName); } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetOrganization(nsAString& aOrganization) { aOrganization.Truncate(); UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } UniquePORTString organization(CERT_GetOrgName(&cert->subject)); if (organization) { LossyUTF8ToUTF16(organization.get(), strlen(organization.get()), aOrganization); } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetIssuerCommonName(nsAString& aCommonName) { aCommonName.Truncate(); UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } UniquePORTString commonName(CERT_GetCommonName(&cert->issuer)); if (commonName) { LossyUTF8ToUTF16(commonName.get(), strlen(commonName.get()), aCommonName); } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetIssuerOrganization(nsAString& aOrganization) { aOrganization.Truncate(); UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } UniquePORTString organization(CERT_GetOrgName(&cert->issuer)); if (organization) { LossyUTF8ToUTF16(organization.get(), strlen(organization.get()), aOrganization); } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetIssuerOrganizationUnit(nsAString& aOrganizationUnit) { aOrganizationUnit.Truncate(); UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } UniquePORTString organizationUnit(CERT_GetOrgUnitName(&cert->issuer)); if (organizationUnit) { LossyUTF8ToUTF16(organizationUnit.get(), strlen(organizationUnit.get()), aOrganizationUnit); } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetOrganizationalUnit(nsAString& aOrganizationalUnit) { aOrganizationalUnit.Truncate(); UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } UniquePORTString orgunit(CERT_GetOrgUnitName(&cert->subject)); if (orgunit) { LossyUTF8ToUTF16(orgunit.get(), strlen(orgunit.get()), aOrganizationalUnit); } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetSubjectName(nsAString& _subjectName) { _subjectName.Truncate(); UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } if (cert->subjectName) { LossyUTF8ToUTF16(cert->subjectName, strlen(cert->subjectName), _subjectName); } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetIssuerName(nsAString& _issuerName) { _issuerName.Truncate(); UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } if (cert->issuerName) { LossyUTF8ToUTF16(cert->issuerName, strlen(cert->issuerName), _issuerName); } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetSerialNumber(nsAString& _serialNumber) { _serialNumber.Truncate(); UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } UniquePORTString tmpstr( CERT_Hexify(&cert->serialNumber, true /* use colon delimiters */)); if (tmpstr) { _serialNumber = NS_ConvertASCIItoUTF16(tmpstr.get()); return NS_OK; } return NS_ERROR_FAILURE; } nsresult nsNSSCertificate::GetCertificateHash(nsAString& aFingerprint, SECOidTag aHashAlg) { aFingerprint.Truncate(); if (!EnsureNSSInitializedChromeOrContent()) { return NS_ERROR_NOT_AVAILABLE; } nsTArray digestArray; nsresult rv = Digest::DigestBuf(aHashAlg, mDER.Elements(), mDER.Length(), digestArray); if (NS_FAILED(rv)) { return rv; } SECItem digestItem = {siBuffer, digestArray.Elements(), static_cast(digestArray.Length())}; UniquePORTString fpStr( CERT_Hexify(&digestItem, true /* use colon delimiters */)); if (!fpStr) { return NS_ERROR_FAILURE; } aFingerprint.AssignASCII(fpStr.get()); return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetSha256Fingerprint(nsAString& aSha256Fingerprint) { return GetCertificateHash(aSha256Fingerprint, SEC_OID_SHA256); } NS_IMETHODIMP nsNSSCertificate::GetSha1Fingerprint(nsAString& _sha1Fingerprint) { return GetCertificateHash(_sha1Fingerprint, SEC_OID_SHA1); } NS_IMETHODIMP nsNSSCertificate::GetTokenName(nsAString& aTokenName) { UniqueCERTCertificate cert(GetOrInstantiateCert()); if (!cert) { return NS_ERROR_FAILURE; } UniquePK11SlotInfo internalSlot(PK11_GetInternalSlot()); if (!internalSlot) { return NS_ERROR_FAILURE; } nsCOMPtr token( new nsPK11Token(cert->slot ? cert->slot : internalSlot.get())); nsAutoCString tmp; nsresult rv = token->GetTokenName(tmp); if (NS_FAILED(rv)) { return rv; } aTokenName.Assign(NS_ConvertUTF8toUTF16(tmp)); return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetSha256SubjectPublicKeyInfoDigest( nsACString& aSha256SPKIDigest) { aSha256SPKIDigest.Truncate(); if (!EnsureNSSInitializedChromeOrContent()) { return NS_ERROR_NOT_AVAILABLE; } pkix::Input certInput; pkix::Result result = certInput.Init(mDER.Elements(), mDER.Length()); if (result != pkix::Result::Success) { return NS_ERROR_INVALID_ARG; } // NB: since we're not building a trust path, the endEntityOrCA parameter is // irrelevant. pkix::BackCert cert(certInput, pkix::EndEntityOrCA::MustBeEndEntity, nullptr); result = cert.Init(); if (result != pkix::Result::Success) { return NS_ERROR_INVALID_ARG; } pkix::Input derPublicKey = cert.GetSubjectPublicKeyInfo(); nsTArray digestArray; nsresult rv = Digest::DigestBuf(SEC_OID_SHA256, derPublicKey.UnsafeGetData(), derPublicKey.GetLength(), digestArray); if (NS_FAILED(rv)) { return rv; } rv = Base64Encode(nsDependentCSubstring( reinterpret_cast(digestArray.Elements()), digestArray.Length()), aSha256SPKIDigest); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetRawDER(nsTArray& aArray) { aArray.SetLength(mDER.Length()); memcpy(aArray.Elements(), mDER.Elements(), mDER.Length()); return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetBase64DERString(nsACString& base64DERString) { nsDependentCSubstring derString( reinterpret_cast(mDER.Elements()), mDER.Length()); nsresult rv = Base64Encode(derString, base64DERString); if (NS_FAILED(rv)) { return rv; } return NS_OK; } CERTCertificate* nsNSSCertificate::GetCert() { UniqueCERTCertificate cert(GetOrInstantiateCert()); return cert.release(); // caller takes ownership } NS_IMETHODIMP nsNSSCertificate::GetValidity(nsIX509CertValidity** aValidity) { NS_ENSURE_ARG(aValidity); pkix::Input certInput; pkix::Result rv = certInput.Init(mDER.Elements(), mDER.Length()); if (rv != pkix::Success) { return NS_ERROR_FAILURE; } nsCOMPtr validity = new X509CertValidity(certInput); validity.forget(aValidity); return NS_OK; } // NB: Any updates (except disk-only fields) must be kept in sync with // |SerializeToIPC|. NS_IMETHODIMP nsNSSCertificate::Write(nsIObjectOutputStream* aStream) { // This field used to be the cached EV status, but it is no longer necessary. nsresult rv = aStream->Write32(0); if (NS_FAILED(rv)) { return rv; } rv = aStream->Write32(mDER.Length()); if (NS_FAILED(rv)) { return rv; } return aStream->WriteBytes(Span(mDER)); } // NB: Any updates (except disk-only fields) must be kept in sync with // |DeserializeFromIPC|. NS_IMETHODIMP nsNSSCertificate::Read(nsIObjectInputStream* aStream) { auto lock = mCert.Lock(); auto& maybeCert = lock.ref(); if (!mDER.IsEmpty() || maybeCert.isSome()) { return NS_ERROR_ALREADY_INITIALIZED; } // This field is no longer used. uint32_t unusedCachedEVStatus; nsresult rv = aStream->Read32(&unusedCachedEVStatus); if (NS_FAILED(rv)) { return rv; } uint32_t len; rv = aStream->Read32(&len); if (NS_FAILED(rv)) { return rv; } rv = aStream->ReadByteArray(len, mDER); if (NS_FAILED(rv)) { return rv; } return NS_OK; } void nsNSSCertificate::SerializeToIPC(IPC::MessageWriter* aWriter) { bool hasCert = !mDER.IsEmpty(); WriteParam(aWriter, hasCert); if (!hasCert) { return; } WriteParam(aWriter, mDER); } bool nsNSSCertificate::DeserializeFromIPC(IPC::MessageReader* aReader) { auto lock = mCert.Lock(); auto& maybeCert = lock.ref(); if (!mDER.IsEmpty() || maybeCert.isSome()) { return false; } bool hasCert = false; if (!ReadParam(aReader, &hasCert)) { return false; } if (!hasCert) { return true; } if (!ReadParam(aReader, &mDER)) { return false; } return true; } NS_IMETHODIMP nsNSSCertificate::GetInterfaces(nsTArray& array) { array.Clear(); return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetScriptableHelper(nsIXPCScriptable** _retval) { *_retval = nullptr; return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetContractID(nsACString& aContractID) { aContractID.SetIsVoid(true); return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetClassDescription(nsACString& aClassDescription) { aClassDescription.SetIsVoid(true); return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetClassID(nsCID** aClassID) { *aClassID = (nsCID*)moz_xmalloc(sizeof(nsCID)); return GetClassIDNoAlloc(*aClassID); } NS_IMETHODIMP nsNSSCertificate::GetFlags(uint32_t* aFlags) { *aFlags = nsIClassInfo::THREADSAFE; return NS_OK; } NS_IMETHODIMP nsNSSCertificate::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { static NS_DEFINE_CID(kNSSCertificateCID, NS_X509CERT_CID); *aClassIDNoAlloc = kNSSCertificateCID; return NS_OK; }