Source code

Revision control

Other Tools

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 "ContentSignatureVerifier.h"
8
9
#include "BRNameMatchingPolicy.h"
10
#include "CryptoTask.h"
11
#include "ScopedNSSTypes.h"
12
#include "SharedCertVerifier.h"
13
#include "cryptohi.h"
14
#include "keyhi.h"
15
#include "mozilla/Base64.h"
16
#include "mozilla/Unused.h"
17
#include "mozilla/dom/Promise.h"
18
#include "nsCOMPtr.h"
19
#include "nsPromiseFlatString.h"
20
#include "nsProxyRelease.h"
21
#include "nsSecurityHeaderParser.h"
22
#include "nsWhitespaceTokenizer.h"
23
#include "mozpkix/pkix.h"
24
#include "mozpkix/pkixtypes.h"
25
#include "secerr.h"
26
27
NS_IMPL_ISUPPORTS(ContentSignatureVerifier, nsIContentSignatureVerifier)
28
29
using namespace mozilla;
30
using namespace mozilla::pkix;
31
using namespace mozilla::psm;
32
using dom::Promise;
33
34
static LazyLogModule gCSVerifierPRLog("ContentSignatureVerifier");
35
#define CSVerifier_LOG(args) MOZ_LOG(gCSVerifierPRLog, LogLevel::Debug, args)
36
37
// Content-Signature prefix
38
const unsigned char kPREFIX[] = {'C', 'o', 'n', 't', 'e', 'n', 't',
39
'-', 'S', 'i', 'g', 'n', 'a', 't',
40
'u', 'r', 'e', ':', 0};
41
42
class VerifyContentSignatureTask : public CryptoTask {
43
public:
44
VerifyContentSignatureTask(const nsACString& aData,
45
const nsACString& aCSHeader,
46
const nsACString& aCertChain,
47
const nsACString& aHostname,
48
RefPtr<Promise>& aPromise)
49
: mData(aData),
50
mCSHeader(aCSHeader),
51
mCertChain(aCertChain),
52
mHostname(aHostname),
53
mSignatureVerified(false),
54
mPromise(new nsMainThreadPtrHolder<Promise>(
55
"VerifyContentSignatureTask::mPromise", aPromise)) {}
56
57
private:
58
virtual nsresult CalculateResult() override;
59
virtual void CallCallback(nsresult rv) override;
60
61
nsCString mData;
62
nsCString mCSHeader;
63
nsCString mCertChain;
64
nsCString mHostname;
65
bool mSignatureVerified;
66
nsMainThreadPtrHandle<Promise> mPromise;
67
};
68
69
NS_IMETHODIMP
70
ContentSignatureVerifier::AsyncVerifyContentSignature(
71
const nsACString& aData, const nsACString& aCSHeader,
72
const nsACString& aCertChain, const nsACString& aHostname, JSContext* aCx,
73
Promise** aPromise) {
74
NS_ENSURE_ARG_POINTER(aCx);
75
76
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
77
if (NS_WARN_IF(!globalObject)) {
78
return NS_ERROR_UNEXPECTED;
79
}
80
81
ErrorResult result;
82
RefPtr<Promise> promise = Promise::Create(globalObject, result);
83
if (NS_WARN_IF(result.Failed())) {
84
return result.StealNSResult();
85
}
86
87
RefPtr<VerifyContentSignatureTask> task(new VerifyContentSignatureTask(
88
aData, aCSHeader, aCertChain, aHostname, promise));
89
nsresult rv = task->Dispatch();
90
if (NS_FAILED(rv)) {
91
return rv;
92
}
93
94
promise.forget(aPromise);
95
return NS_OK;
96
}
97
98
static nsresult VerifyContentSignatureInternal(
99
const nsACString& aData, const nsACString& aCSHeader,
100
const nsACString& aCertChain, const nsACString& aHostname,
101
/* out */
102
mozilla::Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS&
103
aErrorLabel,
104
/* out */ nsACString& aCertFingerprint, /* out */ uint32_t& aErrorValue);
105
static nsresult ParseContentSignatureHeader(
106
const nsACString& aContentSignatureHeader,
107
/* out */ nsCString& aSignature);
108
109
nsresult VerifyContentSignatureTask::CalculateResult() {
110
// 3 is the default, non-specific, "something failed" error.
111
Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS errorLabel =
112
Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err3;
113
nsAutoCString certFingerprint;
114
uint32_t errorValue = 3;
115
nsresult rv =
116
VerifyContentSignatureInternal(mData, mCSHeader, mCertChain, mHostname,
117
errorLabel, certFingerprint, errorValue);
118
if (NS_FAILED(rv)) {
119
CSVerifier_LOG(("CSVerifier: Signature verification failed"));
120
if (certFingerprint.Length() > 0) {
121
Telemetry::AccumulateCategoricalKeyed(certFingerprint, errorLabel);
122
}
123
Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, errorValue);
124
if (rv == NS_ERROR_INVALID_SIGNATURE) {
125
return NS_OK;
126
}
127
return rv;
128
}
129
130
mSignatureVerified = true;
131
Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 0);
132
133
return NS_OK;
134
}
135
136
void VerifyContentSignatureTask::CallCallback(nsresult rv) {
137
if (NS_FAILED(rv)) {
138
mPromise->MaybeReject(rv);
139
} else {
140
mPromise->MaybeResolve(mSignatureVerified);
141
}
142
}
143
144
bool IsNewLine(char16_t c) { return c == '\n' || c == '\r'; }
145
146
nsresult ReadChainIntoCertList(const nsACString& aCertChain,
147
CERTCertList* aCertList) {
148
bool inBlock = false;
149
bool certFound = false;
150
151
const nsCString header = NS_LITERAL_CSTRING("-----BEGIN CERTIFICATE-----");
152
const nsCString footer = NS_LITERAL_CSTRING("-----END CERTIFICATE-----");
153
154
nsCWhitespaceTokenizerTemplate<IsNewLine> tokenizer(aCertChain);
155
156
nsAutoCString blockData;
157
while (tokenizer.hasMoreTokens()) {
158
nsDependentCSubstring token = tokenizer.nextToken();
159
if (token.IsEmpty()) {
160
continue;
161
}
162
if (inBlock) {
163
if (token.Equals(footer)) {
164
inBlock = false;
165
certFound = true;
166
// base64 decode data, make certs, append to chain
167
nsAutoCString derString;
168
nsresult rv = Base64Decode(blockData, derString);
169
if (NS_FAILED(rv)) {
170
CSVerifier_LOG(("CSVerifier: decoding the signature failed"));
171
return rv;
172
}
173
SECItem der = {
174
siBuffer,
175
BitwiseCast<unsigned char*, const char*>(derString.get()),
176
derString.Length(),
177
};
178
UniqueCERTCertificate tmpCert(CERT_NewTempCertificate(
179
CERT_GetDefaultCertDB(), &der, nullptr, false, true));
180
if (!tmpCert) {
181
return NS_ERROR_FAILURE;
182
}
183
// if adding tmpCert succeeds, tmpCert will now be owned by aCertList
184
SECStatus res = CERT_AddCertToListTail(aCertList, tmpCert.get());
185
if (res != SECSuccess) {
186
return MapSECStatus(res);
187
}
188
Unused << tmpCert.release();
189
} else {
190
blockData.Append(token);
191
}
192
} else if (token.Equals(header)) {
193
inBlock = true;
194
blockData = "";
195
}
196
}
197
if (inBlock || !certFound) {
198
// the PEM data did not end; bad data.
199
CSVerifier_LOG(("CSVerifier: supplied chain contains bad data"));
200
return NS_ERROR_FAILURE;
201
}
202
return NS_OK;
203
}
204
205
// Given data to verify, a content signature header value, a string representing
206
// a list of PEM-encoded certificates, and a hostname to validate the
207
// certificates against, this function attempts to validate the certificate
208
// chain, extract the signature from the header, and verify the data using the
209
// key in the end-entity certificate from the chain. Returns NS_OK if everything
210
// is satisfactory and a failing nsresult otherwise. The output parameters are
211
// filled with telemetry data to report in the case of failures.
212
static nsresult VerifyContentSignatureInternal(
213
const nsACString& aData, const nsACString& aCSHeader,
214
const nsACString& aCertChain, const nsACString& aHostname,
215
/* out */
216
Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS& aErrorLabel,
217
/* out */ nsACString& aCertFingerprint,
218
/* out */ uint32_t& aErrorValue) {
219
UniqueCERTCertList certCertList(CERT_NewCertList());
220
if (!certCertList) {
221
return NS_ERROR_OUT_OF_MEMORY;
222
}
223
224
nsresult rv = ReadChainIntoCertList(aCertChain, certCertList.get());
225
if (NS_FAILED(rv)) {
226
return rv;
227
}
228
229
CERTCertListNode* node = CERT_LIST_HEAD(certCertList.get());
230
if (!node || CERT_LIST_END(node, certCertList.get()) || !node->cert) {
231
return NS_ERROR_FAILURE;
232
}
233
234
SECItem* certSecItem = &node->cert->derCert;
235
236
Input certDER;
237
mozilla::pkix::Result result =
238
certDER.Init(BitwiseCast<uint8_t*, unsigned char*>(certSecItem->data),
239
certSecItem->len);
240
if (result != Success) {
241
return NS_ERROR_FAILURE;
242
}
243
244
// Get EE certificate fingerprint for telemetry.
245
unsigned char fingerprint[SHA256_LENGTH] = {0};
246
SECStatus srv = PK11_HashBuf(SEC_OID_SHA256, fingerprint, certSecItem->data,
247
AssertedCast<int32_t>(certSecItem->len));
248
if (srv != SECSuccess) {
249
return NS_ERROR_FAILURE;
250
}
251
SECItem fingerprintItem = {siBuffer, fingerprint, SHA256_LENGTH};
252
UniquePORTString tmpFingerprintString(CERT_Hexify(&fingerprintItem, 0));
253
if (!tmpFingerprintString) {
254
return NS_ERROR_OUT_OF_MEMORY;
255
}
256
aCertFingerprint.Assign(tmpFingerprintString.get());
257
258
// Check the signerCert chain is good
259
CSTrustDomain trustDomain(certCertList);
260
result = BuildCertChain(
261
trustDomain, certDER, Now(), EndEntityOrCA::MustBeEndEntity,
262
KeyUsage::noParticularKeyUsageRequired, KeyPurposeId::id_kp_codeSigning,
263
CertPolicyId::anyPolicy, nullptr /*stapledOCSPResponse*/);
264
if (result != Success) {
265
// if there was a library error, return an appropriate error
266
if (IsFatalError(result)) {
267
return NS_ERROR_FAILURE;
268
}
269
// otherwise, assume the signature was invalid
270
if (result == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE) {
271
aErrorLabel =
272
Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err4;
273
aErrorValue = 4;
274
} else if (result ==
275
mozilla::pkix::Result::ERROR_NOT_YET_VALID_CERTIFICATE) {
276
aErrorLabel =
277
Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err5;
278
aErrorValue = 5;
279
} else {
280
// Building cert chain failed for some other reason.
281
aErrorLabel =
282
Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err6;
283
aErrorValue = 6;
284
}
285
CSVerifier_LOG(("CSVerifier: The supplied chain is bad (%s)",
286
MapResultToName(result)));
287
return NS_ERROR_INVALID_SIGNATURE;
288
}
289
290
// Check the SAN
291
Input hostnameInput;
292
293
result = hostnameInput.Init(
294
BitwiseCast<const uint8_t*, const char*>(aHostname.BeginReading()),
295
aHostname.Length());
296
if (result != Success) {
297
return NS_ERROR_FAILURE;
298
}
299
300
BRNameMatchingPolicy nameMatchingPolicy(BRNameMatchingPolicy::Mode::Enforce);
301
result = CheckCertHostname(certDER, hostnameInput, nameMatchingPolicy);
302
if (result != Success) {
303
// EE cert isnot valid for the given host name.
304
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err7;
305
aErrorValue = 7;
306
return NS_ERROR_INVALID_SIGNATURE;
307
}
308
309
mozilla::UniqueSECKEYPublicKey key(CERT_ExtractPublicKey(node->cert));
310
// in case we were not able to extract a key
311
if (!key) {
312
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8;
313
aErrorValue = 8;
314
CSVerifier_LOG(("CSVerifier: unable to extract a key"));
315
return NS_ERROR_INVALID_SIGNATURE;
316
}
317
318
nsAutoCString signature;
319
rv = ParseContentSignatureHeader(aCSHeader, signature);
320
if (NS_FAILED(rv)) {
321
return rv;
322
}
323
324
// Base 64 decode the signature
325
nsAutoCString rawSignature;
326
rv = Base64Decode(signature, rawSignature);
327
if (NS_FAILED(rv)) {
328
CSVerifier_LOG(("CSVerifier: decoding the signature failed"));
329
return rv;
330
}
331
332
// get signature object
333
ScopedAutoSECItem signatureItem;
334
SECItem rawSignatureItem = {
335
siBuffer,
336
BitwiseCast<unsigned char*, const char*>(rawSignature.get()),
337
rawSignature.Length(),
338
};
339
// We have a raw ecdsa signature r||s so we have to DER-encode it first
340
// Note that we have to check rawSignatureItem->len % 2 here as
341
// DSAU_EncodeDerSigWithLen asserts this
342
if (rawSignatureItem.len == 0 || rawSignatureItem.len % 2 != 0) {
343
CSVerifier_LOG(("CSVerifier: signature length is bad"));
344
return NS_ERROR_FAILURE;
345
}
346
if (DSAU_EncodeDerSigWithLen(&signatureItem, &rawSignatureItem,
347
rawSignatureItem.len) != SECSuccess) {
348
CSVerifier_LOG(("CSVerifier: encoding the signature failed"));
349
return NS_ERROR_FAILURE;
350
}
351
352
// this is the only OID we support for now
353
SECOidTag oid = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE;
354
mozilla::UniqueVFYContext cx(
355
VFY_CreateContext(key.get(), &signatureItem, oid, nullptr));
356
if (!cx) {
357
// Creating context failed.
358
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err9;
359
aErrorValue = 9;
360
return NS_ERROR_INVALID_SIGNATURE;
361
}
362
363
if (VFY_Begin(cx.get()) != SECSuccess) {
364
// Creating context failed.
365
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err9;
366
aErrorValue = 9;
367
return NS_ERROR_INVALID_SIGNATURE;
368
}
369
if (VFY_Update(cx.get(), kPREFIX, sizeof(kPREFIX)) != SECSuccess) {
370
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1;
371
aErrorValue = 1;
372
return NS_ERROR_INVALID_SIGNATURE;
373
}
374
if (VFY_Update(cx.get(),
375
reinterpret_cast<const unsigned char*>(aData.BeginReading()),
376
aData.Length()) != SECSuccess) {
377
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1;
378
aErrorValue = 1;
379
return NS_ERROR_INVALID_SIGNATURE;
380
}
381
if (VFY_End(cx.get()) != SECSuccess) {
382
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1;
383
aErrorValue = 1;
384
return NS_ERROR_INVALID_SIGNATURE;
385
}
386
387
return NS_OK;
388
}
389
390
static nsresult ParseContentSignatureHeader(
391
const nsACString& aContentSignatureHeader,
392
/* out */ nsCString& aSignature) {
393
// We only support p384 ecdsa.
394
NS_NAMED_LITERAL_CSTRING(signature_var, "p384ecdsa");
395
396
aSignature.Truncate();
397
398
const nsCString& flatHeader = PromiseFlatCString(aContentSignatureHeader);
399
nsSecurityHeaderParser parser(flatHeader);
400
nsresult rv = parser.Parse();
401
if (NS_FAILED(rv)) {
402
CSVerifier_LOG(("CSVerifier: could not parse ContentSignature header"));
403
return NS_ERROR_FAILURE;
404
}
405
LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
406
407
for (nsSecurityHeaderDirective* directive = directives->getFirst();
408
directive != nullptr; directive = directive->getNext()) {
409
CSVerifier_LOG(
410
("CSVerifier: found directive '%s'", directive->mName.get()));
411
if (directive->mName.Length() == signature_var.Length() &&
412
directive->mName.EqualsIgnoreCase(signature_var.get(),
413
signature_var.Length())) {
414
if (!aSignature.IsEmpty()) {
415
CSVerifier_LOG(("CSVerifier: found two ContentSignatures"));
416
return NS_ERROR_INVALID_SIGNATURE;
417
}
418
419
CSVerifier_LOG(("CSVerifier: found a ContentSignature directive"));
420
aSignature.Assign(directive->mValue);
421
}
422
}
423
424
// we have to ensure that we found a signature at this point
425
if (aSignature.IsEmpty()) {
426
CSVerifier_LOG(
427
("CSVerifier: got a Content-Signature header but didn't find a "
428
"signature."));
429
return NS_ERROR_FAILURE;
430
}
431
432
// Bug 769521: We have to change b64 url to regular encoding as long as we
433
// don't have a b64 url decoder. This should change soon, but in the meantime
434
// we have to live with this.
435
aSignature.ReplaceChar('-', '+');
436
aSignature.ReplaceChar('_', '/');
437
438
return NS_OK;
439
}