/src/mozilla-central/dom/media/webrtc/RTCCertificate.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 0 | #define RTCCERTIFICATE_SC_VERSION 0x00000001 |
25 | | |
26 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCCertificate, mGlobal) |
27 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCCertificate) |
28 | | 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 | 0 | #define ONE_DAY PRTime(PR_USEC_PER_SEC) * PRTime(60) /*sec*/ \ |
37 | 0 | * PRTime(60) /*min*/ * PRTime(24) /*hours*/ |
38 | 0 | #define EXPIRATION_DEFAULT ONE_DAY * PRTime(30) |
39 | 0 | #define EXPIRATION_SLACK ONE_DAY |
40 | 0 | #define EXPIRATION_MAX ONE_DAY * PRTime(365) /*year*/ |
41 | | |
42 | | const size_t RTCCertificateCommonNameLength = 16; |
43 | | const size_t RTCCertificateMinRsaSize = 1024; |
44 | | |
45 | | class GenerateRTCCertificateTask : public GenerateAsymmetricKeyTask |
46 | | { |
47 | | public: |
48 | | GenerateRTCCertificateTask(nsIGlobalObject* aGlobal, JSContext* aCx, |
49 | | const ObjectOrString& aAlgorithm, |
50 | | const Sequence<nsString>& aKeyUsages, |
51 | | PRTime aExpires) |
52 | | : GenerateAsymmetricKeyTask(aGlobal, aCx, aAlgorithm, true, aKeyUsages), |
53 | | mExpires(aExpires), |
54 | | mAuthType(ssl_kea_null), |
55 | | mCertificate(nullptr), |
56 | | mSignatureAlg(SEC_OID_UNKNOWN) |
57 | 0 | { |
58 | 0 | } |
59 | | |
60 | | private: |
61 | | PRTime mExpires; |
62 | | SSLKEAType mAuthType; |
63 | | UniqueCERTCertificate mCertificate; |
64 | | SECOidTag mSignatureAlg; |
65 | | |
66 | | static CERTName* GenerateRandomName(PK11SlotInfo* aSlot) |
67 | 0 | { |
68 | 0 | uint8_t randomName[RTCCertificateCommonNameLength]; |
69 | 0 | SECStatus rv = PK11_GenerateRandomOnSlot(aSlot, randomName, |
70 | 0 | sizeof(randomName)); |
71 | 0 | if (rv != SECSuccess) { |
72 | 0 | return nullptr; |
73 | 0 | } |
74 | 0 | |
75 | 0 | 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 | 0 | } |
80 | 0 | buf[sizeof(buf) - 1] = '\0'; |
81 | 0 |
|
82 | 0 | return CERT_AsciiToName(buf); |
83 | 0 | } |
84 | | |
85 | | nsresult GenerateCertificate() |
86 | 0 | { |
87 | 0 | UniquePK11SlotInfo slot(PK11_GetInternalSlot()); |
88 | 0 | MOZ_ASSERT(slot.get()); |
89 | 0 |
|
90 | 0 | UniqueCERTName subjectName(GenerateRandomName(slot.get())); |
91 | 0 | if (!subjectName) { |
92 | 0 | return NS_ERROR_DOM_UNKNOWN_ERR; |
93 | 0 | } |
94 | 0 | |
95 | 0 | UniqueSECKEYPublicKey publicKey(mKeyPair->mPublicKey.get()->GetPublicKey()); |
96 | 0 | UniqueCERTSubjectPublicKeyInfo spki( |
97 | 0 | SECKEY_CreateSubjectPublicKeyInfo(publicKey.get())); |
98 | 0 | if (!spki) { |
99 | 0 | return NS_ERROR_DOM_UNKNOWN_ERR; |
100 | 0 | } |
101 | 0 | |
102 | 0 | UniqueCERTCertificateRequest certreq( |
103 | 0 | CERT_CreateCertificateRequest(subjectName.get(), spki.get(), nullptr)); |
104 | 0 | if (!certreq) { |
105 | 0 | return NS_ERROR_DOM_UNKNOWN_ERR; |
106 | 0 | } |
107 | 0 | |
108 | 0 | PRTime now = PR_Now(); |
109 | 0 | PRTime notBefore = now - EXPIRATION_SLACK; |
110 | 0 | mExpires += now; |
111 | 0 |
|
112 | 0 | UniqueCERTValidity validity(CERT_CreateValidity(notBefore, mExpires)); |
113 | 0 | if (!validity) { |
114 | 0 | return NS_ERROR_DOM_UNKNOWN_ERR; |
115 | 0 | } |
116 | 0 | |
117 | 0 | unsigned long serial; |
118 | 0 | // Note: This serial in principle could collide, but it's unlikely, and we |
119 | 0 | // don't expect anyone to be validating certificates anyway. |
120 | 0 | SECStatus rv = |
121 | 0 | PK11_GenerateRandomOnSlot(slot.get(), |
122 | 0 | reinterpret_cast<unsigned char *>(&serial), |
123 | 0 | sizeof(serial)); |
124 | 0 | if (rv != SECSuccess) { |
125 | 0 | return NS_ERROR_DOM_UNKNOWN_ERR; |
126 | 0 | } |
127 | 0 | |
128 | 0 | CERTCertificate* cert = CERT_CreateCertificate(serial, subjectName.get(), |
129 | 0 | validity.get(), |
130 | 0 | certreq.get()); |
131 | 0 | if (!cert) { |
132 | 0 | return NS_ERROR_DOM_UNKNOWN_ERR; |
133 | 0 | } |
134 | 0 | mCertificate.reset(cert); |
135 | 0 | return NS_OK; |
136 | 0 | } |
137 | | |
138 | | nsresult SignCertificate() |
139 | 0 | { |
140 | 0 | MOZ_ASSERT(mSignatureAlg != SEC_OID_UNKNOWN); |
141 | 0 | PLArenaPool *arena = mCertificate->arena; |
142 | 0 |
|
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 | 0 | } |
148 | 0 | |
149 | 0 | // Set version to X509v3. |
150 | 0 | *(mCertificate->version.data) = SEC_CERTIFICATE_VERSION_3; |
151 | 0 | mCertificate->version.len = 1; |
152 | 0 |
|
153 | 0 | SECItem innerDER = { siBuffer, nullptr, 0 }; |
154 | 0 | if (!SEC_ASN1EncodeItem(arena, &innerDER, mCertificate.get(), |
155 | 0 | SEC_ASN1_GET(CERT_CertificateTemplate))) { |
156 | 0 | return NS_ERROR_DOM_UNKNOWN_ERR; |
157 | 0 | } |
158 | 0 | |
159 | 0 | SECItem *signedCert = PORT_ArenaZNew(arena, SECItem); |
160 | 0 | if (!signedCert) { |
161 | 0 | return NS_ERROR_DOM_UNKNOWN_ERR; |
162 | 0 | } |
163 | 0 | |
164 | 0 | 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 | 0 | } |
171 | 0 | mCertificate->derCert = *signedCert; |
172 | 0 | return NS_OK; |
173 | 0 | } |
174 | | |
175 | | nsresult BeforeCrypto() override |
176 | 0 | { |
177 | 0 | if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) { |
178 | 0 | // 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 | 0 | } |
183 | 0 | |
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 | 0 | } |
189 | 0 | |
190 | 0 | mSignatureAlg = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; |
191 | 0 | mAuthType = ssl_kea_rsa; |
192 | 0 |
|
193 | 0 | } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { |
194 | 0 | // We only support good curves in WebCrypto. |
195 | 0 | // If that ever changes, check that a good one was chosen. |
196 | 0 |
|
197 | 0 | mSignatureAlg = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; |
198 | 0 | mAuthType = ssl_kea_ecdh; |
199 | 0 | } else { |
200 | 0 | return NS_ERROR_DOM_NOT_SUPPORTED_ERR; |
201 | 0 | } |
202 | 0 | return NS_OK; |
203 | 0 | } |
204 | | |
205 | | nsresult DoCrypto() override |
206 | 0 | { |
207 | 0 | nsresult rv = GenerateAsymmetricKeyTask::DoCrypto(); |
208 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
209 | 0 |
|
210 | 0 | rv = GenerateCertificate(); |
211 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
212 | 0 |
|
213 | 0 | rv = SignCertificate(); |
214 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
215 | 0 |
|
216 | 0 | return NS_OK; |
217 | 0 | } |
218 | | |
219 | | virtual void Resolve() override |
220 | 0 | { |
221 | 0 | // Make copies of the private key and certificate, otherwise, when this |
222 | 0 | // 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 | 0 | 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 | | ReadExpires(JSContext* aCx, const ObjectOrString& aOptions, |
234 | | ErrorResult& aRv) |
235 | 0 | { |
236 | 0 | // This conversion might fail, but we don't really care; use the default. |
237 | 0 | // If this isn't an object, or it doesn't coerce into the right type, |
238 | 0 | // 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 | 0 | } |
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 | 0 | } |
248 | 0 | |
249 | 0 | if (!expiration.mExpires.WasPassed()) { |
250 | 0 | return EXPIRATION_DEFAULT; |
251 | 0 | } |
252 | 0 | static const uint64_t max = |
253 | 0 | static_cast<uint64_t>(EXPIRATION_MAX / PR_USEC_PER_MSEC); |
254 | 0 | if (expiration.mExpires.Value() > max) { |
255 | 0 | return EXPIRATION_MAX; |
256 | 0 | } |
257 | 0 | return static_cast<PRTime>(expiration.mExpires.Value() * PR_USEC_PER_MSEC); |
258 | 0 | } |
259 | | |
260 | | already_AddRefed<Promise> |
261 | | RTCCertificate::GenerateCertificate( |
262 | | const GlobalObject& aGlobal, const ObjectOrString& aOptions, |
263 | | ErrorResult& aRv, JS::Compartment* aCompartment) |
264 | 0 | { |
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 | 0 | } |
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 | 0 | } |
275 | 0 | |
276 | 0 | PRTime expires = ReadExpires(aGlobal.Context(), aOptions, aRv); |
277 | 0 | if (aRv.Failed()) { |
278 | 0 | return nullptr; |
279 | 0 | } |
280 | 0 | 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 | 0 | } |
286 | | |
287 | | RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal) |
288 | | : mGlobal(aGlobal), |
289 | | mPrivateKey(nullptr), |
290 | | mCertificate(nullptr), |
291 | | mAuthType(ssl_kea_null), |
292 | | mExpires(0) |
293 | 0 | { |
294 | 0 | } |
295 | | |
296 | | RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal, |
297 | | SECKEYPrivateKey* aPrivateKey, |
298 | | CERTCertificate* aCertificate, |
299 | | SSLKEAType aAuthType, |
300 | | PRTime aExpires) |
301 | | : mGlobal(aGlobal), |
302 | | mPrivateKey(aPrivateKey), |
303 | | mCertificate(aCertificate), |
304 | | mAuthType(aAuthType), |
305 | | mExpires(aExpires) |
306 | 0 | { |
307 | 0 | } |
308 | | |
309 | | RefPtr<DtlsIdentity> |
310 | | RTCCertificate::CreateDtlsIdentity() const |
311 | 0 | { |
312 | 0 | if (!mPrivateKey || !mCertificate) { |
313 | 0 | return nullptr; |
314 | 0 | } |
315 | 0 | UniqueSECKEYPrivateKey key(SECKEY_CopyPrivateKey(mPrivateKey.get())); |
316 | 0 | UniqueCERTCertificate cert(CERT_DupCertificate(mCertificate.get())); |
317 | 0 | RefPtr<DtlsIdentity> id = new DtlsIdentity(std::move(key), std::move(cert), mAuthType); |
318 | 0 | return id; |
319 | 0 | } |
320 | | |
321 | | JSObject* |
322 | | RTCCertificate::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
323 | 0 | { |
324 | 0 | return RTCCertificate_Binding::Wrap(aCx, this, aGivenProto); |
325 | 0 | } |
326 | | |
327 | | bool |
328 | | RTCCertificate::WritePrivateKey(JSStructuredCloneWriter* aWriter) const |
329 | 0 | { |
330 | 0 | JsonWebKey jwk; |
331 | 0 | nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey.get(), jwk); |
332 | 0 | if (NS_FAILED(rv)) { |
333 | 0 | return false; |
334 | 0 | } |
335 | 0 | nsString json; |
336 | 0 | if (!jwk.ToJSON(json)) { |
337 | 0 | return false; |
338 | 0 | } |
339 | 0 | return WriteString(aWriter, json); |
340 | 0 | } |
341 | | |
342 | | bool |
343 | | RTCCertificate::WriteCertificate(JSStructuredCloneWriter* aWriter) const |
344 | 0 | { |
345 | 0 | UniqueCERTCertificateList certs(CERT_CertListFromCert(mCertificate.get())); |
346 | 0 | if (!certs || certs->len <= 0) { |
347 | 0 | return false; |
348 | 0 | } |
349 | 0 | if (!JS_WriteUint32Pair(aWriter, certs->certs[0].len, 0)) { |
350 | 0 | return false; |
351 | 0 | } |
352 | 0 | return JS_WriteBytes(aWriter, certs->certs[0].data, certs->certs[0].len); |
353 | 0 | } |
354 | | |
355 | | bool |
356 | | RTCCertificate::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const |
357 | 0 | { |
358 | 0 | if (!mPrivateKey || !mCertificate) { |
359 | 0 | return false; |
360 | 0 | } |
361 | 0 | |
362 | 0 | return JS_WriteUint32Pair(aWriter, RTCCERTIFICATE_SC_VERSION, mAuthType) && |
363 | 0 | JS_WriteUint32Pair(aWriter, (mExpires >> 32) & 0xffffffff, |
364 | 0 | mExpires & 0xffffffff) && |
365 | 0 | WritePrivateKey(aWriter) && |
366 | 0 | WriteCertificate(aWriter); |
367 | 0 | } |
368 | | |
369 | | bool |
370 | | RTCCertificate::ReadPrivateKey(JSStructuredCloneReader* aReader) |
371 | 0 | { |
372 | 0 | nsString json; |
373 | 0 | if (!ReadString(aReader, json)) { |
374 | 0 | return false; |
375 | 0 | } |
376 | 0 | JsonWebKey jwk; |
377 | 0 | if (!jwk.Init(json)) { |
378 | 0 | return false; |
379 | 0 | } |
380 | 0 | mPrivateKey = CryptoKey::PrivateKeyFromJwk(jwk); |
381 | 0 | return !!mPrivateKey; |
382 | 0 | } |
383 | | |
384 | | bool |
385 | | RTCCertificate::ReadCertificate(JSStructuredCloneReader* aReader) |
386 | 0 | { |
387 | 0 | CryptoBuffer cert; |
388 | 0 | if (!ReadBuffer(aReader, cert) || cert.Length() == 0) { |
389 | 0 | return false; |
390 | 0 | } |
391 | 0 | |
392 | 0 | SECItem der = { siBuffer, cert.Elements(), |
393 | 0 | static_cast<unsigned int>(cert.Length()) }; |
394 | 0 | mCertificate.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), |
395 | 0 | &der, nullptr, true, true)); |
396 | 0 | return !!mCertificate; |
397 | 0 | } |
398 | | |
399 | | bool |
400 | | RTCCertificate::ReadStructuredClone(JSStructuredCloneReader* aReader) |
401 | 0 | { |
402 | 0 | uint32_t version, authType; |
403 | 0 | if (!JS_ReadUint32Pair(aReader, &version, &authType) || |
404 | 0 | version != RTCCERTIFICATE_SC_VERSION) { |
405 | 0 | return false; |
406 | 0 | } |
407 | 0 | mAuthType = static_cast<SSLKEAType>(authType); |
408 | 0 |
|
409 | 0 | uint32_t high, low; |
410 | 0 | if (!JS_ReadUint32Pair(aReader, &high, &low)) { |
411 | 0 | return false; |
412 | 0 | } |
413 | 0 | mExpires = static_cast<PRTime>(high) << 32 | low; |
414 | 0 |
|
415 | 0 | return ReadPrivateKey(aReader) && |
416 | 0 | ReadCertificate(aReader); |
417 | 0 | } |
418 | | |
419 | | } // namespace dom |
420 | | } // namespace mozilla |