author | Daniel Veditz <dveditz@cruzio.com> |
Tue, 05 May 2015 20:21:00 +0200 | |
changeset 242947 | e8c071496de1185a4b283f5615ead356ac8b8bb2 |
parent 242946 | 056ceb0ad5eabb4c40d7b8c7d4adebde7982f316 |
child 242948 | 128faa8631644d21b70149bd14e5a1a5af6da08f |
push id | 28714 |
push user | kwierso@gmail.com |
push date | Fri, 08 May 2015 17:29:48 +0000 |
treeherder | mozilla-central@5e8adf0e7f2c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | keeler |
bugs | 1038072 |
milestone | 40.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/security/apps/AppSignatureVerification.cpp +++ b/security/apps/AppSignatureVerification.cpp @@ -15,16 +15,17 @@ #include "nsComponentManagerUtils.h" #include "nsCOMPtr.h" #include "nsDataSignatureVerifier.h" #include "nsHashKeys.h" #include "nsIFile.h" #include "nsIFileStreams.h" #include "nsIInputStream.h" #include "nsIStringEnumerator.h" +#include "nsIDirectoryEnumerator.h" #include "nsIZipReader.h" #include "nsNetUtil.h" #include "nsNSSCertificate.h" #include "nsProxyRelease.h" #include "nssb64.h" #include "NSSCertDBTrustDomain.h" #include "nsString.h" #include "nsTHashtable.h" @@ -93,17 +94,17 @@ ReadStream(const nsCOMPtr<nsIInputStream return NS_ERROR_FILE_CORRUPTED; } buf.data[buf.len - 1] = 0; // null-terminate return NS_OK; } -// Finds exactly one (signature metadata) entry that matches the given +// Finds exactly one (signature metadata) JAR entry that matches the given // search pattern, and then load it. Fails if there are no matches or if // there is more than one match. If bugDigest is not null then on success // bufDigest will contain the SHA-1 digeset of the entry. nsresult FindAndLoadOneEntry(nsIZipReader * zip, const nsACString & searchPattern, /*out*/ nsACString & filename, /*out*/ SECItem & buf, @@ -148,37 +149,32 @@ FindAndLoadOneEntry(nsIZipReader * zip, return NS_OK; } // Verify the digest of an entry. We avoid loading the entire entry into memory // at once, which would require memory in proportion to the size of the largest // entry. Instead, we require only a small, fixed amount of memory. // +// @param stream an input stream from a JAR entry or file depending on whether +// it is from a signed archive or unpacked into a directory // @param digestFromManifest The digest that we're supposed to check the file's // contents against, from the manifest // @param buf A scratch buffer that we use for doing the I/O, which must have // already been allocated. The size of this buffer is the unit // size of our I/O. nsresult -VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename, - const SECItem & digestFromManifest, SECItem & buf) +VerifyStreamContentDigest(nsIInputStream* stream, + const SECItem& digestFromManifest, SECItem& buf) { MOZ_ASSERT(buf.len > 0); if (digestFromManifest.len != SHA1_LENGTH) return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; nsresult rv; - - nsCOMPtr<nsIInputStream> stream; - rv = zip->GetInputStream(aFilename, getter_AddRefs(stream)); - if (NS_FAILED(rv)) { - return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; - } - uint64_t len64; rv = stream->Available(&len64); NS_ENSURE_SUCCESS(rv, rv); if (len64 > UINT32_MAX) { return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE; } ScopedPK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1)); @@ -221,16 +217,91 @@ VerifyEntryContentDigest(nsIZipReader * if (SECITEM_CompareItem(&digestFromManifest, &digest.get()) != SECEqual) { return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY; } return NS_OK; } +nsresult +VerifyEntryContentDigest(nsIZipReader* zip, const nsACString& aFilename, + const SECItem& digestFromManifest, SECItem& buf) +{ + nsCOMPtr<nsIInputStream> stream; + nsresult rv = zip->GetInputStream(aFilename, getter_AddRefs(stream)); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; + } + + return VerifyStreamContentDigest(stream, digestFromManifest, buf); +} + +// @oaram aDir directory containing the unpacked signed archive +// @param aFilename path of the target file relative to aDir +// @param digestFromManifest The digest that we're supposed to check the file's +// contents against, from the manifest +// @param buf A scratch buffer that we use for doing the I/O +nsresult +VerifyFileContentDigest(nsIFile* aDir, const nsAString& aFilename, + const SECItem& digestFromManifest, SECItem& buf) +{ + // Find the file corresponding to the manifest path + nsCOMPtr<nsIFile> file; + nsresult rv = aDir->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + // We don't know how to handle JARs with signed directory entries. + // It's technically possible in the manifest but makes no sense on disk. + // Inside an archive we just ignore them, but here we have to treat it + // as an error because the signed bytes never got unpacked. + int32_t pos = 0; + int32_t slash; + int32_t namelen = aFilename.Length(); + if (namelen == 0 || aFilename[namelen - 1] == '/') { + return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; + } + + // Append path segments one by one + do { + slash = aFilename.FindChar('/', pos); + int32_t segend = (slash == kNotFound) ? namelen : slash; + rv = file->Append(Substring(aFilename, pos, (segend - pos))); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; + } + pos = slash + 1; + } while (pos < namelen && slash != kNotFound); + + bool exists; + rv = file->Exists(&exists); + if (NS_FAILED(rv) || !exists) { + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; + } + + bool isDir; + rv = file->IsDirectory(&isDir); + if (NS_FAILED(rv) || isDir) { + // We only support signed files, not directory entries + return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; + } + + // Open an input stream for that file and verify it. + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1, + nsIFileInputStream::CLOSE_ON_EOF); + if (NS_FAILED(rv) || !stream) { + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; + } + + return VerifyStreamContentDigest(stream, digestFromManifest, buf); +} + // On input, nextLineStart is the start of the current line. On output, // nextLineStart is the start of the next line. nsresult ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line, bool allowContinuations = true) { line.Truncate(); size_t previousLength = 0; @@ -281,16 +352,17 @@ ReadLine(/*in/out*/ const char* & nextLi ++nextLineStart; // skip space and keep appending } } // The header strings are defined in the JAR specification. #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$" #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$" #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$" +#define JAR_META_DIR "META-INF" #define JAR_MF_HEADER "Manifest-Version: 1.0" #define JAR_SF_HEADER "Signature-Version: 1.0" nsresult ParseAttribute(const nsAutoCString & curLine, /*out*/ nsAutoCString & attrName, /*out*/ nsAutoCString & attrValue) { @@ -944,8 +1016,502 @@ nsNSSCertificateDB::VerifySignedManifest NS_ENSURE_ARG_POINTER(aSignatureStream); NS_ENSURE_ARG_POINTER(aCallback); RefPtr<VerifySignedmanifestTask> task( new VerifySignedmanifestTask(aTrustedRoot, aManifestStream, aSignatureStream, aCallback)); return task->Dispatch("SignedManifest"); } + + +// +// Signature verification for archives unpacked into a file structure +// + +// Finds the "*.rsa" signature file in the META-INF directory and returns +// the name. It is an error if there are none or more than one .rsa file +nsresult +FindSignatureFilename(nsIFile* aMetaDir, + /*out*/ nsAString& aFilename) +{ + nsCOMPtr<nsISimpleEnumerator> entries; + nsresult rv = aMetaDir->GetDirectoryEntries(getter_AddRefs(entries)); + nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(entries); + if (NS_FAILED(rv) || !files) { + return NS_ERROR_SIGNED_JAR_NOT_SIGNED; + } + + bool found = false; + nsCOMPtr<nsIFile> file; + rv = files->GetNextFile(getter_AddRefs(file)); + + while (NS_SUCCEEDED(rv) && file) { + nsAutoString leafname; + rv = file->GetLeafName(leafname); + if (NS_SUCCEEDED(rv)) { + if (StringEndsWith(leafname, NS_LITERAL_STRING(".rsa"))) { + if (!found) { + found = true; + aFilename = leafname; + } else { + // second signature file is an error + rv = NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + break; + } + } + rv = files->GetNextFile(getter_AddRefs(file)); + } + } + + if (!found) { + rv = NS_ERROR_SIGNED_JAR_NOT_SIGNED; + } + + files->Close(); + return rv; +} + +// Loads the signature metadata file that matches the given filename in +// the passed-in Meta-inf directory. If bufDigest is not null then on +// success bufDigest will contain the SHA-1 digest of the entry. +nsresult +LoadOneMetafile(nsIFile* aMetaDir, + const nsAString& aFilename, + /*out*/ SECItem& aBuf, + /*optional, out*/ Digest* aBufDigest) +{ + nsCOMPtr<nsIFile> metafile; + nsresult rv = aMetaDir->Clone(getter_AddRefs(metafile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = metafile->Append(aFilename); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = metafile->Exists(&exists); + if (NS_FAILED(rv) || !exists) { + // we can call a missing .rsa file "unsigned" but FindSignatureFilename() + // already found one: missing other metadata files means a broken signature. + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metafile); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadStream(stream, aBuf); + stream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + if (aBufDigest) { + rv = aBufDigest->DigestBuf(SEC_OID_SHA1, aBuf.data, aBuf.len - 1); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// Parses MANIFEST.MF and verifies the contents of the unpacked files +// listed in the manifest. +// The filenames of all entries will be returned in aMfItems. aBuf must +// be a pre-allocated scratch buffer that is used for doing I/O. +nsresult +ParseMFUnpacked(const char* aFilebuf, nsIFile* aDir, + /*out*/ nsTHashtable<nsStringHashKey>& aMfItems, + ScopedAutoSECItem& aBuf) +{ + nsresult rv; + + const char* nextLineStart = aFilebuf; + + rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER)); + if (NS_FAILED(rv)) { + return rv; + } + + // Skip the rest of the header section, which ends with a blank line. + { + nsAutoCString line; + do { + rv = ReadLine(nextLineStart, line); + if (NS_FAILED(rv)) { + return rv; + } + } while (line.Length() > 0); + + // Manifest containing no file entries is OK, though useless. + if (*nextLineStart == '\0') { + return NS_OK; + } + } + + nsAutoString curItemName; + ScopedAutoSECItem digest; + + for (;;) { + nsAutoCString curLine; + rv = ReadLine(nextLineStart, curLine); + if (NS_FAILED(rv)) { + return rv; + } + + if (curLine.Length() == 0) { + // end of section (blank line or end-of-file) + + if (curItemName.Length() == 0) { + // '...Each section must start with an attribute with the name as + // "Name",...', so every section must have a Name attribute. + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + if (digest.len == 0) { + // We require every entry to have a digest, since we require every + // entry to be signed and we don't allow duplicate entries. + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + if (aMfItems.Contains(curItemName)) { + // Duplicate entry + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + // Verify that the file's content digest matches the digest from this + // MF section. + rv = VerifyFileContentDigest(aDir, curItemName, digest, aBuf); + if (NS_FAILED(rv)) { + return rv; + } + + aMfItems.PutEntry(curItemName); + + if (*nextLineStart == '\0') { + // end-of-file + break; + } + + // reset so we know we haven't encountered either of these for the next + // item yet. + curItemName.Truncate(); + digest.reset(); + + continue; // skip the rest of the loop below + } + + nsAutoCString attrName; + nsAutoCString attrValue; + rv = ParseAttribute(curLine, attrName, attrValue); + if (NS_FAILED(rv)) { + return rv; + } + + // Lines to look for: + + // (1) Digest: + if (attrName.LowerCaseEqualsLiteral("sha1-digest")) { + if (digest.len > 0) { + // multiple SHA1 digests in section + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get())); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + continue; + } + + // (2) Name: associates this manifest section with a file in the jar. + if (attrName.LowerCaseEqualsLiteral("name")) { + if (MOZ_UNLIKELY(curItemName.Length() > 0)) { + // multiple names in section + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + if (MOZ_UNLIKELY(attrValue.Length() == 0)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + curItemName = NS_ConvertUTF8toUTF16(attrValue); + + continue; + } + + // (3) Magic: the only other must-understand attribute + if (attrName.LowerCaseEqualsLiteral("magic")) { + // We don't understand any magic, so we can't verify an entry that + // requires magic. Since we require every entry to have a valid + // signature, we have no choice but to reject the entry. + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + // unrecognized attributes must be ignored + } + + return NS_OK; +} + +// recursively check a directory tree for files not in the list of +// verified files we found in the manifest. For each file we find +// Check it against the files found in the manifest. If the file wasn't +// in the manifest then it's unsigned and we can stop looking. Otherwise +// remove it from the collection so we can check leftovers later. +// +// @param aDir Directory to check +// @param aPath Relative path to that directory (to check against aItems) +// @param aItems All the files found +// @param *Filename signature files that won't be in the manifest +nsresult +CheckDirForUnsignedFiles(nsIFile* aDir, + const nsString& aPath, + /* in/out */ nsTHashtable<nsStringHashKey>& aItems, + const nsAString& sigFilename, + const nsAString& sfFilename, + const nsAString& mfFilename) +{ + nsCOMPtr<nsISimpleEnumerator> entries; + nsresult rv = aDir->GetDirectoryEntries(getter_AddRefs(entries)); + nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(entries); + if (NS_FAILED(rv) || !files) { + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; + } + + bool inMeta = StringBeginsWith(aPath, NS_LITERAL_STRING(JAR_META_DIR)); + + while (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> file; + rv = files->GetNextFile(getter_AddRefs(file)); + if (NS_FAILED(rv) || !file) { + break; + } + + nsAutoString leafname; + rv = file->GetLeafName(leafname); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString curName(aPath + leafname); + + bool isDir; + rv = file->IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + + // if it's a directory we need to recurse + if (isDir) { + curName.Append(NS_LITERAL_STRING("/")); + rv = CheckDirForUnsignedFiles(file, curName, aItems, + sigFilename, sfFilename, mfFilename); + } else { + // The files that comprise the signature mechanism are not covered by the + // signature. + // + // XXX: This is OK for a single signature, but doesn't work for + // multiple signatures because the metadata for the other signatures + // is not signed either. + if (inMeta && ( leafname == sigFilename || + leafname == sfFilename || + leafname == mfFilename )) { + continue; + } + + // make sure the current file was found in the manifest + nsStringHashKey* item = aItems.GetEntry(curName); + if (!item) { + return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY; + } + + // Remove the item so we can check for leftover items later + aItems.RemoveEntry(curName); + } + } + files->Close(); + return rv; +} + +/* + * Verify the signature of a directory structure as if it were a + * signed JAR file (used for unpacked JARs) + */ +nsresult +VerifySignedDirectory(AppTrustedRoot aTrustedRoot, + nsIFile* aDirectory, + /*out, optional */ nsIX509Cert** aSignerCert) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + + if (aSignerCert) { + *aSignerCert = nullptr; + } + + // Make sure there's a META-INF directory + + nsCOMPtr<nsIFile> metaDir; + nsresult rv = aDirectory->Clone(getter_AddRefs(metaDir)); + if (NS_FAILED(rv)) { + return rv; + } + rv = metaDir->Append(NS_LITERAL_STRING(JAR_META_DIR)); + if (NS_FAILED(rv)) { + return rv; + } + + bool exists; + rv = metaDir->Exists(&exists); + if (NS_FAILED(rv) || !exists) { + return NS_ERROR_SIGNED_JAR_NOT_SIGNED; + } + bool isDirectory; + rv = metaDir->IsDirectory(&isDirectory); + if (NS_FAILED(rv) || !isDirectory) { + return NS_ERROR_SIGNED_JAR_NOT_SIGNED; + } + + // Find and load the Signature (RSA) file + + nsAutoString sigFilename; + rv = FindSignatureFilename(metaDir, sigFilename); + if (NS_FAILED(rv)) { + return rv; + } + + ScopedAutoSECItem sigBuffer; + rv = LoadOneMetafile(metaDir, sigFilename, sigBuffer, nullptr); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_NOT_SIGNED; + } + + // Load the signature (SF) file and verify the signature. + // The .sf and .rsa files must have the same name apart from the extension. + + nsAutoString sfFilename(Substring(sigFilename, 0, sigFilename.Length() - 3) + + NS_LITERAL_STRING("sf")); + + ScopedAutoSECItem sfBuffer; + Digest sfCalculatedDigest; + rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, &sfCalculatedDigest); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + sigBuffer.type = siBuffer; + ScopedCERTCertList builtChain; + rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(), + builtChain); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + // Get the expected manifest hash from the signed .sf file + + ScopedAutoSECItem mfDigest; + rv = ParseSF(char_ptr_cast(sfBuffer.data), mfDigest); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + // Load manifest (MF) file and verify signature + + nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf")); + ScopedAutoSECItem manifestBuffer; + Digest mfCalculatedDigest; + rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, &mfCalculatedDigest); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + // Parse manifest and verify signed hash of all listed files + + // Allocate the I/O buffer only once per JAR, instead of once per entry, in + // order to minimize malloc/free calls and in order to avoid fragmenting + // memory. + ScopedAutoSECItem buf(128 * 1024); + + nsTHashtable<nsStringHashKey> items; + rv = ParseMFUnpacked(char_ptr_cast(manifestBuffer.data), + aDirectory, items, buf); + if (NS_FAILED(rv)){ + return rv; + } + + // We've checked that everything listed in the manifest exists and is signed + // correctly. Now check on disk for extra (unsigned) files. + // Deletes found entries from items as it goes. + rv = CheckDirForUnsignedFiles(aDirectory, EmptyString(), items, + sigFilename, sfFilename, mfFilename); + if (NS_FAILED(rv)) { + return rv; + } + + // We verified that every entry that we require to be signed is signed. But, + // were there any missing entries--that is, entries that are mentioned in the + // manifest but missing from the directory tree? (There shouldn't be given + // ParseMFUnpacked() checking them all, but it's a cheap sanity check.) + if (items.Count() != 0) { + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; + } + + // Return the signer's certificate to the reader if they want it. + // XXX: We should return an nsIX509CertList with the whole validated chain. + if (aSignerCert) { + MOZ_ASSERT(CERT_LIST_HEAD(builtChain)); + nsCOMPtr<nsIX509Cert> signerCert = + nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain)->cert); + NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY); + signerCert.forget(aSignerCert); + } + + return NS_OK; +} + +class VerifySignedDirectoryTask final : public CryptoTask +{ +public: + VerifySignedDirectoryTask(AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar, + nsIVerifySignedDirectoryCallback* aCallback) + : mTrustedRoot(aTrustedRoot) + , mDirectory(aUnpackedJar) + , mCallback(new nsMainThreadPtrHolder<nsIVerifySignedDirectoryCallback>(aCallback)) + { + } + +private: + virtual nsresult CalculateResult() override + { + return VerifySignedDirectory(mTrustedRoot, + mDirectory, + getter_AddRefs(mSignerCert)); + } + + // This class doesn't directly hold NSS resources so there's nothing that + // needs to be released + virtual void ReleaseNSSResources() override { } + + virtual void CallCallback(nsresult rv) override + { + (void) mCallback->VerifySignedDirectoryFinished(rv, mSignerCert); + } + + const AppTrustedRoot mTrustedRoot; + const nsCOMPtr<nsIFile> mDirectory; + nsMainThreadPtrHandle<nsIVerifySignedDirectoryCallback> mCallback; + nsCOMPtr<nsIX509Cert> mSignerCert; // out +}; + +NS_IMETHODIMP +nsNSSCertificateDB::VerifySignedDirectoryAsync( + AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar, + nsIVerifySignedDirectoryCallback* aCallback) +{ + NS_ENSURE_ARG_POINTER(aUnpackedJar); + NS_ENSURE_ARG_POINTER(aCallback); + RefPtr<VerifySignedDirectoryTask> task(new VerifySignedDirectoryTask(aTrustedRoot, + aUnpackedJar, + aCallback)); + return task->Dispatch("UnpackedJar"); +}
--- a/security/manager/ssl/public/nsIX509CertDB.idl +++ b/security/manager/ssl/public/nsIX509CertDB.idl @@ -23,28 +23,35 @@ typedef uint32_t AppTrustedRoot; [scriptable, function, uuid(fc2b60e5-9a07-47c2-a2cd-b83b68a660ac)] interface nsIOpenSignedAppFileCallback : nsISupports { void openSignedAppFileFinished(in nsresult rv, in nsIZipReader aZipReader, in nsIX509Cert aSignerCert); }; +[scriptable, function, uuid(d5f97827-622a-488f-be08-d850432ac8ec)] +interface nsIVerifySignedDirectoryCallback : nsISupports +{ + void verifySignedDirectoryFinished(in nsresult rv, + in nsIX509Cert aSignerCert); +}; + [scriptable, function, uuid(3d6a9c87-5c5f-46fc-9410-96da6092f0f2)] interface nsIVerifySignedManifestCallback : nsISupports { void verifySignedManifestFinished(in nsresult rv, in nsIX509Cert aSignerCert); }; /** * This represents a service to access and manipulate * X.509 certificates stored in a database. */ -[scriptable, uuid(560bc9ac-3e71-472e-9b08-2270d0c71878)] +[scriptable, uuid(6d27211b-7119-4777-9c62-f29310eeb262)] interface nsIX509CertDB : nsISupports { /** * Constants that define which usages a certificate * is trusted for. */ const unsigned long UNTRUSTED = 0; const unsigned long TRUSTED_SSL = 1 << 0; @@ -313,16 +320,32 @@ interface nsIX509CertDB : nsISupports { const AppTrustedRoot TrustedHostedAppTestRoot = 8; const AppTrustedRoot AddonsPublicRoot = 9; const AppTrustedRoot AddonsStageRoot = 10; void openSignedAppFileAsync(in AppTrustedRoot trustedRoot, in nsIFile aJarFile, in nsIOpenSignedAppFileCallback callback); /** + * Verifies the signature on a directory representing an unpacked signed + * JAR file. To be considered valid, there must be exactly one signature + * on the directory structure and that signature must have signed every + * entry. Further, the signature must come from a certificate that + * is trusted for code signing. + * + * On success NS_OK and the trusted certificate that signed the + * unpacked JAR are returned. + * + * On failure, an error code is returned. + */ + void verifySignedDirectoryAsync(in AppTrustedRoot trustedRoot, + in nsIFile aUnpackedDir, + in nsIVerifySignedDirectoryCallback callback); + + /** * Given streams containing a signature and a manifest file, verifies * that the signature is valid for the manifest. The signature must * come from a certificate that is trusted for code signing and that * was issued by the given trusted root. * * On success, NS_OK and the trusted certificate that signed the * Manifest are returned. *
new file mode 100644 index 0000000000000000000000000000000000000000..c212c70eebb9e42088dd22e556964489667698f0 GIT binary patch literal 6074 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
new file mode 100644 --- /dev/null +++ b/security/manager/ssl/tests/unit/test_signed_dir.js @@ -0,0 +1,173 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/ZipUtils.jsm"); + +do_get_profile(); // must be called before getting nsIX509CertDB +const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB); + +var gSignedXPI = do_get_file("test_signed_apps/sslcontrol.xpi", false); +var gTarget = FileUtils.getDir("TmpD", ["test_signed_dir"]); +gTarget.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + +// tamper data structure, each element is optional +// { copy: [[path,newname], [path2,newname2], ...], +// delete: [path, path2, ...], +// corrupt: [path, path2, ...] +// } + +function prepare(tamper) { + ZipUtils.extractFiles(gSignedXPI, gTarget); + + // copy files + if (tamper.copy) { + tamper.copy.forEach(i => { + let f = gTarget.clone(); + i[0].split('/').forEach(seg => {f.append(seg);}); + f.copyTo(null, i[1]); + }); + } + + // delete files + if (tamper.delete) { + tamper.delete.forEach(i => { + let f = gTarget.clone(); + i.split('/').forEach(seg => {f.append(seg);}); + f.remove(true); + }); + } + + // corrupt files + if (tamper.corrupt) { + tamper.corrupt.forEach(i => { + let f = gTarget.clone(); + i.split('/').forEach(seg => {f.append(seg);}); + let s = FileUtils.openFileOutputStream(f, FileUtils.MODE_WRONLY); + const str = "Kilroy was here"; + s.write(str, str.length); + s.close(); + }); + } + + return gTarget; +} + + +function check_result(name, expectedRv, dir) { + return function verifySignedDirCallback(rv, aSignerCert) { + equal(rv, expectedRv, name + " rv:"); + equal(aSignerCert != null, Components.isSuccessCode(expectedRv), + "expecting certificate:"); + // cleanup and kick off next test + dir.remove(true); + run_next_test(); + }; +} + +function verifyDirAsync(name, expectedRv, tamper) { + let targetDir = prepare(tamper); + certdb.verifySignedDirectoryAsync( + Ci.nsIX509CertDB.AddonsPublicRoot, targetDir, + check_result(name, expectedRv, targetDir)); +} + + +// +// the tests +// + +add_test(function() { + verifyDirAsync("'valid'", Cr.NS_OK, {} /* no tampering */ ); +}); + +add_test(function() { + verifyDirAsync("'no meta dir'", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED, + {delete: ["META-INF"]}); +}); + +add_test(function() { + verifyDirAsync("'empty meta dir'", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED, + {delete: ["META-INF/mozilla.rsa", + "META-INF/mozilla.sf", + "META-INF/manifest.mf"]}); +}); + +add_test(function() { + verifyDirAsync("'two rsa files'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {copy: [["META-INF/mozilla.rsa","extra.rsa"]]}); +}); + +add_test(function() { + verifyDirAsync("'corrupt rsa file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {corrupt: ["META-INF/mozilla.rsa"]}); +}); + +add_test(function() { + verifyDirAsync("'missing sf file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {delete: ["META-INF/mozilla.sf"]}); +}); + +add_test(function() { + verifyDirAsync("'corrupt sf file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {corrupt: ["META-INF/mozilla.sf"]}); +}); + +add_test(function() { + verifyDirAsync("'extra .sf file (invalid)'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY, + {copy: [["META-INF/mozilla.rsa","extra.sf"]]}); +}); + +add_test(function() { + verifyDirAsync("'extra .sf file (valid)'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY, + {copy: [["META-INF/mozilla.sf","extra.sf"]]}); +}); + +add_test(function() { + verifyDirAsync("'missing manifest'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {delete: ["META-INF/manifest.mf"]}); +}); + +add_test(function() { + verifyDirAsync("'corrupt manifest'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {corrupt: ["META-INF/manifest.mf"]}); +}); + +add_test(function() { + verifyDirAsync("'missing file'", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING, + {delete: ["bootstrap.js"]}); +}); + +add_test(function() { + verifyDirAsync("'corrupt file'", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY, + {corrupt: ["bootstrap.js"]}); +}); + +add_test(function() { + verifyDirAsync("'extra file'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY, + {copy: [["bootstrap.js","extra"]]}); +}); + +add_test(function() { + verifyDirAsync("'missing file in dir'", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING, + {delete: ["content/options.xul"]}); +}); + +add_test(function() { + verifyDirAsync("'corrupt file in dir'", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY, + {corrupt: ["content/options.xul"]}); +}); + +add_test(function() { + verifyDirAsync("'extra file in dir'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY, + {copy: [["content/options.xul","extra"]]}); +}); + +do_register_cleanup(function() { + if (gTarget.exists()) { + gTarget.remove(true); + } +}); + +function run_test() { + run_next_test(); +} \ No newline at end of file
--- a/security/manager/ssl/tests/unit/xpcshell.ini +++ b/security/manager/ssl/tests/unit/xpcshell.ini @@ -65,16 +65,17 @@ run-sequentially = hardcoded ports [test_cert_overrides.js] run-sequentially = hardcoded ports [test_intermediate_basic_usage_constraints.js] [test_name_constraints.js] [test_cert_trust.js] [test_cert_version.js] [test_signed_apps.js] [test_signed_apps-marketplace.js] +[test_signed_dir.js] [test_cert_eku-CA_EP.js] [test_cert_eku-CA_EP_NS_OS_SA_TS.js] [test_cert_eku-CA.js] [test_cert_eku-CA_NS.js] [test_cert_eku-CA_OS.js] [test_cert_eku-CA_SA.js] [test_cert_eku-CA_TS.js]