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