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