LCOV - code coverage report
Current view: top level - dom/media/webrtc - RTCCertificate.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 219 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 32 0.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13