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