/src/mozilla-central/security/apps/AppSignatureVerification.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "nsNSSCertificateDB.h" |
8 | | |
9 | | #include "AppTrustDomain.h" |
10 | | #include "CryptoTask.h" |
11 | | #include "NSSCertDBTrustDomain.h" |
12 | | #include "ScopedNSSTypes.h" |
13 | | #include "SharedCertVerifier.h" |
14 | | #include "certdb.h" |
15 | | #include "cms.h" |
16 | | #include "cosec.h" |
17 | | #include "mozilla/Base64.h" |
18 | | #include "mozilla/Casting.h" |
19 | | #include "mozilla/Logging.h" |
20 | | #include "mozilla/Preferences.h" |
21 | | #include "mozilla/RefPtr.h" |
22 | | #include "mozilla/UniquePtr.h" |
23 | | #include "mozilla/Unused.h" |
24 | | #include "nsCOMPtr.h" |
25 | | #include "nsComponentManagerUtils.h" |
26 | | #include "nsDependentString.h" |
27 | | #include "nsHashKeys.h" |
28 | | #include "nsIDirectoryEnumerator.h" |
29 | | #include "nsIFile.h" |
30 | | #include "nsIFileStreams.h" |
31 | | #include "nsIInputStream.h" |
32 | | #include "nsIStringEnumerator.h" |
33 | | #include "nsIZipReader.h" |
34 | | #include "nsNSSCertificate.h" |
35 | | #include "nsNetUtil.h" |
36 | | #include "nsProxyRelease.h" |
37 | | #include "nsString.h" |
38 | | #include "nsTHashtable.h" |
39 | | #include "pkix/pkix.h" |
40 | | #include "pkix/pkixnss.h" |
41 | | #include "plstr.h" |
42 | | #include "secmime.h" |
43 | | |
44 | | |
45 | | using namespace mozilla::pkix; |
46 | | using namespace mozilla; |
47 | | using namespace mozilla::psm; |
48 | | |
49 | | extern mozilla::LazyLogModule gPIPNSSLog; |
50 | | |
51 | | namespace { |
52 | | |
53 | | // A convenient way to pair the bytes of a digest with the algorithm that |
54 | | // purportedly produced those bytes. Only SHA-1 and SHA-256 are supported. |
55 | | struct DigestWithAlgorithm |
56 | | { |
57 | | nsresult ValidateLength() const |
58 | 0 | { |
59 | 0 | size_t hashLen; |
60 | 0 | switch (mAlgorithm) { |
61 | 0 | case SEC_OID_SHA256: |
62 | 0 | hashLen = SHA256_LENGTH; |
63 | 0 | break; |
64 | 0 | case SEC_OID_SHA1: |
65 | 0 | hashLen = SHA1_LENGTH; |
66 | 0 | break; |
67 | 0 | default: |
68 | 0 | MOZ_ASSERT_UNREACHABLE( |
69 | 0 | "unsupported hash type in DigestWithAlgorithm::ValidateLength"); |
70 | 0 | return NS_ERROR_FAILURE; |
71 | 0 | } |
72 | 0 | if (mDigest.Length() != hashLen) { |
73 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
74 | 0 | } |
75 | 0 | return NS_OK; |
76 | 0 | } |
77 | | |
78 | | nsAutoCString mDigest; |
79 | | SECOidTag mAlgorithm; |
80 | | }; |
81 | | |
82 | | // The digest must have a lifetime greater than or equal to the returned string. |
83 | | inline nsDependentCSubstring |
84 | | DigestToDependentString(const Digest& digest) |
85 | 0 | { |
86 | 0 | return nsDependentCSubstring( |
87 | 0 | BitwiseCast<char*, unsigned char*>(digest.get().data), |
88 | 0 | digest.get().len); |
89 | 0 | } |
90 | | |
91 | | // Reads a maximum of 1MB from a stream into the supplied buffer. |
92 | | // The reason for the 1MB limit is because this function is used to read |
93 | | // signature-related files and we want to avoid OOM. The uncompressed length of |
94 | | // an entry can be hundreds of times larger than the compressed version, |
95 | | // especially if someone has specifically crafted the entry to cause OOM or to |
96 | | // consume massive amounts of disk space. |
97 | | // |
98 | | // @param stream The input stream to read from. |
99 | | // @param buf The buffer that we read the stream into, which must have |
100 | | // already been allocated. |
101 | | nsresult |
102 | | ReadStream(const nsCOMPtr<nsIInputStream>& stream, /*out*/ SECItem& buf) |
103 | 0 | { |
104 | 0 | // The size returned by Available() might be inaccurate so we need |
105 | 0 | // to check that Available() matches up with the actual length of |
106 | 0 | // the file. |
107 | 0 | uint64_t length; |
108 | 0 | nsresult rv = stream->Available(&length); |
109 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
110 | 0 | return rv; |
111 | 0 | } |
112 | 0 | |
113 | 0 | // Cap the maximum accepted size of signature-related files at 1MB (which is |
114 | 0 | // still crazily huge) to avoid OOM. The uncompressed length of an entry can be |
115 | 0 | // hundreds of times larger than the compressed version, especially if |
116 | 0 | // someone has speifically crafted the entry to cause OOM or to consume |
117 | 0 | // massive amounts of disk space. |
118 | 0 | static const uint32_t MAX_LENGTH = 1024 * 1024; |
119 | 0 | if (length > MAX_LENGTH) { |
120 | 0 | return NS_ERROR_FILE_TOO_BIG; |
121 | 0 | } |
122 | 0 | |
123 | 0 | // With bug 164695 in mind we +1 to leave room for null-terminating |
124 | 0 | // the buffer. |
125 | 0 | SECITEM_AllocItem(buf, static_cast<uint32_t>(length + 1)); |
126 | 0 |
|
127 | 0 | // buf.len == length + 1. We attempt to read length + 1 bytes |
128 | 0 | // instead of length, so that we can check whether the metadata for |
129 | 0 | // the entry is incorrect. |
130 | 0 | uint32_t bytesRead; |
131 | 0 | rv = stream->Read(BitwiseCast<char*, unsigned char*>(buf.data), buf.len, |
132 | 0 | &bytesRead); |
133 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
134 | 0 | return rv; |
135 | 0 | } |
136 | 0 | if (bytesRead != length) { |
137 | 0 | return NS_ERROR_FILE_CORRUPTED; |
138 | 0 | } |
139 | 0 | |
140 | 0 | buf.data[buf.len - 1] = 0; // null-terminate |
141 | 0 |
|
142 | 0 | return NS_OK; |
143 | 0 | } |
144 | | |
145 | | // Finds exactly one (signature metadata) JAR entry that matches the given |
146 | | // search pattern, and then loads it. Fails if there are no matches or if |
147 | | // there is more than one match. If bufDigest is not null then on success |
148 | | // bufDigest will contain the digeset of the entry using the given digest |
149 | | // algorithm. |
150 | | nsresult |
151 | | FindAndLoadOneEntry(nsIZipReader* zip, |
152 | | const nsACString& searchPattern, |
153 | | /*out*/ nsACString& filename, |
154 | | /*out*/ SECItem& buf, |
155 | | /*optional, in*/ SECOidTag digestAlgorithm = SEC_OID_SHA1, |
156 | | /*optional, out*/ Digest* bufDigest = nullptr) |
157 | 0 | { |
158 | 0 | nsCOMPtr<nsIUTF8StringEnumerator> files; |
159 | 0 | nsresult rv = zip->FindEntries(searchPattern, getter_AddRefs(files)); |
160 | 0 | if (NS_FAILED(rv) || !files) { |
161 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
162 | 0 | } |
163 | 0 | |
164 | 0 | bool more; |
165 | 0 | rv = files->HasMore(&more); |
166 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
167 | 0 | if (!more) { |
168 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
169 | 0 | } |
170 | 0 | |
171 | 0 | rv = files->GetNext(filename); |
172 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
173 | 0 |
|
174 | 0 | // Check if there is more than one match, if so then error! |
175 | 0 | rv = files->HasMore(&more); |
176 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
177 | 0 | if (more) { |
178 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
179 | 0 | } |
180 | 0 | |
181 | 0 | nsCOMPtr<nsIInputStream> stream; |
182 | 0 | rv = zip->GetInputStream(filename, getter_AddRefs(stream)); |
183 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
184 | 0 |
|
185 | 0 | rv = ReadStream(stream, buf); |
186 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
187 | 0 | return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; |
188 | 0 | } |
189 | 0 | |
190 | 0 | if (bufDigest) { |
191 | 0 | rv = bufDigest->DigestBuf(digestAlgorithm, buf.data, buf.len - 1); |
192 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
193 | 0 | } |
194 | 0 |
|
195 | 0 | return NS_OK; |
196 | 0 | } |
197 | | |
198 | | // Verify the digest of an entry. We avoid loading the entire entry into memory |
199 | | // at once, which would require memory in proportion to the size of the largest |
200 | | // entry. Instead, we require only a small, fixed amount of memory. |
201 | | // |
202 | | // @param stream an input stream from a JAR entry or file depending on whether |
203 | | // it is from a signed archive or unpacked into a directory |
204 | | // @param digestFromManifest The digest that we're supposed to check the file's |
205 | | // contents against, from the manifest |
206 | | // @param buf A scratch buffer that we use for doing the I/O, which must have |
207 | | // already been allocated. The size of this buffer is the unit |
208 | | // size of our I/O. |
209 | | nsresult |
210 | | VerifyStreamContentDigest(nsIInputStream* stream, |
211 | | const DigestWithAlgorithm& digestFromManifest, |
212 | | SECItem& buf) |
213 | 0 | { |
214 | 0 | MOZ_ASSERT(buf.len > 0); |
215 | 0 | nsresult rv = digestFromManifest.ValidateLength(); |
216 | 0 | if (NS_FAILED(rv)) { |
217 | 0 | return rv; |
218 | 0 | } |
219 | 0 | |
220 | 0 | uint64_t len64; |
221 | 0 | rv = stream->Available(&len64); |
222 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
223 | 0 | if (len64 > UINT32_MAX) { |
224 | 0 | return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE; |
225 | 0 | } |
226 | 0 | |
227 | 0 | UniquePK11Context digestContext( |
228 | 0 | PK11_CreateDigestContext(digestFromManifest.mAlgorithm)); |
229 | 0 | if (!digestContext) { |
230 | 0 | return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); |
231 | 0 | } |
232 | 0 | |
233 | 0 | rv = MapSECStatus(PK11_DigestBegin(digestContext.get())); |
234 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
235 | 0 |
|
236 | 0 | uint64_t totalBytesRead = 0; |
237 | 0 | for (;;) { |
238 | 0 | uint32_t bytesRead; |
239 | 0 | rv = stream->Read(BitwiseCast<char*, unsigned char*>(buf.data), buf.len, |
240 | 0 | &bytesRead); |
241 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
242 | 0 |
|
243 | 0 | if (bytesRead == 0) { |
244 | 0 | break; // EOF |
245 | 0 | } |
246 | 0 | |
247 | 0 | totalBytesRead += bytesRead; |
248 | 0 | if (totalBytesRead >= UINT32_MAX) { |
249 | 0 | return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE; |
250 | 0 | } |
251 | 0 | |
252 | 0 | rv = MapSECStatus(PK11_DigestOp(digestContext.get(), buf.data, bytesRead)); |
253 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
254 | 0 | } |
255 | 0 |
|
256 | 0 | if (totalBytesRead != len64) { |
257 | 0 | // The metadata we used for Available() doesn't match the actual size of |
258 | 0 | // the entry. |
259 | 0 | return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; |
260 | 0 | } |
261 | 0 | |
262 | 0 | // Verify that the digests match. |
263 | 0 | Digest digest; |
264 | 0 | rv = digest.End(digestFromManifest.mAlgorithm, digestContext); |
265 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
266 | 0 |
|
267 | 0 | nsDependentCSubstring digestStr(DigestToDependentString(digest)); |
268 | 0 | if (!digestStr.Equals(digestFromManifest.mDigest)) { |
269 | 0 | return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY; |
270 | 0 | } |
271 | 0 | |
272 | 0 | return NS_OK; |
273 | 0 | } |
274 | | |
275 | | nsresult |
276 | | VerifyEntryContentDigest(nsIZipReader* zip, const nsACString& aFilename, |
277 | | const DigestWithAlgorithm& digestFromManifest, |
278 | | SECItem& buf) |
279 | 0 | { |
280 | 0 | nsCOMPtr<nsIInputStream> stream; |
281 | 0 | nsresult rv = zip->GetInputStream(aFilename, getter_AddRefs(stream)); |
282 | 0 | if (NS_FAILED(rv)) { |
283 | 0 | return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; |
284 | 0 | } |
285 | 0 | |
286 | 0 | return VerifyStreamContentDigest(stream, digestFromManifest, buf); |
287 | 0 | } |
288 | | |
289 | | // On input, nextLineStart is the start of the current line. On output, |
290 | | // nextLineStart is the start of the next line. |
291 | | nsresult |
292 | | ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line, |
293 | | bool allowContinuations = true) |
294 | 0 | { |
295 | 0 | line.Truncate(); |
296 | 0 | size_t previousLength = 0; |
297 | 0 | size_t currentLength = 0; |
298 | 0 | for (;;) { |
299 | 0 | const char* eol = PL_strpbrk(nextLineStart, "\r\n"); |
300 | 0 |
|
301 | 0 | if (!eol) { // Reached end of file before newline |
302 | 0 | eol = nextLineStart + strlen(nextLineStart); |
303 | 0 | } |
304 | 0 |
|
305 | 0 | previousLength = currentLength; |
306 | 0 | line.Append(nextLineStart, eol - nextLineStart); |
307 | 0 | currentLength = line.Length(); |
308 | 0 |
|
309 | 0 | // The spec says "No line may be longer than 72 bytes (not characters)" |
310 | 0 | // in its UTF8-encoded form. |
311 | 0 | static const size_t lineLimit = 72; |
312 | 0 | if (currentLength - previousLength > lineLimit) { |
313 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
314 | 0 | } |
315 | 0 | |
316 | 0 | // The spec says: "Implementations should support 65535-byte |
317 | 0 | // (not character) header values..." |
318 | 0 | if (currentLength > 65535) { |
319 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
320 | 0 | } |
321 | 0 | |
322 | 0 | if (*eol == '\r') { |
323 | 0 | ++eol; |
324 | 0 | } |
325 | 0 | if (*eol == '\n') { |
326 | 0 | ++eol; |
327 | 0 | } |
328 | 0 |
|
329 | 0 | nextLineStart = eol; |
330 | 0 |
|
331 | 0 | if (*eol != ' ') { |
332 | 0 | // not a continuation |
333 | 0 | return NS_OK; |
334 | 0 | } |
335 | 0 | |
336 | 0 | // continuation |
337 | 0 | if (!allowContinuations) { |
338 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
339 | 0 | } |
340 | 0 | |
341 | 0 | ++nextLineStart; // skip space and keep appending |
342 | 0 | } |
343 | 0 | } |
344 | | |
345 | | // The header strings are defined in the JAR specification. |
346 | | #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$" |
347 | | #define JAR_COSE_MF_SEARCH_STRING "(M|/M)ETA-INF/cose.manifest$" |
348 | | #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$" |
349 | 0 | #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$" |
350 | | #define JAR_COSE_SEARCH_STRING "(M|/M)ETA-INF/cose.sig$" |
351 | | #define JAR_META_DIR "META-INF" |
352 | | #define JAR_MF_HEADER "Manifest-Version: 1.0" |
353 | | #define JAR_SF_HEADER "Signature-Version: 1.0" |
354 | | |
355 | | nsresult |
356 | | ParseAttribute(const nsAutoCString & curLine, |
357 | | /*out*/ nsAutoCString & attrName, |
358 | | /*out*/ nsAutoCString & attrValue) |
359 | 0 | { |
360 | 0 | // Find the colon that separates the name from the value. |
361 | 0 | int32_t colonPos = curLine.FindChar(':'); |
362 | 0 | if (colonPos == kNotFound) { |
363 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
364 | 0 | } |
365 | 0 | |
366 | 0 | // set attrName to the name, skipping spaces between the name and colon |
367 | 0 | int32_t nameEnd = colonPos; |
368 | 0 | for (;;) { |
369 | 0 | if (nameEnd == 0) { |
370 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; // colon with no name |
371 | 0 | } |
372 | 0 | if (curLine[nameEnd - 1] != ' ') |
373 | 0 | break; |
374 | 0 | --nameEnd; |
375 | 0 | } |
376 | 0 | curLine.Left(attrName, nameEnd); |
377 | 0 |
|
378 | 0 | // Set attrValue to the value, skipping spaces between the colon and the |
379 | 0 | // value. The value may be empty. |
380 | 0 | int32_t valueStart = colonPos + 1; |
381 | 0 | int32_t curLineLength = curLine.Length(); |
382 | 0 | while (valueStart != curLineLength && curLine[valueStart] == ' ') { |
383 | 0 | ++valueStart; |
384 | 0 | } |
385 | 0 | curLine.Right(attrValue, curLineLength - valueStart); |
386 | 0 |
|
387 | 0 | return NS_OK; |
388 | 0 | } |
389 | | |
390 | | // Parses the version line of the MF or SF header. |
391 | | nsresult |
392 | | CheckManifestVersion(const char* & nextLineStart, |
393 | | const nsACString & expectedHeader) |
394 | 0 | { |
395 | 0 | // The JAR spec says: "Manifest-Version and Signature-Version must be first, |
396 | 0 | // and in exactly that case (so that they can be recognized easily as magic |
397 | 0 | // strings)." |
398 | 0 | nsAutoCString curLine; |
399 | 0 | nsresult rv = ReadLine(nextLineStart, curLine, false); |
400 | 0 | if (NS_FAILED(rv)) { |
401 | 0 | return rv; |
402 | 0 | } |
403 | 0 | if (!curLine.Equals(expectedHeader)) { |
404 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
405 | 0 | } |
406 | 0 | return NS_OK; |
407 | 0 | } |
408 | | |
409 | | // Parses a signature file (SF) based on the JDK 8 JAR Specification. |
410 | | // |
411 | | // The SF file must contain a SHA*-Digest-Manifest attribute in the main |
412 | | // section (where the * is either 1 or 256, depending on the given digest |
413 | | // algorithm). All other sections are ignored. This means that this will NOT |
414 | | // parse old-style signature files that have separate digests per entry. |
415 | | // The JDK8 x-Digest-Manifest variant is better because: |
416 | | // |
417 | | // (1) It allows us to follow the principle that we should minimize the |
418 | | // processing of data that we do before we verify its signature. In |
419 | | // particular, with the x-Digest-Manifest style, we can verify the digest |
420 | | // of MANIFEST.MF before we parse it, which prevents malicious JARs |
421 | | // exploiting our MANIFEST.MF parser. |
422 | | // (2) It is more time-efficient and space-efficient to have one |
423 | | // x-Digest-Manifest instead of multiple x-Digest values. |
424 | | // |
425 | | // filebuf must be null-terminated. On output, mfDigest will contain the |
426 | | // decoded value of the appropriate SHA*-DigestManifest, if found. |
427 | | nsresult |
428 | | ParseSF(const char* filebuf, SECOidTag digestAlgorithm, |
429 | | /*out*/ nsAutoCString& mfDigest) |
430 | 0 | { |
431 | 0 | const char* digestNameToFind = nullptr; |
432 | 0 | switch (digestAlgorithm) { |
433 | 0 | case SEC_OID_SHA256: |
434 | 0 | digestNameToFind = "sha256-digest-manifest"; |
435 | 0 | break; |
436 | 0 | case SEC_OID_SHA1: |
437 | 0 | digestNameToFind = "sha1-digest-manifest"; |
438 | 0 | break; |
439 | 0 | default: |
440 | 0 | MOZ_ASSERT_UNREACHABLE("bad argument to ParseSF"); |
441 | 0 | return NS_ERROR_FAILURE; |
442 | 0 | } |
443 | 0 |
|
444 | 0 | const char* nextLineStart = filebuf; |
445 | 0 | nsresult rv = CheckManifestVersion(nextLineStart, |
446 | 0 | NS_LITERAL_CSTRING(JAR_SF_HEADER)); |
447 | 0 | if (NS_FAILED(rv)) { |
448 | 0 | return rv; |
449 | 0 | } |
450 | 0 | |
451 | 0 | for (;;) { |
452 | 0 | nsAutoCString curLine; |
453 | 0 | rv = ReadLine(nextLineStart, curLine); |
454 | 0 | if (NS_FAILED(rv)) { |
455 | 0 | return rv; |
456 | 0 | } |
457 | 0 | |
458 | 0 | if (curLine.Length() == 0) { |
459 | 0 | // End of main section (blank line or end-of-file). We didn't find the |
460 | 0 | // SHA*-Digest-Manifest we were looking for. |
461 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
462 | 0 | } |
463 | 0 | |
464 | 0 | nsAutoCString attrName; |
465 | 0 | nsAutoCString attrValue; |
466 | 0 | rv = ParseAttribute(curLine, attrName, attrValue); |
467 | 0 | if (NS_FAILED(rv)) { |
468 | 0 | return rv; |
469 | 0 | } |
470 | 0 | |
471 | 0 | if (attrName.EqualsIgnoreCase(digestNameToFind)) { |
472 | 0 | rv = Base64Decode(attrValue, mfDigest); |
473 | 0 | if (NS_FAILED(rv)) { |
474 | 0 | return rv; |
475 | 0 | } |
476 | 0 | |
477 | 0 | // There could be multiple SHA*-Digest-Manifest attributes, which |
478 | 0 | // would be an error, but it's better to just skip any erroneous |
479 | 0 | // duplicate entries rather than trying to detect them, because: |
480 | 0 | // |
481 | 0 | // (1) It's simpler, and simpler generally means more secure |
482 | 0 | // (2) An attacker can't make us accept a JAR we would otherwise |
483 | 0 | // reject just by adding additional SHA*-Digest-Manifest |
484 | 0 | // attributes. |
485 | 0 | return NS_OK; |
486 | 0 | } |
487 | 0 |
|
488 | 0 | // ignore unrecognized attributes |
489 | 0 | } |
490 | 0 |
|
491 | 0 | MOZ_ASSERT_UNREACHABLE("somehow exited loop in ParseSF without returning"); |
492 | 0 | return NS_ERROR_FAILURE; |
493 | 0 | } |
494 | | |
495 | | // Parses MANIFEST.MF. The filenames of all entries will be returned in |
496 | | // mfItems. buf must be a pre-allocated scratch buffer that is used for doing |
497 | | // I/O. Each file's contents are verified against the entry in the manifest with |
498 | | // the digest algorithm that matches the given one. This algorithm comes from |
499 | | // the signature file. If the signature file has a SHA-256 digest, then SHA-256 |
500 | | // entries must be present in the manifest file. If the signature file only has |
501 | | // a SHA-1 digest, then only SHA-1 digests will be used in the manifest file. |
502 | | nsresult |
503 | | ParseMF(const char* filebuf, nsIZipReader* zip, SECOidTag digestAlgorithm, |
504 | | /*out*/ nsTHashtable<nsCStringHashKey>& mfItems, ScopedAutoSECItem& buf) |
505 | 0 | { |
506 | 0 | const char* digestNameToFind = nullptr; |
507 | 0 | switch (digestAlgorithm) { |
508 | 0 | case SEC_OID_SHA256: |
509 | 0 | digestNameToFind = "sha256-digest"; |
510 | 0 | break; |
511 | 0 | case SEC_OID_SHA1: |
512 | 0 | digestNameToFind = "sha1-digest"; |
513 | 0 | break; |
514 | 0 | default: |
515 | 0 | MOZ_ASSERT_UNREACHABLE("bad argument to ParseMF"); |
516 | 0 | return NS_ERROR_FAILURE; |
517 | 0 | } |
518 | 0 |
|
519 | 0 | const char* nextLineStart = filebuf; |
520 | 0 | nsresult rv = CheckManifestVersion(nextLineStart, |
521 | 0 | NS_LITERAL_CSTRING(JAR_MF_HEADER)); |
522 | 0 | if (NS_FAILED(rv)) { |
523 | 0 | return rv; |
524 | 0 | } |
525 | 0 | |
526 | 0 | // Skip the rest of the header section, which ends with a blank line. |
527 | 0 | { |
528 | 0 | nsAutoCString line; |
529 | 0 | do { |
530 | 0 | rv = ReadLine(nextLineStart, line); |
531 | 0 | if (NS_FAILED(rv)) { |
532 | 0 | return rv; |
533 | 0 | } |
534 | 0 | } while (line.Length() > 0); |
535 | 0 |
|
536 | 0 | // Manifest containing no file entries is OK, though useless. |
537 | 0 | if (*nextLineStart == '\0') { |
538 | 0 | return NS_OK; |
539 | 0 | } |
540 | 0 | } |
541 | 0 | |
542 | 0 | nsAutoCString curItemName; |
543 | 0 | nsAutoCString digest; |
544 | 0 |
|
545 | 0 | for (;;) { |
546 | 0 | nsAutoCString curLine; |
547 | 0 | rv = ReadLine(nextLineStart, curLine); |
548 | 0 | if (NS_FAILED(rv)) { |
549 | 0 | return rv; |
550 | 0 | } |
551 | 0 | |
552 | 0 | if (curLine.Length() == 0) { |
553 | 0 | // end of section (blank line or end-of-file) |
554 | 0 |
|
555 | 0 | if (curItemName.Length() == 0) { |
556 | 0 | // '...Each section must start with an attribute with the name as |
557 | 0 | // "Name",...', so every section must have a Name attribute. |
558 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
559 | 0 | } |
560 | 0 | |
561 | 0 | if (digest.IsEmpty()) { |
562 | 0 | // We require every entry to have a digest, since we require every |
563 | 0 | // entry to be signed and we don't allow duplicate entries. |
564 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
565 | 0 | } |
566 | 0 | |
567 | 0 | if (mfItems.Contains(curItemName)) { |
568 | 0 | // Duplicate entry |
569 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
570 | 0 | } |
571 | 0 | |
572 | 0 | // Verify that the entry's content digest matches the digest from this |
573 | 0 | // MF section. |
574 | 0 | DigestWithAlgorithm digestWithAlgorithm = { digest, digestAlgorithm }; |
575 | 0 | rv = VerifyEntryContentDigest(zip, curItemName, digestWithAlgorithm, buf); |
576 | 0 | if (NS_FAILED(rv)) { |
577 | 0 | return rv; |
578 | 0 | } |
579 | 0 | |
580 | 0 | mfItems.PutEntry(curItemName); |
581 | 0 |
|
582 | 0 | if (*nextLineStart == '\0') { |
583 | 0 | // end-of-file |
584 | 0 | break; |
585 | 0 | } |
586 | 0 | |
587 | 0 | // reset so we know we haven't encountered either of these for the next |
588 | 0 | // item yet. |
589 | 0 | curItemName.Truncate(); |
590 | 0 | digest.Truncate(); |
591 | 0 |
|
592 | 0 | continue; // skip the rest of the loop below |
593 | 0 | } |
594 | 0 | |
595 | 0 | nsAutoCString attrName; |
596 | 0 | nsAutoCString attrValue; |
597 | 0 | rv = ParseAttribute(curLine, attrName, attrValue); |
598 | 0 | if (NS_FAILED(rv)) { |
599 | 0 | return rv; |
600 | 0 | } |
601 | 0 | |
602 | 0 | // Lines to look for: |
603 | 0 | |
604 | 0 | // (1) Digest: |
605 | 0 | if (attrName.EqualsIgnoreCase(digestNameToFind)) { |
606 | 0 | if (!digest.IsEmpty()) { // multiple SHA* digests in section |
607 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
608 | 0 | } |
609 | 0 | |
610 | 0 | rv = Base64Decode(attrValue, digest); |
611 | 0 | if (NS_FAILED(rv)) { |
612 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
613 | 0 | } |
614 | 0 | |
615 | 0 | continue; |
616 | 0 | } |
617 | 0 | |
618 | 0 | // (2) Name: associates this manifest section with a file in the jar. |
619 | 0 | if (attrName.LowerCaseEqualsLiteral("name")) { |
620 | 0 | if (MOZ_UNLIKELY(curItemName.Length() > 0)) // multiple names in section |
621 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
622 | 0 | |
623 | 0 | if (MOZ_UNLIKELY(attrValue.Length() == 0)) |
624 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
625 | 0 | |
626 | 0 | curItemName = attrValue; |
627 | 0 |
|
628 | 0 | continue; |
629 | 0 | } |
630 | 0 | |
631 | 0 | // (3) Magic: the only other must-understand attribute |
632 | 0 | if (attrName.LowerCaseEqualsLiteral("magic")) { |
633 | 0 | // We don't understand any magic, so we can't verify an entry that |
634 | 0 | // requires magic. Since we require every entry to have a valid |
635 | 0 | // signature, we have no choice but to reject the entry. |
636 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
637 | 0 | } |
638 | 0 |
|
639 | 0 | // unrecognized attributes must be ignored |
640 | 0 | } |
641 | 0 |
|
642 | 0 | return NS_OK; |
643 | 0 | } |
644 | | |
645 | | nsresult |
646 | | VerifyCertificate(CERTCertificate* signerCert, AppTrustedRoot trustedRoot, |
647 | | /*out*/ UniqueCERTCertList& builtChain) |
648 | 0 | { |
649 | 0 | if (NS_WARN_IF(!signerCert)) { |
650 | 0 | return NS_ERROR_INVALID_ARG; |
651 | 0 | } |
652 | 0 | // TODO: pinArg is null. |
653 | 0 | AppTrustDomain trustDomain(builtChain, nullptr); |
654 | 0 | nsresult rv = trustDomain.SetTrustedRoot(trustedRoot); |
655 | 0 | if (NS_FAILED(rv)) { |
656 | 0 | return rv; |
657 | 0 | } |
658 | 0 | Input certDER; |
659 | 0 | mozilla::pkix::Result result = certDER.Init(signerCert->derCert.data, |
660 | 0 | signerCert->derCert.len); |
661 | 0 | if (result != Success) { |
662 | 0 | return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result)); |
663 | 0 | } |
664 | 0 | |
665 | 0 | result = BuildCertChain(trustDomain, certDER, Now(), |
666 | 0 | EndEntityOrCA::MustBeEndEntity, |
667 | 0 | KeyUsage::digitalSignature, |
668 | 0 | KeyPurposeId::id_kp_codeSigning, |
669 | 0 | CertPolicyId::anyPolicy, |
670 | 0 | nullptr /*stapledOCSPResponse*/); |
671 | 0 | if (result == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE) { |
672 | 0 | // For code-signing you normally need trusted 3rd-party timestamps to |
673 | 0 | // handle expiration properly. The signer could always mess with their |
674 | 0 | // system clock so you can't trust the certificate was un-expired when |
675 | 0 | // the signing took place. The choice is either to ignore expiration |
676 | 0 | // or to enforce expiration at time of use. The latter leads to the |
677 | 0 | // user-hostile result that perfectly good code stops working. |
678 | 0 | // |
679 | 0 | // Our package format doesn't support timestamps (nor do we have a |
680 | 0 | // trusted 3rd party timestamper), but since we sign all of our apps and |
681 | 0 | // add-ons ourselves we can trust ourselves not to mess with the clock |
682 | 0 | // on the signing systems. We also have a revocation mechanism if we |
683 | 0 | // need it. It's OK to ignore cert expiration under these conditions. |
684 | 0 | // |
685 | 0 | // This is an invalid approach if |
686 | 0 | // * we issue certs to let others sign their own packages |
687 | 0 | // * mozilla::pkix returns "expired" when there are "worse" problems |
688 | 0 | // with the certificate or chain. |
689 | 0 | // (see bug 1267318) |
690 | 0 | result = Success; |
691 | 0 | } |
692 | 0 | if (result != Success) { |
693 | 0 | return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result)); |
694 | 0 | } |
695 | 0 | |
696 | 0 | return NS_OK; |
697 | 0 | } |
698 | | |
699 | | // Given a SECOidTag representing a digest algorithm (either SEC_OID_SHA1 or |
700 | | // SEC_OID_SHA256), returns the first signerInfo in the given signedData that |
701 | | // purports to have been created using that digest algorithm, or nullptr if |
702 | | // there is none. |
703 | | // The returned signerInfo is owned by signedData, so the caller must ensure |
704 | | // that the lifetime of the signerInfo is contained by the lifetime of the |
705 | | // signedData. |
706 | | NSSCMSSignerInfo* |
707 | | GetSignerInfoForDigestAlgorithm(NSSCMSSignedData* signedData, |
708 | | SECOidTag digestAlgorithm) |
709 | 0 | { |
710 | 0 | MOZ_ASSERT(digestAlgorithm == SEC_OID_SHA1 || |
711 | 0 | digestAlgorithm == SEC_OID_SHA256); |
712 | 0 | if (digestAlgorithm != SEC_OID_SHA1 && digestAlgorithm != SEC_OID_SHA256) { |
713 | 0 | return nullptr; |
714 | 0 | } |
715 | 0 | |
716 | 0 | int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData); |
717 | 0 | if (numSigners < 1) { |
718 | 0 | return nullptr; |
719 | 0 | } |
720 | 0 | for (int i = 0; i < numSigners; i++) { |
721 | 0 | NSSCMSSignerInfo* signerInfo = |
722 | 0 | NSS_CMSSignedData_GetSignerInfo(signedData, i); |
723 | 0 | // NSS_CMSSignerInfo_GetDigestAlgTag isn't exported from NSS. |
724 | 0 | SECOidData* digestAlgOID = SECOID_FindOID(&signerInfo->digestAlg.algorithm); |
725 | 0 | if (!digestAlgOID) { |
726 | 0 | continue; |
727 | 0 | } |
728 | 0 | if (digestAlgorithm == digestAlgOID->offset) { |
729 | 0 | return signerInfo; |
730 | 0 | } |
731 | 0 | } |
732 | 0 | return nullptr; |
733 | 0 | } |
734 | | |
735 | | nsresult |
736 | | VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer, |
737 | | const SECItem& detachedSHA1Digest, |
738 | | const SECItem& detachedSHA256Digest, |
739 | | /*out*/ SECOidTag& digestAlgorithm, |
740 | | /*out*/ UniqueCERTCertList& builtChain) |
741 | 0 | { |
742 | 0 | if (NS_WARN_IF(!buffer.data || buffer.len == 0 || !detachedSHA1Digest.data || |
743 | 0 | detachedSHA1Digest.len == 0 || !detachedSHA256Digest.data || |
744 | 0 | detachedSHA256Digest.len == 0)) { |
745 | 0 | return NS_ERROR_INVALID_ARG; |
746 | 0 | } |
747 | 0 | |
748 | 0 | UniqueNSSCMSMessage |
749 | 0 | cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr, |
750 | 0 | nullptr, nullptr, nullptr, nullptr, |
751 | 0 | nullptr)); |
752 | 0 | if (!cmsMsg) { |
753 | 0 | return NS_ERROR_CMS_VERIFY_NOT_SIGNED; |
754 | 0 | } |
755 | 0 | |
756 | 0 | if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) { |
757 | 0 | return NS_ERROR_CMS_VERIFY_NOT_SIGNED; |
758 | 0 | } |
759 | 0 | |
760 | 0 | NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0); |
761 | 0 | if (!cinfo) { |
762 | 0 | return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; |
763 | 0 | } |
764 | 0 | |
765 | 0 | // We're expecting this to be a PKCS#7 signedData content info. |
766 | 0 | if (NSS_CMSContentInfo_GetContentTypeTag(cinfo) |
767 | 0 | != SEC_OID_PKCS7_SIGNED_DATA) { |
768 | 0 | return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; |
769 | 0 | } |
770 | 0 | |
771 | 0 | // signedData is non-owning |
772 | 0 | NSSCMSSignedData* signedData = |
773 | 0 | static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo)); |
774 | 0 | if (!signedData) { |
775 | 0 | return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; |
776 | 0 | } |
777 | 0 | |
778 | 0 | // Parse the certificates into CERTCertificate objects held in memory so |
779 | 0 | // verifyCertificate will be able to find them during path building. |
780 | 0 | UniqueCERTCertList certs(CERT_NewCertList()); |
781 | 0 | if (!certs) { |
782 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
783 | 0 | } |
784 | 0 | if (signedData->rawCerts) { |
785 | 0 | for (size_t i = 0; signedData->rawCerts[i]; ++i) { |
786 | 0 | UniqueCERTCertificate |
787 | 0 | cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), |
788 | 0 | signedData->rawCerts[i], nullptr, false, |
789 | 0 | true)); |
790 | 0 | // Skip certificates that fail to parse |
791 | 0 | if (!cert) { |
792 | 0 | continue; |
793 | 0 | } |
794 | 0 | |
795 | 0 | if (CERT_AddCertToListTail(certs.get(), cert.get()) != SECSuccess) { |
796 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
797 | 0 | } |
798 | 0 | |
799 | 0 | Unused << cert.release(); // Ownership transferred to the cert list. |
800 | 0 | } |
801 | 0 | } |
802 | 0 |
|
803 | 0 | NSSCMSSignerInfo* signerInfo = |
804 | 0 | GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA256); |
805 | 0 | const SECItem* detachedDigest = &detachedSHA256Digest; |
806 | 0 | digestAlgorithm = SEC_OID_SHA256; |
807 | 0 | if (!signerInfo) { |
808 | 0 | signerInfo = GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA1); |
809 | 0 | if (!signerInfo) { |
810 | 0 | return NS_ERROR_CMS_VERIFY_NOT_SIGNED; |
811 | 0 | } |
812 | 0 | detachedDigest = &detachedSHA1Digest; |
813 | 0 | digestAlgorithm = SEC_OID_SHA1; |
814 | 0 | } |
815 | 0 |
|
816 | 0 | // Get the end-entity certificate. |
817 | 0 | CERTCertificate* signerCert = |
818 | 0 | NSS_CMSSignerInfo_GetSigningCertificate(signerInfo, |
819 | 0 | CERT_GetDefaultCertDB()); |
820 | 0 | if (!signerCert) { |
821 | 0 | return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; |
822 | 0 | } |
823 | 0 | |
824 | 0 | nsresult rv = VerifyCertificate(signerCert, trustedRoot, builtChain); |
825 | 0 | if (NS_FAILED(rv)) { |
826 | 0 | return rv; |
827 | 0 | } |
828 | 0 | |
829 | 0 | // Ensure that the PKCS#7 data OID is present as the PKCS#9 contentType. |
830 | 0 | const char* pkcs7DataOidString = "1.2.840.113549.1.7.1"; |
831 | 0 | ScopedAutoSECItem pkcs7DataOid; |
832 | 0 | if (SEC_StringToOID(nullptr, &pkcs7DataOid, pkcs7DataOidString, 0) |
833 | 0 | != SECSuccess) { |
834 | 0 | return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; |
835 | 0 | } |
836 | 0 | |
837 | 0 | return MapSECStatus( |
838 | 0 | NSS_CMSSignerInfo_Verify(signerInfo, const_cast<SECItem*>(detachedDigest), |
839 | 0 | &pkcs7DataOid)); |
840 | 0 | } |
841 | | |
842 | | class CoseVerificationContext |
843 | | { |
844 | | public: |
845 | | explicit CoseVerificationContext(AppTrustedRoot aTrustedRoot) |
846 | | : mTrustedRoot(aTrustedRoot) |
847 | | , mCertDER(nullptr) |
848 | | , mCertDERLen(0) |
849 | 0 | { |
850 | 0 | } |
851 | 0 | ~CoseVerificationContext() {} |
852 | | |
853 | 0 | AppTrustedRoot GetTrustedRoot() { return mTrustedRoot; } |
854 | | nsresult SetCert(SECItem* aCertDER) |
855 | 0 | { |
856 | 0 | mCertDERLen = aCertDER->len; |
857 | 0 | mCertDER = MakeUnique<uint8_t[]>(mCertDERLen); |
858 | 0 | if (!mCertDER) { |
859 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
860 | 0 | } |
861 | 0 | memcpy(mCertDER.get(), aCertDER->data, mCertDERLen); |
862 | 0 | return NS_OK; |
863 | 0 | } |
864 | 0 | uint8_t* GetCert() { return mCertDER.get(); } |
865 | 0 | unsigned int GetCertLen() { return mCertDERLen; } |
866 | | |
867 | | private: |
868 | | AppTrustedRoot mTrustedRoot; |
869 | | UniquePtr<uint8_t[]> mCertDER; |
870 | | unsigned int mCertDERLen; |
871 | | }; |
872 | | |
873 | | // Verification function called from cose-rust. |
874 | | // Returns true if everything goes well and the signature and certificate chain |
875 | | // are good, false in any other case. |
876 | | bool |
877 | | CoseVerificationCallback(const uint8_t* aPayload, |
878 | | size_t aPayloadLen, |
879 | | const uint8_t** aCertChain, |
880 | | size_t aCertChainLen, |
881 | | const size_t* aCertsLen, |
882 | | const uint8_t* aEECert, |
883 | | size_t aEECertLen, |
884 | | const uint8_t* aSignature, |
885 | | size_t aSignatureLen, |
886 | | uint8_t aSignatureAlgorithm, |
887 | | void* ctx) |
888 | 0 | { |
889 | 0 | if (!ctx || !aPayload || !aEECert || !aSignature) { |
890 | 0 | return false; |
891 | 0 | } |
892 | 0 | // The ctx here is a pointer to a CoseVerificationContext object |
893 | 0 | CoseVerificationContext* context = static_cast<CoseVerificationContext*>(ctx); |
894 | 0 | AppTrustedRoot aTrustedRoot = context->GetTrustedRoot(); |
895 | 0 |
|
896 | 0 | CK_MECHANISM_TYPE mechanism; |
897 | 0 | SECOidTag oid; |
898 | 0 | uint32_t hash_length; |
899 | 0 | SECItem param = { siBuffer, nullptr, 0 }; |
900 | 0 | switch (aSignatureAlgorithm) { |
901 | 0 | case ES256: |
902 | 0 | mechanism = CKM_ECDSA; |
903 | 0 | oid = SEC_OID_SHA256; |
904 | 0 | hash_length = SHA256_LENGTH; |
905 | 0 | break; |
906 | 0 | case ES384: |
907 | 0 | mechanism = CKM_ECDSA; |
908 | 0 | oid = SEC_OID_SHA384; |
909 | 0 | hash_length = SHA384_LENGTH; |
910 | 0 | break; |
911 | 0 | case ES512: |
912 | 0 | mechanism = CKM_ECDSA; |
913 | 0 | oid = SEC_OID_SHA512; |
914 | 0 | hash_length = SHA512_LENGTH; |
915 | 0 | break; |
916 | 0 | default: |
917 | 0 | return false; |
918 | 0 | } |
919 | 0 | |
920 | 0 | uint8_t hashBuf[HASH_LENGTH_MAX]; |
921 | 0 | SECStatus rv = PK11_HashBuf(oid, hashBuf, aPayload, aPayloadLen); |
922 | 0 | if (rv != SECSuccess) { |
923 | 0 | return false; |
924 | 0 | } |
925 | 0 | SECItem hashItem = { siBuffer, hashBuf, hash_length }; |
926 | 0 | CERTCertDBHandle* dbHandle = CERT_GetDefaultCertDB(); |
927 | 0 | if (!dbHandle) { |
928 | 0 | return false; |
929 | 0 | } |
930 | 0 | SECItem derCert = { siBuffer, |
931 | 0 | const_cast<uint8_t*>(aEECert), |
932 | 0 | static_cast<unsigned int>(aEECertLen) }; |
933 | 0 | UniqueCERTCertificate cert( |
934 | 0 | CERT_NewTempCertificate(dbHandle, &derCert, nullptr, false, true)); |
935 | 0 | if (!cert) { |
936 | 0 | return false; |
937 | 0 | } |
938 | 0 | UniqueSECKEYPublicKey key(CERT_ExtractPublicKey(cert.get())); |
939 | 0 | if (!key) { |
940 | 0 | return false; |
941 | 0 | } |
942 | 0 | SECItem signatureItem = { siBuffer, |
943 | 0 | const_cast<uint8_t*>(aSignature), |
944 | 0 | static_cast<unsigned int>(aSignatureLen) }; |
945 | 0 | rv = PK11_VerifyWithMechanism( |
946 | 0 | key.get(), mechanism, ¶m, &signatureItem, &hashItem, nullptr); |
947 | 0 | if (rv != SECSuccess) { |
948 | 0 | return false; |
949 | 0 | } |
950 | 0 | |
951 | 0 | // Load intermediate certs into NSS so we can verify the cert chain. |
952 | 0 | UniqueCERTCertList tempCerts(CERT_NewCertList()); |
953 | 0 | for (size_t i = 0; i < aCertChainLen; ++i) { |
954 | 0 | SECItem derCert = { siBuffer, |
955 | 0 | const_cast<uint8_t*>(aCertChain[i]), |
956 | 0 | static_cast<unsigned int>(aCertsLen[i]) }; |
957 | 0 | UniqueCERTCertificate tempCert( |
958 | 0 | CERT_NewTempCertificate(dbHandle, &derCert, nullptr, false, true)); |
959 | 0 | // Skip certs that we can't parse. If it was one we needed, the verification |
960 | 0 | // will fail later. |
961 | 0 | if (!tempCert) { |
962 | 0 | continue; |
963 | 0 | } |
964 | 0 | if (CERT_AddCertToListTail(tempCerts.get(), tempCert.get()) != SECSuccess) { |
965 | 0 | return false; |
966 | 0 | } |
967 | 0 | Unused << tempCert.release(); |
968 | 0 | } |
969 | 0 |
|
970 | 0 | UniqueCERTCertList builtChain; |
971 | 0 | nsresult nrv = VerifyCertificate(cert.get(), aTrustedRoot, builtChain); |
972 | 0 | bool result = true; |
973 | 0 | if (NS_FAILED(nrv)) { |
974 | 0 | result = false; |
975 | 0 | } |
976 | 0 |
|
977 | 0 | // Passing back the signing certificate in form of the DER cert. |
978 | 0 | nrv = context->SetCert(&cert->derCert); |
979 | 0 | if (NS_FAILED(nrv)) { |
980 | 0 | result = false; |
981 | 0 | } |
982 | 0 |
|
983 | 0 | return result; |
984 | 0 | } |
985 | | |
986 | | nsresult |
987 | | VerifyAppManifest(SECOidTag aDigestToUse, nsCOMPtr<nsIZipReader> aZip, |
988 | | nsTHashtable<nsCStringHashKey>& aIgnoredFiles, |
989 | | const SECItem& aManifestBuffer) |
990 | 0 | { |
991 | 0 | // Allocate the I/O buffer only once per JAR, instead of once per entry, in |
992 | 0 | // order to minimize malloc/free calls and in order to avoid fragmenting |
993 | 0 | // memory. |
994 | 0 | ScopedAutoSECItem buf(128 * 1024); |
995 | 0 |
|
996 | 0 | nsTHashtable<nsCStringHashKey> items; |
997 | 0 |
|
998 | 0 | nsresult rv = ParseMF(BitwiseCast<char*, unsigned char*>(aManifestBuffer.data), |
999 | 0 | aZip, aDigestToUse, items, buf); |
1000 | 0 | if (NS_FAILED(rv)) { |
1001 | 0 | return rv; |
1002 | 0 | } |
1003 | 0 | |
1004 | 0 | // Verify every entry in the file. |
1005 | 0 | nsCOMPtr<nsIUTF8StringEnumerator> entries; |
1006 | 0 | rv = aZip->FindEntries(EmptyCString(), getter_AddRefs(entries)); |
1007 | 0 | if (NS_FAILED(rv)) { |
1008 | 0 | return rv; |
1009 | 0 | } |
1010 | 0 | if (!entries) { |
1011 | 0 | return NS_ERROR_UNEXPECTED; |
1012 | 0 | } |
1013 | 0 | |
1014 | 0 | for (;;) { |
1015 | 0 | bool hasMore; |
1016 | 0 | rv = entries->HasMore(&hasMore); |
1017 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1018 | 0 |
|
1019 | 0 | if (!hasMore) { |
1020 | 0 | break; |
1021 | 0 | } |
1022 | 0 | |
1023 | 0 | nsAutoCString entryFilename; |
1024 | 0 | rv = entries->GetNext(entryFilename); |
1025 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1026 | 0 |
|
1027 | 0 | MOZ_LOG(gPIPNSSLog, |
1028 | 0 | LogLevel::Debug, |
1029 | 0 | ("Verifying digests for %s", entryFilename.get())); |
1030 | 0 |
|
1031 | 0 | if (entryFilename.Length() == 0) { |
1032 | 0 | return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; |
1033 | 0 | } |
1034 | 0 | |
1035 | 0 | // The files that comprise the signature mechanism are not covered by the |
1036 | 0 | // signature. Ignore these files. |
1037 | 0 | if (aIgnoredFiles.Contains(entryFilename)) { |
1038 | 0 | continue; |
1039 | 0 | } |
1040 | 0 | |
1041 | 0 | // Entries with names that end in "/" are directory entries, which are not |
1042 | 0 | // signed. |
1043 | 0 | // |
1044 | 0 | // Since bug 1415991 we don't support unpacked JARs. The "/" entries are |
1045 | 0 | // therefore harmless. |
1046 | 0 | if (entryFilename.Last() == '/') { |
1047 | 0 | continue; |
1048 | 0 | } |
1049 | 0 | |
1050 | 0 | nsCStringHashKey* item = items.GetEntry(entryFilename); |
1051 | 0 | if (!item) { |
1052 | 0 | return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY; |
1053 | 0 | } |
1054 | 0 | |
1055 | 0 | // Remove the item so we can check for leftover items later |
1056 | 0 | items.RemoveEntry(item); |
1057 | 0 | } |
1058 | 0 |
|
1059 | 0 | // We verified that every entry that we require to be signed is signed. But, |
1060 | 0 | // were there any missing entries--that is, entries that are mentioned in the |
1061 | 0 | // manifest but missing from the archive? |
1062 | 0 | if (items.Count() != 0) { |
1063 | 0 | return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; |
1064 | 0 | } |
1065 | 0 | |
1066 | 0 | return NS_OK; |
1067 | 0 | } |
1068 | | |
1069 | | // This corresponds to the preference "security.signed_app_signatures.policy". |
1070 | | // The lowest order bit determines which PKCS#7 algorithms are accepted. |
1071 | | // xxx_0_: SHA-1 and/or SHA-256 PKCS#7 allowed |
1072 | | // xxx_1_: SHA-256 PKCS#7 allowed |
1073 | | // The next two bits determine whether COSE is required and PKCS#7 is allowed |
1074 | | // x_00_x: COSE disabled, ignore files, PKCS#7 must verify |
1075 | | // x_01_x: COSE is verified if present, PKCS#7 must verify |
1076 | | // x_10_x: COSE is required, PKCS#7 must verify if present |
1077 | | // x_11_x: COSE is required, PKCS#7 disabled (fail when present) |
1078 | | class SignaturePolicy |
1079 | | { |
1080 | | public: |
1081 | | explicit SignaturePolicy(int32_t preference) |
1082 | | : mProcessCose(true) |
1083 | | , mCoseRequired(false) |
1084 | | , mProcessPK7(true) |
1085 | | , mPK7Required(true) |
1086 | | , mSHA1Allowed(true) |
1087 | | , mSHA256Allowed(true) |
1088 | 0 | { |
1089 | 0 | mCoseRequired = (preference & 0b100) != 0; |
1090 | 0 | mProcessCose = (preference & 0b110) != 0; |
1091 | 0 | mPK7Required = (preference & 0b100) == 0; |
1092 | 0 | mProcessPK7 = (preference & 0b110) != 0b110; |
1093 | 0 | if ((preference & 0b1) == 0) { |
1094 | 0 | mSHA1Allowed = true; |
1095 | 0 | mSHA256Allowed = true; |
1096 | 0 | } else { |
1097 | 0 | mSHA1Allowed = false; |
1098 | 0 | mSHA256Allowed = true; |
1099 | 0 | } |
1100 | 0 | } |
1101 | | ~SignaturePolicy() |
1102 | 0 | { |
1103 | 0 | } |
1104 | 0 | bool ProcessCOSE() { return mProcessCose; } |
1105 | 0 | bool COSERequired() { return mCoseRequired; } |
1106 | 0 | bool PK7Required() { return mPK7Required; } |
1107 | 0 | bool ProcessPK7() { return mProcessPK7; } |
1108 | | bool IsPK7HashAllowed(SECOidTag aHashAlg) |
1109 | 0 | { |
1110 | 0 | if (aHashAlg == SEC_OID_SHA256 && mSHA256Allowed) { |
1111 | 0 | return true; |
1112 | 0 | } |
1113 | 0 | if (aHashAlg == SEC_OID_SHA1 && mSHA1Allowed) { |
1114 | 0 | return true; |
1115 | 0 | } |
1116 | 0 | return false; |
1117 | 0 | } |
1118 | | |
1119 | | private: |
1120 | | bool mProcessCose; |
1121 | | bool mCoseRequired; |
1122 | | bool mProcessPK7; |
1123 | | bool mPK7Required; |
1124 | | bool mSHA1Allowed; |
1125 | | bool mSHA256Allowed; |
1126 | | }; |
1127 | | |
1128 | | nsresult |
1129 | | VerifyCOSESignature(AppTrustedRoot aTrustedRoot, nsIZipReader* aZip, |
1130 | | SignaturePolicy& aPolicy, |
1131 | | nsTHashtable<nsCStringHashKey>& aIgnoredFiles, |
1132 | | /* out */ bool& aVerified, |
1133 | | /* out */ UniqueSECItem* aCoseCertItem) |
1134 | 0 | { |
1135 | 0 | NS_ENSURE_ARG_POINTER(aZip); |
1136 | 0 | NS_ENSURE_ARG_POINTER(aCoseCertItem); |
1137 | 0 | bool required = aPolicy.COSERequired(); |
1138 | 0 | aVerified = false; |
1139 | 0 |
|
1140 | 0 | // Read COSE signature file. |
1141 | 0 | nsAutoCString coseFilename; |
1142 | 0 | ScopedAutoSECItem coseBuffer; |
1143 | 0 | nsresult rv = FindAndLoadOneEntry( |
1144 | 0 | aZip, NS_LITERAL_CSTRING(JAR_COSE_SEARCH_STRING), coseFilename, coseBuffer); |
1145 | 0 | if (NS_FAILED(rv)) { |
1146 | 0 | return required ? NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE : NS_OK; |
1147 | 0 | } |
1148 | 0 |
|
1149 | 0 | // Verify COSE signature. |
1150 | 0 | nsAutoCString mfFilename; |
1151 | 0 | ScopedAutoSECItem manifestBuffer; |
1152 | 0 | rv = FindAndLoadOneEntry(aZip, |
1153 | 0 | NS_LITERAL_CSTRING(JAR_COSE_MF_SEARCH_STRING), |
1154 | 0 | mfFilename, |
1155 | 0 | manifestBuffer); |
1156 | 0 | if (NS_FAILED(rv)) { |
1157 | 0 | return required ? NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE : rv; |
1158 | 0 | } |
1159 | 0 | MOZ_ASSERT(manifestBuffer.len >= 1); |
1160 | 0 | MOZ_ASSERT(coseBuffer.len >= 1); |
1161 | 0 | CoseVerificationContext context(aTrustedRoot); |
1162 | 0 | bool coseVerification = verify_cose_signature_ffi(manifestBuffer.data, |
1163 | 0 | manifestBuffer.len - 1, |
1164 | 0 | coseBuffer.data, |
1165 | 0 | coseBuffer.len - 1, |
1166 | 0 | &context, |
1167 | 0 | CoseVerificationCallback); |
1168 | 0 | if (!coseVerification) { |
1169 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
1170 | 0 | } |
1171 | 0 | // CoseVerificationCallback sets the context certificate to the first cert |
1172 | 0 | // it encounters. |
1173 | 0 | const SECItem derCert = { siBuffer, context.GetCert(), context.GetCertLen() }; |
1174 | 0 | aCoseCertItem->reset(SECITEM_DupItem(&derCert)); |
1175 | 0 | if (!aCoseCertItem) { |
1176 | 0 | return NS_ERROR_FAILURE; |
1177 | 0 | } |
1178 | 0 | |
1179 | 0 | // aIgnoredFiles contains the PKCS#7 manifest and signature files iff the |
1180 | 0 | // PKCS#7 verification was successful. |
1181 | 0 | aIgnoredFiles.PutEntry(mfFilename); |
1182 | 0 | aIgnoredFiles.PutEntry(coseFilename); |
1183 | 0 | rv = VerifyAppManifest(SEC_OID_SHA256, aZip, aIgnoredFiles, manifestBuffer); |
1184 | 0 | if (NS_FAILED(rv)) { |
1185 | 0 | return rv; |
1186 | 0 | } |
1187 | 0 | |
1188 | 0 | aVerified = true; |
1189 | 0 | return NS_OK; |
1190 | 0 | } |
1191 | | |
1192 | | nsresult |
1193 | | VerifyPK7Signature(AppTrustedRoot aTrustedRoot, nsIZipReader* aZip, |
1194 | | SignaturePolicy& aPolicy, |
1195 | | /* out */ nsTHashtable<nsCStringHashKey>& aIgnoredFiles, |
1196 | | /* out */ bool& aVerified, |
1197 | | /* out */ UniqueCERTCertList& aBuiltChain) |
1198 | 0 | { |
1199 | 0 | NS_ENSURE_ARG_POINTER(aZip); |
1200 | 0 | bool required = aPolicy.PK7Required(); |
1201 | 0 | aVerified = false; |
1202 | 0 |
|
1203 | 0 | // Signature (RSA) file |
1204 | 0 | nsAutoCString sigFilename; |
1205 | 0 | ScopedAutoSECItem sigBuffer; |
1206 | 0 | nsresult rv = FindAndLoadOneEntry( |
1207 | 0 | aZip, nsLiteralCString(JAR_RSA_SEARCH_STRING), sigFilename, sigBuffer); |
1208 | 0 | if (NS_FAILED(rv)) { |
1209 | 0 | return required ? NS_ERROR_SIGNED_JAR_NOT_SIGNED : NS_OK; |
1210 | 0 | } |
1211 | 0 |
|
1212 | 0 | // Signature (SF) file |
1213 | 0 | nsAutoCString sfFilename; |
1214 | 0 | ScopedAutoSECItem sfBuffer; |
1215 | 0 | rv = FindAndLoadOneEntry( |
1216 | 0 | aZip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING), sfFilename, sfBuffer); |
1217 | 0 | if (NS_FAILED(rv)) { |
1218 | 0 | return required ? NS_ERROR_SIGNED_JAR_MANIFEST_INVALID : NS_OK; |
1219 | 0 | } |
1220 | 0 |
|
1221 | 0 | // Calculate both the SHA-1 and SHA-256 hashes of the signature file - we |
1222 | 0 | // don't know what algorithm the PKCS#7 signature used. |
1223 | 0 | Digest sfCalculatedSHA1Digest; |
1224 | 0 | rv = sfCalculatedSHA1Digest.DigestBuf( |
1225 | 0 | SEC_OID_SHA1, sfBuffer.data, sfBuffer.len - 1); |
1226 | 0 | if (NS_FAILED(rv)) { |
1227 | 0 | return rv; |
1228 | 0 | } |
1229 | 0 | Digest sfCalculatedSHA256Digest; |
1230 | 0 | rv = sfCalculatedSHA256Digest.DigestBuf( |
1231 | 0 | SEC_OID_SHA256, sfBuffer.data, sfBuffer.len - 1); |
1232 | 0 | if (NS_FAILED(rv)) { |
1233 | 0 | return rv; |
1234 | 0 | } |
1235 | 0 | |
1236 | 0 | // Verify PKCS#7 signature. |
1237 | 0 | // If we get here, the signature has to verify even if PKCS#7 is not required. |
1238 | 0 | sigBuffer.type = siBuffer; |
1239 | 0 | SECOidTag digestToUse; |
1240 | 0 | rv = VerifySignature(aTrustedRoot, |
1241 | 0 | sigBuffer, |
1242 | 0 | sfCalculatedSHA1Digest.get(), |
1243 | 0 | sfCalculatedSHA256Digest.get(), |
1244 | 0 | digestToUse, |
1245 | 0 | aBuiltChain); |
1246 | 0 | if (NS_FAILED(rv)) { |
1247 | 0 | return rv; |
1248 | 0 | } |
1249 | 0 | |
1250 | 0 | // Check the digest used for the signature against the policy. |
1251 | 0 | if (!aPolicy.IsPK7HashAllowed(digestToUse)) { |
1252 | 0 | return NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE; |
1253 | 0 | } |
1254 | 0 | |
1255 | 0 | nsAutoCString mfDigest; |
1256 | 0 | rv = ParseSF( |
1257 | 0 | BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse, mfDigest); |
1258 | 0 | if (NS_FAILED(rv)) { |
1259 | 0 | return rv; |
1260 | 0 | } |
1261 | 0 | |
1262 | 0 | // Read PK7 manifest (MF) file. |
1263 | 0 | ScopedAutoSECItem manifestBuffer; |
1264 | 0 | Digest mfCalculatedDigest; |
1265 | 0 | nsAutoCString mfFilename; |
1266 | 0 | rv = FindAndLoadOneEntry(aZip, |
1267 | 0 | NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING), |
1268 | 0 | mfFilename, |
1269 | 0 | manifestBuffer, |
1270 | 0 | digestToUse, |
1271 | 0 | &mfCalculatedDigest); |
1272 | 0 | if (NS_FAILED(rv)) { |
1273 | 0 | return rv; |
1274 | 0 | } |
1275 | 0 | |
1276 | 0 | nsDependentCSubstring calculatedDigest( |
1277 | 0 | DigestToDependentString(mfCalculatedDigest)); |
1278 | 0 | if (!mfDigest.Equals(calculatedDigest)) { |
1279 | 0 | return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; |
1280 | 0 | } |
1281 | 0 | |
1282 | 0 | // Verify PKCS7 manifest file hashes. |
1283 | 0 | aIgnoredFiles.PutEntry(sfFilename); |
1284 | 0 | aIgnoredFiles.PutEntry(sigFilename); |
1285 | 0 | aIgnoredFiles.PutEntry(mfFilename); |
1286 | 0 | rv = VerifyAppManifest(digestToUse, aZip, aIgnoredFiles, manifestBuffer); |
1287 | 0 | if (NS_FAILED(rv)) { |
1288 | 0 | aIgnoredFiles.Clear(); |
1289 | 0 | return rv; |
1290 | 0 | } |
1291 | 0 | |
1292 | 0 | aVerified = true; |
1293 | 0 | return NS_OK; |
1294 | 0 | } |
1295 | | |
1296 | | nsresult |
1297 | | OpenSignedAppFile(AppTrustedRoot aTrustedRoot, |
1298 | | nsIFile* aJarFile, |
1299 | | SignaturePolicy aPolicy, |
1300 | | /* out, optional */ nsIZipReader** aZipReader, |
1301 | | /* out, optional */ nsIX509Cert** aSignerCert) |
1302 | 0 | { |
1303 | 0 | NS_ENSURE_ARG_POINTER(aJarFile); |
1304 | 0 |
|
1305 | 0 | if (aZipReader) { |
1306 | 0 | *aZipReader = nullptr; |
1307 | 0 | } |
1308 | 0 |
|
1309 | 0 | if (aSignerCert) { |
1310 | 0 | *aSignerCert = nullptr; |
1311 | 0 | } |
1312 | 0 |
|
1313 | 0 | nsresult rv; |
1314 | 0 |
|
1315 | 0 | static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); |
1316 | 0 | nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv); |
1317 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1318 | 0 |
|
1319 | 0 | rv = zip->Open(aJarFile); |
1320 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1321 | 0 |
|
1322 | 0 | bool pk7Verified = false; |
1323 | 0 | bool coseVerified = false; |
1324 | 0 | nsTHashtable<nsCStringHashKey> ignoredFiles; |
1325 | 0 | UniqueCERTCertList pk7BuiltChain; |
1326 | 0 | UniqueSECItem coseCertItem; |
1327 | 0 |
|
1328 | 0 | // First we have to verify the PKCS#7 signature if there is one. |
1329 | 0 | // This signature covers all files (except for the signature files itself), |
1330 | 0 | // including the COSE signature files. Only when this verification is |
1331 | 0 | // successful the respective files will be ignored in the subsequent COSE |
1332 | 0 | // signature verification. |
1333 | 0 | if (aPolicy.ProcessPK7()) { |
1334 | 0 | rv = VerifyPK7Signature( |
1335 | 0 | aTrustedRoot, zip, aPolicy, ignoredFiles, pk7Verified, pk7BuiltChain); |
1336 | 0 | if (NS_FAILED(rv)) { |
1337 | 0 | return rv; |
1338 | 0 | } |
1339 | 0 | } |
1340 | 0 | |
1341 | 0 | if (aPolicy.ProcessCOSE()) { |
1342 | 0 | rv = VerifyCOSESignature( |
1343 | 0 | aTrustedRoot, zip, aPolicy, ignoredFiles, coseVerified, &coseCertItem); |
1344 | 0 | if (NS_FAILED(rv)) { |
1345 | 0 | return rv; |
1346 | 0 | } |
1347 | 0 | } |
1348 | 0 | |
1349 | 0 | if ((aPolicy.PK7Required() && !pk7Verified) || |
1350 | 0 | (aPolicy.COSERequired() && !coseVerified)) { |
1351 | 0 | return NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE; |
1352 | 0 | } |
1353 | 0 | |
1354 | 0 | // Return the reader to the caller if they want it |
1355 | 0 | if (aZipReader) { |
1356 | 0 | zip.forget(aZipReader); |
1357 | 0 | } |
1358 | 0 |
|
1359 | 0 | // Return the signer's certificate to the reader if they want it. |
1360 | 0 | // XXX: We should return an nsIX509CertList with the whole validated chain. |
1361 | 0 | if (aSignerCert) { |
1362 | 0 | // The COSE certificate is authoritative. |
1363 | 0 | if (aPolicy.COSERequired() || (coseCertItem && coseCertItem->len != 0)) { |
1364 | 0 | if (!coseCertItem || coseCertItem->len == 0) { |
1365 | 0 | return NS_ERROR_FAILURE; |
1366 | 0 | } |
1367 | 0 | CERTCertDBHandle* dbHandle = CERT_GetDefaultCertDB(); |
1368 | 0 | if (!dbHandle) { |
1369 | 0 | return NS_ERROR_FAILURE; |
1370 | 0 | } |
1371 | 0 | UniqueCERTCertificate cert(CERT_NewTempCertificate( |
1372 | 0 | dbHandle, coseCertItem.get(), nullptr, false, true)); |
1373 | 0 | if (!cert) { |
1374 | 0 | return NS_ERROR_FAILURE; |
1375 | 0 | } |
1376 | 0 | nsCOMPtr<nsIX509Cert> signerCert = nsNSSCertificate::Create(cert.get()); |
1377 | 0 | if (!signerCert) { |
1378 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1379 | 0 | } |
1380 | 0 | signerCert.forget(aSignerCert); |
1381 | 0 | } else { |
1382 | 0 | CERTCertListNode* signerCertNode = CERT_LIST_HEAD(pk7BuiltChain); |
1383 | 0 | if (!signerCertNode || CERT_LIST_END(signerCertNode, pk7BuiltChain) || |
1384 | 0 | !signerCertNode->cert) { |
1385 | 0 | return NS_ERROR_FAILURE; |
1386 | 0 | } |
1387 | 0 | nsCOMPtr<nsIX509Cert> signerCert = |
1388 | 0 | nsNSSCertificate::Create(signerCertNode->cert); |
1389 | 0 | NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY); |
1390 | 0 | signerCert.forget(aSignerCert); |
1391 | 0 | } |
1392 | 0 | } |
1393 | 0 |
|
1394 | 0 | return NS_OK; |
1395 | 0 | } |
1396 | | |
1397 | | class OpenSignedAppFileTask final : public CryptoTask |
1398 | | { |
1399 | | public: |
1400 | | OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile, |
1401 | | SignaturePolicy aPolicy, |
1402 | | nsIOpenSignedAppFileCallback* aCallback) |
1403 | | : mTrustedRoot(aTrustedRoot) |
1404 | | , mJarFile(aJarFile) |
1405 | | , mPolicy(aPolicy) |
1406 | | , mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>( |
1407 | | "OpenSignedAppFileTask::mCallback", aCallback)) |
1408 | 0 | { |
1409 | 0 | } |
1410 | | |
1411 | | private: |
1412 | | virtual nsresult CalculateResult() override |
1413 | 0 | { |
1414 | 0 | return OpenSignedAppFile(mTrustedRoot, mJarFile, mPolicy, |
1415 | 0 | getter_AddRefs(mZipReader), |
1416 | 0 | getter_AddRefs(mSignerCert)); |
1417 | 0 | } |
1418 | | |
1419 | | virtual void CallCallback(nsresult rv) override |
1420 | 0 | { |
1421 | 0 | (void) mCallback->OpenSignedAppFileFinished(rv, mZipReader, mSignerCert); |
1422 | 0 | } |
1423 | | |
1424 | | const AppTrustedRoot mTrustedRoot; |
1425 | | const nsCOMPtr<nsIFile> mJarFile; |
1426 | | const SignaturePolicy mPolicy; |
1427 | | nsMainThreadPtrHandle<nsIOpenSignedAppFileCallback> mCallback; |
1428 | | nsCOMPtr<nsIZipReader> mZipReader; // out |
1429 | | nsCOMPtr<nsIX509Cert> mSignerCert; // out |
1430 | | }; |
1431 | | |
1432 | | static const int32_t sDefaultSignaturePolicy = 0b10; |
1433 | | |
1434 | | } // unnamed namespace |
1435 | | |
1436 | | NS_IMETHODIMP |
1437 | | nsNSSCertificateDB::OpenSignedAppFileAsync( |
1438 | | AppTrustedRoot aTrustedRoot, nsIFile* aJarFile, |
1439 | | nsIOpenSignedAppFileCallback* aCallback) |
1440 | 0 | { |
1441 | 0 | NS_ENSURE_ARG_POINTER(aJarFile); |
1442 | 0 | NS_ENSURE_ARG_POINTER(aCallback); |
1443 | 0 | if (!NS_IsMainThread()) { |
1444 | 0 | return NS_ERROR_NOT_SAME_THREAD; |
1445 | 0 | } |
1446 | 0 | int32_t policyInt = |
1447 | 0 | Preferences::GetInt("security.signed_app_signatures.policy", |
1448 | 0 | static_cast<int32_t>(sDefaultSignaturePolicy)); |
1449 | 0 | SignaturePolicy policy(policyInt); |
1450 | 0 | RefPtr<OpenSignedAppFileTask> task(new OpenSignedAppFileTask(aTrustedRoot, |
1451 | 0 | aJarFile, |
1452 | 0 | policy, |
1453 | 0 | aCallback)); |
1454 | 0 | return task->Dispatch("SignedJAR"); |
1455 | 0 | } |