Coverage Report

Created: 2018-09-25 14:53

/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