1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=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 "mozilla/dom/RTCCertificate.h"
8
9 #include <cmath>
10 #include "cert.h"
11 #include "jsapi.h"
12 #include "mozilla/dom/CryptoKey.h"
13 #include "mozilla/dom/RTCCertificateBinding.h"
14 #include "mozilla/dom/WebCryptoCommon.h"
15 #include "mozilla/dom/WebCryptoTask.h"
16 #include "mozilla/Sprintf.h"
17
18 #include <cstdio>
19
20 namespace mozilla {
21 namespace dom {
22
23 #define RTCCERTIFICATE_SC_VERSION 0x00000001
24
25 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCCertificate, mGlobal)
26 NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCCertificate)
27 NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCCertificate)
28 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCCertificate)
29 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
30 NS_INTERFACE_MAP_ENTRY(nsISupports)
31 NS_INTERFACE_MAP_END
32
33 // Note: explicit casts necessary to avoid
34 // warning C4307: '*' : integral constant overflow
35 #define ONE_DAY PRTime(PR_USEC_PER_SEC) * PRTime(60) /*sec*/ \
36 * PRTime(60) /*min*/ * PRTime(24) /*hours*/
37 #define EXPIRATION_DEFAULT ONE_DAY * PRTime(30)
38 #define EXPIRATION_SLACK ONE_DAY
39 #define EXPIRATION_MAX ONE_DAY * PRTime(365) /*year*/
40
41 const size_t RTCCertificateCommonNameLength = 16;
42 const size_t RTCCertificateMinRsaSize = 1024;
43
44 class GenerateRTCCertificateTask : public GenerateAsymmetricKeyTask
45 {
46 public:
GenerateRTCCertificateTask(nsIGlobalObject * aGlobal,JSContext * aCx,const ObjectOrString & aAlgorithm,const Sequence<nsString> & aKeyUsages,PRTime aExpires)47 GenerateRTCCertificateTask(nsIGlobalObject* aGlobal, JSContext* aCx,
48 const ObjectOrString& aAlgorithm,
49 const Sequence<nsString>& aKeyUsages,
50 PRTime aExpires)
51 : GenerateAsymmetricKeyTask(aGlobal, aCx, aAlgorithm, true, aKeyUsages),
52 mExpires(aExpires),
53 mAuthType(ssl_kea_null),
54 mCertificate(nullptr),
55 mSignatureAlg(SEC_OID_UNKNOWN)
56 {
57 }
58
59 private:
60 PRTime mExpires;
61 SSLKEAType mAuthType;
62 UniqueCERTCertificate mCertificate;
63 SECOidTag mSignatureAlg;
64
GenerateRandomName(PK11SlotInfo * aSlot)65 static CERTName* GenerateRandomName(PK11SlotInfo* aSlot)
66 {
67 uint8_t randomName[RTCCertificateCommonNameLength];
68 SECStatus rv = PK11_GenerateRandomOnSlot(aSlot, randomName,
69 sizeof(randomName));
70 if (rv != SECSuccess) {
71 return nullptr;
72 }
73
74 char buf[sizeof(randomName) * 2 + 4];
75 PL_strncpy(buf, "CN=", 3);
76 for (size_t i = 0; i < sizeof(randomName); ++i) {
77 snprintf(&buf[i * 2 + 3], 2, "%.2x", randomName[i]);
78 }
79 buf[sizeof(buf) - 1] = '\0';
80
81 return CERT_AsciiToName(buf);
82 }
83
GenerateCertificate()84 nsresult GenerateCertificate()
85 {
86 ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
87 MOZ_ASSERT(slot.get());
88
89 ScopedCERTName subjectName(GenerateRandomName(slot.get()));
90 if (!subjectName) {
91 return NS_ERROR_DOM_UNKNOWN_ERR;
92 }
93
94 ScopedSECKEYPublicKey publicKey(mKeyPair->mPublicKey.get()->GetPublicKey());
95 ScopedCERTSubjectPublicKeyInfo spki(
96 SECKEY_CreateSubjectPublicKeyInfo(publicKey));
97 if (!spki) {
98 return NS_ERROR_DOM_UNKNOWN_ERR;
99 }
100
101 ScopedCERTCertificateRequest certreq(
102 CERT_CreateCertificateRequest(subjectName, spki, nullptr));
103 if (!certreq) {
104 return NS_ERROR_DOM_UNKNOWN_ERR;
105 }
106
107 PRTime now = PR_Now();
108 PRTime notBefore = now - EXPIRATION_SLACK;
109 mExpires += now;
110
111 ScopedCERTValidity validity(CERT_CreateValidity(notBefore, mExpires));
112 if (!validity) {
113 return NS_ERROR_DOM_UNKNOWN_ERR;
114 }
115
116 unsigned long serial;
117 // Note: This serial in principle could collide, but it's unlikely, and we
118 // don't expect anyone to be validating certificates anyway.
119 SECStatus rv =
120 PK11_GenerateRandomOnSlot(slot,
121 reinterpret_cast<unsigned char *>(&serial),
122 sizeof(serial));
123 if (rv != SECSuccess) {
124 return NS_ERROR_DOM_UNKNOWN_ERR;
125 }
126
127 CERTCertificate* cert = CERT_CreateCertificate(serial, subjectName,
128 validity, certreq);
129 if (!cert) {
130 return NS_ERROR_DOM_UNKNOWN_ERR;
131 }
132 mCertificate.reset(cert);
133 return NS_OK;
134 }
135
SignCertificate()136 nsresult SignCertificate()
137 {
138 MOZ_ASSERT(mSignatureAlg != SEC_OID_UNKNOWN);
139 PLArenaPool *arena = mCertificate->arena;
140
141 SECStatus rv = SECOID_SetAlgorithmID(arena, &mCertificate->signature,
142 mSignatureAlg, nullptr);
143 if (rv != SECSuccess) {
144 return NS_ERROR_DOM_UNKNOWN_ERR;
145 }
146
147 // Set version to X509v3.
148 *(mCertificate->version.data) = SEC_CERTIFICATE_VERSION_3;
149 mCertificate->version.len = 1;
150
151 SECItem innerDER = { siBuffer, nullptr, 0 };
152 if (!SEC_ASN1EncodeItem(arena, &innerDER, mCertificate.get(),
153 SEC_ASN1_GET(CERT_CertificateTemplate))) {
154 return NS_ERROR_DOM_UNKNOWN_ERR;
155 }
156
157 SECItem *signedCert = PORT_ArenaZNew(arena, SECItem);
158 if (!signedCert) {
159 return NS_ERROR_DOM_UNKNOWN_ERR;
160 }
161
162 ScopedSECKEYPrivateKey privateKey(mKeyPair->mPrivateKey.get()->GetPrivateKey());
163 rv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len,
164 privateKey, mSignatureAlg);
165 if (rv != SECSuccess) {
166 return NS_ERROR_DOM_UNKNOWN_ERR;
167 }
168 mCertificate->derCert = *signedCert;
169 return NS_OK;
170 }
171
BeforeCrypto()172 nsresult BeforeCrypto() override
173 {
174 if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
175 // Double check that size is OK.
176 auto sz = static_cast<size_t>(mRsaParams.keySizeInBits);
177 if (sz < RTCCertificateMinRsaSize) {
178 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
179 }
180
181 KeyAlgorithmProxy& alg = mKeyPair->mPublicKey.get()->Algorithm();
182 if (alg.mType != KeyAlgorithmProxy::RSA ||
183 !alg.mRsa.mHash.mName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
184 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
185 }
186
187 mSignatureAlg = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION;
188 mAuthType = ssl_kea_rsa;
189
190 } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
191 // We only support good curves in WebCrypto.
192 // If that ever changes, check that a good one was chosen.
193
194 mSignatureAlg = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE;
195 mAuthType = ssl_kea_ecdh;
196 } else {
197 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
198 }
199 return NS_OK;
200 }
201
DoCrypto()202 nsresult DoCrypto() override
203 {
204 nsresult rv = GenerateAsymmetricKeyTask::DoCrypto();
205 NS_ENSURE_SUCCESS(rv, rv);
206
207 rv = GenerateCertificate();
208 NS_ENSURE_SUCCESS(rv, rv);
209
210 rv = SignCertificate();
211 NS_ENSURE_SUCCESS(rv, rv);
212
213 return NS_OK;
214 }
215
Resolve()216 virtual void Resolve() override
217 {
218 // Make copies of the private key and certificate, otherwise, when this
219 // object is deleted, the structures they reference will be deleted too.
220 SECKEYPrivateKey* key = mKeyPair->mPrivateKey.get()->GetPrivateKey();
221 CERTCertificate* cert = CERT_DupCertificate(mCertificate.get());
222 RefPtr<RTCCertificate> result =
223 new RTCCertificate(mResultPromise->GetParentObject(),
224 key, cert, mAuthType, mExpires);
225 mResultPromise->MaybeResolve(result);
226 }
227 };
228
229 static PRTime
ReadExpires(JSContext * aCx,const ObjectOrString & aOptions,ErrorResult & aRv)230 ReadExpires(JSContext* aCx, const ObjectOrString& aOptions,
231 ErrorResult& aRv)
232 {
233 // This conversion might fail, but we don't really care; use the default.
234 // If this isn't an object, or it doesn't coerce into the right type,
235 // then we won't get the |expires| value. Either will be caught later.
236 RTCCertificateExpiration expiration;
237 if (!aOptions.IsObject()) {
238 return EXPIRATION_DEFAULT;
239 }
240 JS::RootedValue value(aCx, JS::ObjectValue(*aOptions.GetAsObject()));
241 if (!expiration.Init(aCx, value)) {
242 aRv.NoteJSContextException(aCx);
243 return 0;
244 }
245
246 if (!expiration.mExpires.WasPassed()) {
247 return EXPIRATION_DEFAULT;
248 }
249 static const uint64_t max =
250 static_cast<uint64_t>(EXPIRATION_MAX / PR_USEC_PER_MSEC);
251 if (expiration.mExpires.Value() > max) {
252 return EXPIRATION_MAX;
253 }
254 return static_cast<PRTime>(expiration.mExpires.Value() * PR_USEC_PER_MSEC);
255 }
256
257 already_AddRefed<Promise>
GenerateCertificate(const GlobalObject & aGlobal,const ObjectOrString & aOptions,ErrorResult & aRv,JSCompartment * aCompartment)258 RTCCertificate::GenerateCertificate(
259 const GlobalObject& aGlobal, const ObjectOrString& aOptions,
260 ErrorResult& aRv, JSCompartment* aCompartment)
261 {
262 nsIGlobalObject* global = xpc::NativeGlobal(aGlobal.Get());
263 RefPtr<Promise> p = Promise::Create(global, aRv);
264 if (aRv.Failed()) {
265 return nullptr;
266 }
267 Sequence<nsString> usages;
268 if (!usages.AppendElement(NS_LITERAL_STRING("sign"), fallible)) {
269 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
270 return nullptr;
271 }
272
273 PRTime expires = ReadExpires(aGlobal.Context(), aOptions, aRv);
274 if (aRv.Failed()) {
275 return nullptr;
276 }
277 RefPtr<WebCryptoTask> task =
278 new GenerateRTCCertificateTask(global, aGlobal.Context(),
279 aOptions, usages, expires);
280 task->DispatchWithPromise(p);
281 return p.forget();
282 }
283
RTCCertificate(nsIGlobalObject * aGlobal)284 RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal)
285 : mGlobal(aGlobal),
286 mPrivateKey(nullptr),
287 mCertificate(nullptr),
288 mAuthType(ssl_kea_null),
289 mExpires(0)
290 {
291 }
292
RTCCertificate(nsIGlobalObject * aGlobal,SECKEYPrivateKey * aPrivateKey,CERTCertificate * aCertificate,SSLKEAType aAuthType,PRTime aExpires)293 RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal,
294 SECKEYPrivateKey* aPrivateKey,
295 CERTCertificate* aCertificate,
296 SSLKEAType aAuthType,
297 PRTime aExpires)
298 : mGlobal(aGlobal),
299 mPrivateKey(aPrivateKey),
300 mCertificate(aCertificate),
301 mAuthType(aAuthType),
302 mExpires(aExpires)
303 {
304 }
305
~RTCCertificate()306 RTCCertificate::~RTCCertificate()
307 {
308 nsNSSShutDownPreventionLock locker;
309 if (isAlreadyShutDown()) {
310 return;
311 }
312 destructorSafeDestroyNSSReference();
313 shutdown(ShutdownCalledFrom::Object);
314 }
315
316 // This creates some interesting lifecycle consequences, since the DtlsIdentity
317 // holds NSS objects, but does not implement nsNSSShutDownObject.
318
319 // Unfortunately, the code that uses DtlsIdentity cannot always use that lock
320 // due to external linkage requirements. Therefore, the lock is held on this
321 // object instead. Consequently, the DtlsIdentity that this method returns must
322 // have a lifetime that is strictly shorter than the RTCCertificate.
323 //
324 // RTCPeerConnection provides this guarantee by holding a strong reference to
325 // the RTCCertificate. It will cleanup any DtlsIdentity instances that it
326 // creates before the RTCCertificate reference is released.
327 RefPtr<DtlsIdentity>
CreateDtlsIdentity() const328 RTCCertificate::CreateDtlsIdentity() const
329 {
330 nsNSSShutDownPreventionLock locker;
331 if (isAlreadyShutDown() || !mPrivateKey || !mCertificate) {
332 return nullptr;
333 }
334 SECKEYPrivateKey* key = SECKEY_CopyPrivateKey(mPrivateKey.get());
335 CERTCertificate* cert = CERT_DupCertificate(mCertificate.get());
336 RefPtr<DtlsIdentity> id = new DtlsIdentity(key, cert, mAuthType);
337 return id;
338 }
339
340 JSObject*
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)341 RTCCertificate::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
342 {
343 return RTCCertificateBinding::Wrap(aCx, this, aGivenProto);
344 }
345
346 void
virtualDestroyNSSReference()347 RTCCertificate::virtualDestroyNSSReference()
348 {
349 destructorSafeDestroyNSSReference();
350 }
351
352 void
destructorSafeDestroyNSSReference()353 RTCCertificate::destructorSafeDestroyNSSReference()
354 {
355 mPrivateKey.reset();
356 mCertificate.reset();
357 }
358
359 bool
WritePrivateKey(JSStructuredCloneWriter * aWriter,const nsNSSShutDownPreventionLock & aLockProof) const360 RTCCertificate::WritePrivateKey(JSStructuredCloneWriter* aWriter,
361 const nsNSSShutDownPreventionLock& aLockProof) const
362 {
363 JsonWebKey jwk;
364 nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey.get(), jwk, aLockProof);
365 if (NS_FAILED(rv)) {
366 return false;
367 }
368 nsString json;
369 if (!jwk.ToJSON(json)) {
370 return false;
371 }
372 return WriteString(aWriter, json);
373 }
374
375 bool
WriteCertificate(JSStructuredCloneWriter * aWriter,const nsNSSShutDownPreventionLock &) const376 RTCCertificate::WriteCertificate(JSStructuredCloneWriter* aWriter,
377 const nsNSSShutDownPreventionLock& /*proof*/) const
378 {
379 ScopedCERTCertificateList certs(CERT_CertListFromCert(mCertificate.get()));
380 if (!certs || certs->len <= 0) {
381 return false;
382 }
383 if (!JS_WriteUint32Pair(aWriter, certs->certs[0].len, 0)) {
384 return false;
385 }
386 return JS_WriteBytes(aWriter, certs->certs[0].data, certs->certs[0].len);
387 }
388
389 bool
WriteStructuredClone(JSStructuredCloneWriter * aWriter) const390 RTCCertificate::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
391 {
392 nsNSSShutDownPreventionLock locker;
393 if (isAlreadyShutDown() || !mPrivateKey || !mCertificate) {
394 return false;
395 }
396
397 return JS_WriteUint32Pair(aWriter, RTCCERTIFICATE_SC_VERSION, mAuthType) &&
398 JS_WriteUint32Pair(aWriter, (mExpires >> 32) & 0xffffffff,
399 mExpires & 0xffffffff) &&
400 WritePrivateKey(aWriter, locker) &&
401 WriteCertificate(aWriter, locker);
402 }
403
404 bool
ReadPrivateKey(JSStructuredCloneReader * aReader,const nsNSSShutDownPreventionLock & aLockProof)405 RTCCertificate::ReadPrivateKey(JSStructuredCloneReader* aReader,
406 const nsNSSShutDownPreventionLock& aLockProof)
407 {
408 nsString json;
409 if (!ReadString(aReader, json)) {
410 return false;
411 }
412 JsonWebKey jwk;
413 if (!jwk.Init(json)) {
414 return false;
415 }
416 mPrivateKey.reset(CryptoKey::PrivateKeyFromJwk(jwk, aLockProof));
417 return !!mPrivateKey;
418 }
419
420 bool
ReadCertificate(JSStructuredCloneReader * aReader,const nsNSSShutDownPreventionLock &)421 RTCCertificate::ReadCertificate(JSStructuredCloneReader* aReader,
422 const nsNSSShutDownPreventionLock& /*proof*/)
423 {
424 CryptoBuffer cert;
425 if (!ReadBuffer(aReader, cert) || cert.Length() == 0) {
426 return false;
427 }
428
429 SECItem der = { siBuffer, cert.Elements(),
430 static_cast<unsigned int>(cert.Length()) };
431 mCertificate.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
432 &der, nullptr, true, true));
433 return !!mCertificate;
434 }
435
436 bool
ReadStructuredClone(JSStructuredCloneReader * aReader)437 RTCCertificate::ReadStructuredClone(JSStructuredCloneReader* aReader)
438 {
439 nsNSSShutDownPreventionLock locker;
440 if (isAlreadyShutDown()) {
441 return false;
442 }
443
444 uint32_t version, authType;
445 if (!JS_ReadUint32Pair(aReader, &version, &authType) ||
446 version != RTCCERTIFICATE_SC_VERSION) {
447 return false;
448 }
449 mAuthType = static_cast<SSLKEAType>(authType);
450
451 uint32_t high, low;
452 if (!JS_ReadUint32Pair(aReader, &high, &low)) {
453 return false;
454 }
455 mExpires = static_cast<PRTime>(high) << 32 | low;
456
457 return ReadPrivateKey(aReader, locker) &&
458 ReadCertificate(aReader, locker);
459 }
460
461 } // namespace dom
462 } // namespace mozilla
463