From 93f1797507e7e559871f935e84344f34da37e60f Mon Sep 17 00:00:00 2001 From: Martin Thomson Date: Mon, 6 Jul 2015 10:40:04 -0700 Subject: [PATCH] Bug 1172785 - RTCCertificate implementation, r=rbarnes --HG-- extra : commitid : CBco7h85lO6 extra : rebase_source : 9cec281dd07e6d503a19a0ea57e5d4ceee98197c --- config/external/nss/nss.def | 1 + dom/base/StructuredCloneTags.h | 2 + dom/base/nsJSEnvironment.cpp | 28 ++ dom/crypto/WebCryptoCommon.h | 14 +- dom/crypto/WebCryptoTask.cpp | 410 +++++++++++++------------ dom/crypto/WebCryptoTask.h | 25 ++ dom/media/webrtc/RTCCertificate.cpp | 443 ++++++++++++++++++++++++++++ dom/media/webrtc/RTCCertificate.h | 95 ++++++ 8 files changed, 803 insertions(+), 215 deletions(-) create mode 100644 dom/media/webrtc/RTCCertificate.cpp create mode 100644 dom/media/webrtc/RTCCertificate.h diff --git a/config/external/nss/nss.def b/config/external/nss/nss.def index c0b7fc37e2c8..0397793d2b0e 100644 --- a/config/external/nss/nss.def +++ b/config/external/nss/nss.def @@ -23,6 +23,7 @@ CERT_CacheOCSPResponseFromSideChannel CERT_CertChainFromCert CERT_CertificateRequestTemplate DATA CERT_CertificateTemplate DATA +CERT_CertListFromCert CERT_ChangeCertTrust CERT_CheckCertUsage CERT_CheckCertValidTimes diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h index 9b279eed474d..637b9bd566cc 100644 --- a/dom/base/StructuredCloneTags.h +++ b/dom/base/StructuredCloneTags.h @@ -43,6 +43,8 @@ enum StructuredCloneTags { SCTAG_DOM_NFC_NDEF, + SCTAG_DOM_RTC_CERTIFICATE, + SCTAG_DOM_MAX }; diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 877ab1da97bb..5003cac2171b 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -61,6 +61,8 @@ #ifdef MOZ_NFC #include "mozilla/dom/MozNDEFRecord.h" #endif // MOZ_NFC +#include "mozilla/dom/RTCCertificate.h" +#include "mozilla/dom/RTCCertificateBinding.h" #include "mozilla/dom/StructuredClone.h" #include "mozilla/dom/SubtleCryptoBinding.h" #include "mozilla/ipc/BackgroundUtils.h" @@ -2547,6 +2549,25 @@ NS_DOMReadStructuredClone(JSContext* cx, #endif } + if (tag == SCTAG_DOM_RTC_CERTIFICATE) { + nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx)); + if (!global) { + return nullptr; + } + + // Prevent the return value from being trashed by a GC during ~nsRefPtr. + JS::Rooted result(cx); + { + nsRefPtr cert = new RTCCertificate(global); + if (!cert->ReadStructuredClone(reader)) { + result = nullptr; + } else { + result = cert->WrapObject(cx, nullptr); + } + } + return result; + } + // Don't know what this is. Bail. xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR); return nullptr; @@ -2571,6 +2592,13 @@ NS_DOMWriteStructuredClone(JSContext* cx, key->WriteStructuredClone(writer); } + // Handle WebRTC Certificate cloning + RTCCertificate* cert; + if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, obj, cert))) { + return JS_WriteUint32Pair(writer, SCTAG_DOM_RTC_CERTIFICATE, 0) && + cert->WriteStructuredClone(writer); + } + if (xpc::IsReflector(obj)) { nsCOMPtr base = xpc::UnwrapReflectorToISupports(obj); nsCOMPtr principal = do_QueryInterface(base); diff --git a/dom/crypto/WebCryptoCommon.h b/dom/crypto/WebCryptoCommon.h index 98c73fe50a94..681690b3000f 100644 --- a/dom/crypto/WebCryptoCommon.h +++ b/dom/crypto/WebCryptoCommon.h @@ -161,15 +161,21 @@ ReadBuffer(JSStructuredCloneReader* aReader, CryptoBuffer& aBuffer) } inline bool -WriteBuffer(JSStructuredCloneWriter* aWriter, const CryptoBuffer& aBuffer) +WriteBuffer(JSStructuredCloneWriter* aWriter, const uint8_t* aBuffer, size_t aLength) { - bool ret = JS_WriteUint32Pair(aWriter, aBuffer.Length(), 0); - if (ret && aBuffer.Length() > 0) { - ret = JS_WriteBytes(aWriter, aBuffer.Elements(), aBuffer.Length()); + bool ret = JS_WriteUint32Pair(aWriter, aLength, 0); + if (ret && aLength > 0) { + ret = JS_WriteBytes(aWriter, aBuffer, aLength); } return ret; } +inline bool +WriteBuffer(JSStructuredCloneWriter* aWriter, const CryptoBuffer& aBuffer) +{ + return WriteBuffer(aWriter, aBuffer.Elements(), aBuffer.Length()); +} + inline CK_MECHANISM_TYPE MapAlgorithmNameToMechanism(const nsString& aName) { diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp index f9f95a0c6723..b4952d398a66 100644 --- a/dom/crypto/WebCryptoTask.cpp +++ b/dom/crypto/WebCryptoTask.cpp @@ -2163,251 +2163,239 @@ private: } }; -class GenerateAsymmetricKeyTask : public WebCryptoTask +GenerateAsymmetricKeyTask::GenerateAsymmetricKeyTask( + JSContext* aCx, const ObjectOrString& aAlgorithm, bool aExtractable, + const Sequence& aKeyUsages) { -public: - GenerateAsymmetricKeyTask(JSContext* aCx, - const ObjectOrString& aAlgorithm, bool aExtractable, - const Sequence& aKeyUsages) - { - nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); - if (!global) { - mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; - return; - } + nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); + if (!global) { + mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; + return; + } - mArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); - if (!mArena) { - mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; - return; - } + mArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!mArena) { + mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; + return; + } - // Create an empty key and set easy attributes - mKeyPair.mPrivateKey = new CryptoKey(global); - mKeyPair.mPublicKey = new CryptoKey(global); + // Create an empty key and set easy attributes + mKeyPair.mPrivateKey = new CryptoKey(global); + mKeyPair.mPublicKey = new CryptoKey(global); - // Extract algorithm name - nsString algName; - mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName); + // Extract algorithm name + mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, mAlgName); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } + + // Construct an appropriate KeyAlorithm + uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0; + if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) || + mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { + RootedDictionary params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); if (NS_FAILED(mEarlyRv)) { mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; return; } - // Construct an appropriate KeyAlorithm - uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0; - if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) || - algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { - RootedDictionary params(aCx); - mEarlyRv = Coerce(aCx, params, aAlgorithm); - if (NS_FAILED(mEarlyRv)) { - mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; - return; - } + // Pull relevant info + uint32_t modulusLength = params.mModulusLength; + CryptoBuffer publicExponent; + ATTEMPT_BUFFER_INIT(publicExponent, params.mPublicExponent); + nsString hashName; + mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } - // Pull relevant info - uint32_t modulusLength = params.mModulusLength; - CryptoBuffer publicExponent; - ATTEMPT_BUFFER_INIT(publicExponent, params.mPublicExponent); - nsString hashName; - mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName); - if (NS_FAILED(mEarlyRv)) { - mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; - return; - } + // Create algorithm + if (!mKeyPair.mPublicKey.get()->Algorithm().MakeRsa(mAlgName, + modulusLength, + publicExponent, + hashName)) { + mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; + return; + } + if (!mKeyPair.mPrivateKey.get()->Algorithm().MakeRsa(mAlgName, + modulusLength, + publicExponent, + hashName)) { + mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; + return; + } + mMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; - // Create algorithm - if (!mKeyPair.mPublicKey.get()->Algorithm().MakeRsa(algName, - modulusLength, - publicExponent, - hashName)) { - mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; - return; - } - if (!mKeyPair.mPrivateKey.get()->Algorithm().MakeRsa(algName, - modulusLength, - publicExponent, - hashName)) { - mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; - return; - } - mMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; + // Set up params struct + mRsaParams.keySizeInBits = modulusLength; + bool converted = publicExponent.GetBigIntValue(mRsaParams.pe); + if (!converted) { + mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; + return; + } + } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) || + mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { + RootedDictionary params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } - // Set up params struct - mRsaParams.keySizeInBits = modulusLength; - bool converted = publicExponent.GetBigIntValue(mRsaParams.pe); - if (!converted) { - mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; - return; - } - } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) || - algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { - RootedDictionary params(aCx); - mEarlyRv = Coerce(aCx, params, aAlgorithm); - if (NS_FAILED(mEarlyRv)) { - mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; - return; - } - - if (!NormalizeToken(params.mNamedCurve, mNamedCurve)) { - mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; - return; - } - - // Create algorithm. - mKeyPair.mPublicKey.get()->Algorithm().MakeEc(algName, mNamedCurve); - mKeyPair.mPrivateKey.get()->Algorithm().MakeEc(algName, mNamedCurve); - mMechanism = CKM_EC_KEY_PAIR_GEN; - } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) { - RootedDictionary params(aCx); - mEarlyRv = Coerce(aCx, params, aAlgorithm); - if (NS_FAILED(mEarlyRv)) { - mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; - return; - } - - CryptoBuffer prime; - ATTEMPT_BUFFER_INIT(prime, params.mPrime); - - CryptoBuffer generator; - ATTEMPT_BUFFER_INIT(generator, params.mGenerator); - - // Set up params. - if (!prime.ToSECItem(mArena, &mDhParams.prime) || - !generator.ToSECItem(mArena, &mDhParams.base)) { - mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; - return; - } - - // Create algorithm. - if (!mKeyPair.mPublicKey.get()->Algorithm().MakeDh(algName, - prime, - generator)) { - mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; - return; - } - if (!mKeyPair.mPrivateKey.get()->Algorithm().MakeDh(algName, - prime, - generator)) { - mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; - return; - } - mMechanism = CKM_DH_PKCS_KEY_PAIR_GEN; - } else { + if (!NormalizeToken(params.mNamedCurve, mNamedCurve)) { mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; return; } - // Set key usages. - if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) || - algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { - privateAllowedUsages = CryptoKey::SIGN; - publicAllowedUsages = CryptoKey::VERIFY; - } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { - privateAllowedUsages = CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY; - publicAllowedUsages = CryptoKey::ENCRYPT | CryptoKey::WRAPKEY; - } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) || - algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) { - privateAllowedUsages = CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS; - publicAllowedUsages = 0; + // Create algorithm. + mKeyPair.mPublicKey.get()->Algorithm().MakeEc(mAlgName, mNamedCurve); + mKeyPair.mPrivateKey.get()->Algorithm().MakeEc(mAlgName, mNamedCurve); + mMechanism = CKM_EC_KEY_PAIR_GEN; + } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_DH)) { + RootedDictionary params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; } - mKeyPair.mPrivateKey.get()->SetExtractable(aExtractable); - mKeyPair.mPrivateKey.get()->SetType(CryptoKey::PRIVATE); + CryptoBuffer prime; + ATTEMPT_BUFFER_INIT(prime, params.mPrime); - mKeyPair.mPublicKey.get()->SetExtractable(true); - mKeyPair.mPublicKey.get()->SetType(CryptoKey::PUBLIC); + CryptoBuffer generator; + ATTEMPT_BUFFER_INIT(generator, params.mGenerator); - mKeyPair.mPrivateKey.get()->ClearUsages(); - mKeyPair.mPublicKey.get()->ClearUsages(); - for (uint32_t i=0; i < aKeyUsages.Length(); ++i) { - mEarlyRv = mKeyPair.mPrivateKey.get()->AddUsageIntersecting(aKeyUsages[i], - privateAllowedUsages); - if (NS_FAILED(mEarlyRv)) { - return; - } - - mEarlyRv = mKeyPair.mPublicKey.get()->AddUsageIntersecting(aKeyUsages[i], - publicAllowedUsages); - if (NS_FAILED(mEarlyRv)) { - return; - } + // Set up params. + if (!prime.ToSECItem(mArena, &mDhParams.prime) || + !generator.ToSECItem(mArena, &mDhParams.base)) { + mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; + return; } - // If no usages ended up being allowed, DataError - if (!mKeyPair.mPublicKey.get()->HasAnyUsage() && - !mKeyPair.mPrivateKey.get()->HasAnyUsage()) { - mEarlyRv = NS_ERROR_DOM_DATA_ERR; + // Create algorithm. + if (!mKeyPair.mPublicKey.get()->Algorithm().MakeDh(mAlgName, + prime, + generator)) { + mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; + return; + } + if (!mKeyPair.mPrivateKey.get()->Algorithm().MakeDh(mAlgName, + prime, + generator)) { + mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; + return; + } + mMechanism = CKM_DH_PKCS_KEY_PAIR_GEN; + } else { + mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; + return; + } + + // Set key usages. + if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) || + mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { + privateAllowedUsages = CryptoKey::SIGN; + publicAllowedUsages = CryptoKey::VERIFY; + } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { + privateAllowedUsages = CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY; + publicAllowedUsages = CryptoKey::ENCRYPT | CryptoKey::WRAPKEY; + } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) || + mAlgName.EqualsLiteral(WEBCRYPTO_ALG_DH)) { + privateAllowedUsages = CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS; + publicAllowedUsages = 0; + } + + mKeyPair.mPrivateKey.get()->SetExtractable(aExtractable); + mKeyPair.mPrivateKey.get()->SetType(CryptoKey::PRIVATE); + + mKeyPair.mPublicKey.get()->SetExtractable(true); + mKeyPair.mPublicKey.get()->SetType(CryptoKey::PUBLIC); + + mKeyPair.mPrivateKey.get()->ClearUsages(); + mKeyPair.mPublicKey.get()->ClearUsages(); + for (uint32_t i=0; i < aKeyUsages.Length(); ++i) { + mEarlyRv = mKeyPair.mPrivateKey.get()->AddUsageIntersecting(aKeyUsages[i], + privateAllowedUsages); + if (NS_FAILED(mEarlyRv)) { + return; + } + + mEarlyRv = mKeyPair.mPublicKey.get()->AddUsageIntersecting(aKeyUsages[i], + publicAllowedUsages); + if (NS_FAILED(mEarlyRv)) { return; } } -private: - ScopedPLArenaPool mArena; - CryptoKeyPair mKeyPair; - CK_MECHANISM_TYPE mMechanism; - PK11RSAGenParams mRsaParams; - SECKEYDHParams mDhParams; - ScopedSECKEYPublicKey mPublicKey; - ScopedSECKEYPrivateKey mPrivateKey; - nsString mNamedCurve; - - virtual void ReleaseNSSResources() override - { - mPublicKey.dispose(); - mPrivateKey.dispose(); + // If no usages ended up being allowed, DataError + if (!mKeyPair.mPublicKey.get()->HasAnyUsage() && + !mKeyPair.mPrivateKey.get()->HasAnyUsage()) { + mEarlyRv = NS_ERROR_DOM_DATA_ERR; + return; } +} - virtual nsresult DoCrypto() override - { - ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); - MOZ_ASSERT(slot.get()); +void +GenerateAsymmetricKeyTask::ReleaseNSSResources() +{ + mPublicKey.dispose(); + mPrivateKey.dispose(); +} - void* param; - switch (mMechanism) { - case CKM_RSA_PKCS_KEY_PAIR_GEN: - param = &mRsaParams; - break; - case CKM_DH_PKCS_KEY_PAIR_GEN: - param = &mDhParams; - break; - case CKM_EC_KEY_PAIR_GEN: { - param = CreateECParamsForCurve(mNamedCurve, mArena); - if (!param) { - return NS_ERROR_DOM_UNKNOWN_ERR; - } - break; +nsresult +GenerateAsymmetricKeyTask::DoCrypto() +{ + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + MOZ_ASSERT(slot.get()); + + void* param; + switch (mMechanism) { + case CKM_RSA_PKCS_KEY_PAIR_GEN: + param = &mRsaParams; + break; + case CKM_DH_PKCS_KEY_PAIR_GEN: + param = &mDhParams; + break; + case CKM_EC_KEY_PAIR_GEN: { + param = CreateECParamsForCurve(mNamedCurve, mArena); + if (!param) { + return NS_ERROR_DOM_UNKNOWN_ERR; } - default: - return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + break; } - - SECKEYPublicKey* pubKey = nullptr; - mPrivateKey = PK11_GenerateKeyPair(slot.get(), mMechanism, param, &pubKey, - PR_FALSE, PR_FALSE, nullptr); - mPublicKey = pubKey; - if (!mPrivateKey.get() || !mPublicKey.get()) { - return NS_ERROR_DOM_UNKNOWN_ERR; - } - - mKeyPair.mPrivateKey.get()->SetPrivateKey(mPrivateKey); - mKeyPair.mPublicKey.get()->SetPublicKey(mPublicKey); - - // PK11_GenerateKeyPair() does not set a CKA_EC_POINT attribute on the - // private key, we need this later when exporting to PKCS8 and JWK though. - if (mMechanism == CKM_EC_KEY_PAIR_GEN) { - nsresult rv = mKeyPair.mPrivateKey->AddPublicKeyData(mPublicKey); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); - } - - return NS_OK; + default: + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; } - virtual void Resolve() override - { - mResultPromise->MaybeResolve(mKeyPair); + SECKEYPublicKey* pubKey = nullptr; + mPrivateKey = PK11_GenerateKeyPair(slot.get(), mMechanism, param, &pubKey, + PR_FALSE, PR_FALSE, nullptr); + mPublicKey = pubKey; + if (!mPrivateKey.get() || !mPublicKey.get()) { + return NS_ERROR_DOM_UNKNOWN_ERR; } -}; + + mKeyPair.mPrivateKey.get()->SetPrivateKey(mPrivateKey); + mKeyPair.mPublicKey.get()->SetPublicKey(mPublicKey); + + // PK11_GenerateKeyPair() does not set a CKA_EC_POINT attribute on the + // private key, we need this later when exporting to PKCS8 and JWK though. + if (mMechanism == CKM_EC_KEY_PAIR_GEN) { + nsresult rv = mKeyPair.mPrivateKey->AddPublicKeyData(mPublicKey); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); + } + + return NS_OK; +} + +void +GenerateAsymmetricKeyTask::Resolve() +{ + mResultPromise->MaybeResolve(mKeyPair); +} class DerivePbkdfBitsTask : public ReturnArrayBufferViewTask { diff --git a/dom/crypto/WebCryptoTask.h b/dom/crypto/WebCryptoTask.h index 6b8d47dbb5fe..67521ee8d131 100644 --- a/dom/crypto/WebCryptoTask.h +++ b/dom/crypto/WebCryptoTask.h @@ -205,6 +205,31 @@ protected: virtual void CallCallback(nsresult rv) override final; }; +// XXX This class is declared here (unlike others) to enable reuse by WebRTC. +class GenerateAsymmetricKeyTask : public WebCryptoTask +{ +public: + GenerateAsymmetricKeyTask(JSContext* aCx, + const ObjectOrString& aAlgorithm, bool aExtractable, + const Sequence& aKeyUsages); +protected: + ScopedPLArenaPool mArena; + CryptoKeyPair mKeyPair; + nsString mAlgName; + CK_MECHANISM_TYPE mMechanism; + PK11RSAGenParams mRsaParams; + SECKEYDHParams mDhParams; + nsString mNamedCurve; + + virtual void ReleaseNSSResources() override; + virtual nsresult DoCrypto() override; + virtual void Resolve() override; + +private: + ScopedSECKEYPublicKey mPublicKey; + ScopedSECKEYPrivateKey mPrivateKey; +}; + } // namespace dom } // namespace mozilla diff --git a/dom/media/webrtc/RTCCertificate.cpp b/dom/media/webrtc/RTCCertificate.cpp new file mode 100644 index 000000000000..93686d340fa0 --- /dev/null +++ b/dom/media/webrtc/RTCCertificate.cpp @@ -0,0 +1,443 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/RTCCertificate.h" + +#include +#include "cert.h" +#include "jsapi.h" +#include "mozilla/dom/CryptoKey.h" +#include "mozilla/dom/RTCCertificateBinding.h" +#include "mozilla/dom/WebCryptoCommon.h" +#include "mozilla/dom/WebCryptoTask.h" + +#include + +namespace mozilla { +namespace dom { + +#define RTCCERTIFICATE_SC_VERSION 0x00000001 + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCCertificate, mGlobal) +NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCCertificate) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCCertificate) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCCertificate) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +// Note: explicit casts necessary to avoid +// warning C4307: '*' : integral constant overflow +#define ONE_DAY PRTime(PR_USEC_PER_SEC) * PRTime(60) /*sec*/ \ + * PRTime(60) /*min*/ * PRTime(24) /*hours*/ +#define EXPIRATION_DEFAULT ONE_DAY * PRTime(30) +#define EXPIRATION_SLACK ONE_DAY +#define EXPIRATION_MAX ONE_DAY * PRTime(365) /*year*/ + +const size_t RTCCertificateCommonNameLength = 16; +const size_t RTCCertificateMinRsaSize = 1024; + +class GenerateRTCCertificateTask : public GenerateAsymmetricKeyTask +{ +public: + GenerateRTCCertificateTask(JSContext* aCx, const ObjectOrString& aAlgorithm, + const Sequence& aKeyUsages) + : GenerateAsymmetricKeyTask(aCx, aAlgorithm, true, aKeyUsages), + mExpires(0), + mAuthType(ssl_kea_null), + mCertificate(nullptr), + mSignatureAlg(SEC_OID_UNKNOWN) + { + // Expiry is 30 days after by default. + // This is a sort of arbitrary range designed to be valid + // now with some slack in case the other side expects + // some before expiry. + // + + mExpires = EXPIRATION_DEFAULT; + if (!aAlgorithm.IsObject()) { + return; + } + + // Load the "expires" attribute from the algorithm dictionary. This is + // (currently) non-standard; it exists to support testing of certificate + // expiration, since one month is too long to wait for a test to run. + JS::Rooted exp(aCx, JS::UndefinedValue()); + JS::Rooted jsval(aCx, aAlgorithm.GetAsObject()); + bool ok = JS_GetProperty(aCx, jsval, "expires", &exp); + int64_t expval; + if (ok) { + ok = JS::ToInt64(aCx, exp, &expval); + } + if (ok && expval > 0) { + mExpires = std::min(expval, EXPIRATION_MAX); + } + } + +private: + PRTime mExpires; + SSLKEAType mAuthType; + ScopedCERTCertificate mCertificate; + SECOidTag mSignatureAlg; + + static CERTName* GenerateRandomName(PK11SlotInfo* aSlot) + { + uint8_t randomName[RTCCertificateCommonNameLength]; + SECStatus rv = PK11_GenerateRandomOnSlot(aSlot, randomName, + sizeof(randomName)); + if (rv != SECSuccess) { + return nullptr; + } + + char buf[sizeof(randomName) * 2 + 4]; + PL_strncpy(buf, "CN=", 3); + for (size_t i = 0; i < sizeof(randomName); ++i) { + PR_snprintf(&buf[i * 2 + 3], 2, "%.2x", randomName[i]); + } + buf[sizeof(buf) - 1] = '\0'; + + return CERT_AsciiToName(buf); + } + + nsresult GenerateCertificate() + { + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + MOZ_ASSERT(slot.get()); + + ScopedCERTName subjectName(GenerateRandomName(slot.get())); + if (!subjectName) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + ScopedSECKEYPublicKey publicKey(mKeyPair.mPublicKey.get()->GetPublicKey()); + ScopedCERTSubjectPublicKeyInfo spki( + SECKEY_CreateSubjectPublicKeyInfo(publicKey)); + if (!spki) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + ScopedCERTCertificateRequest certreq( + CERT_CreateCertificateRequest(subjectName, spki, nullptr)); + if (!certreq) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + PRTime now = PR_Now(); + PRTime notBefore = now - EXPIRATION_SLACK; + mExpires += now; + + ScopedCERTValidity validity(CERT_CreateValidity(notBefore, mExpires)); + if (!validity) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + unsigned long serial; + // Note: This serial in principle could collide, but it's unlikely, and we + // don't expect anyone to be validating certificates anyway. + SECStatus rv = + PK11_GenerateRandomOnSlot(slot, + reinterpret_cast(&serial), + sizeof(serial)); + if (rv != SECSuccess) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + CERTCertificate* cert = CERT_CreateCertificate(serial, subjectName, + validity, certreq); + if (!cert) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + mCertificate.reset(cert); + return NS_OK; + } + + nsresult SignCertificate() + { + MOZ_ASSERT(mSignatureAlg != SEC_OID_UNKNOWN); + PLArenaPool *arena = mCertificate->arena; + + SECStatus rv = SECOID_SetAlgorithmID(arena, &mCertificate->signature, + mSignatureAlg, nullptr); + if (rv != SECSuccess) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + // Set version to X509v3. + *(mCertificate->version.data) = SEC_CERTIFICATE_VERSION_3; + mCertificate->version.len = 1; + + SECItem innerDER = { siBuffer, nullptr, 0 }; + if (!SEC_ASN1EncodeItem(arena, &innerDER, mCertificate, + SEC_ASN1_GET(CERT_CertificateTemplate))) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + SECItem *signedCert = PORT_ArenaZNew(arena, SECItem); + if (!signedCert) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + ScopedSECKEYPrivateKey privateKey(mKeyPair.mPrivateKey.get()->GetPrivateKey()); + rv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len, + privateKey, mSignatureAlg); + if (rv != SECSuccess) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + mCertificate->derCert = *signedCert; + return NS_OK; + } + + nsresult BeforeCrypto() override + { + if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) { + // Double check that size is OK. + auto sz = static_cast(mRsaParams.keySizeInBits); + if (sz < RTCCertificateMinRsaSize) { + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } + + mSignatureAlg = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; + mAuthType = ssl_kea_rsa; + + } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { + // We only support good curves in WebCrypto. + // If that ever changes, check that a good one was chosen. + + mSignatureAlg = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; + mAuthType = ssl_kea_ecdh; + } else { + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } + return NS_OK; + } + + nsresult DoCrypto() override + { + nsresult rv = GenerateAsymmetricKeyTask::DoCrypto(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GenerateCertificate(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SignCertificate(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + + virtual void Resolve() override + { + // Make copies of the private key and certificate, otherwise, when this + // object is deleted, the structures they reference will be deleted too. + SECKEYPrivateKey* key = mKeyPair.mPrivateKey.get()->GetPrivateKey(); + CERTCertificate* cert = CERT_DupCertificate(mCertificate); + nsRefPtr result = + new RTCCertificate(mResultPromise->GetParentObject(), + key, cert, mAuthType, mExpires); + mResultPromise->MaybeResolve(result); + } +}; + +already_AddRefed +RTCCertificate::GenerateCertificate( + const GlobalObject& aGlobal, const ObjectOrString& aKeygenAlgorithm, + ErrorResult& aRv, JSCompartment* aCompartment) +{ + nsIGlobalObject* global = xpc::NativeGlobal(aGlobal.Get()); + nsRefPtr p = Promise::Create(global, aRv); + if (aRv.Failed()) { + return nullptr; + } + Sequence usages; + if (!usages.AppendElement(NS_LITERAL_STRING("sign"), fallible)) { + return nullptr; + } + nsRefPtr task = + new GenerateRTCCertificateTask(aGlobal.Context(), + aKeygenAlgorithm, usages); + task->DispatchWithPromise(p); + return p.forget(); +} + +RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal) + : mGlobal(aGlobal), + mPrivateKey(nullptr), + mCertificate(nullptr), + mAuthType(ssl_kea_null), + mExpires(0) +{ +} + +RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal, + SECKEYPrivateKey* aPrivateKey, + CERTCertificate* aCertificate, + SSLKEAType aAuthType, + PRTime aExpires) + : mGlobal(aGlobal), + mPrivateKey(aPrivateKey), + mCertificate(aCertificate), + mAuthType(aAuthType), + mExpires(aExpires) +{ +} + +RTCCertificate::~RTCCertificate() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(calledFromObject); +} + +// This creates some interesting lifecycle consequences, since the DtlsIdentity +// holds NSS objects, but does not implement nsNSSShutDownObject. + +// Unfortunately, the code that uses DtlsIdentity cannot always use that lock +// due to external linkage requirements. Therefore, the lock is held on this +// object instead. Consequently, the DtlsIdentity that this method returns must +// have a lifetime that is strictly shorter than the RTCCertificate. +// +// RTCPeerConnection provides this guarantee by holding a strong reference to +// the RTCCertificate. It will cleanup any DtlsIdentity instances that it +// creates before the RTCCertificate reference is released. +RefPtr +RTCCertificate::CreateDtlsIdentity() const +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown() || !mPrivateKey || !mCertificate) { + return nullptr; + } + SECKEYPrivateKey* key = SECKEY_CopyPrivateKey(mPrivateKey); + CERTCertificate* cert = CERT_DupCertificate(mCertificate); + RefPtr id = new DtlsIdentity(key, cert, mAuthType); + return id; +} + +JSObject* +RTCCertificate::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return RTCCertificateBinding::Wrap(aCx, this, aGivenProto); +} + +void +RTCCertificate::virtualDestroyNSSReference() +{ + destructorSafeDestroyNSSReference(); +} + +void +RTCCertificate::destructorSafeDestroyNSSReference() +{ + mPrivateKey.dispose(); + mCertificate.dispose(); +} + +bool +RTCCertificate::WritePrivateKey(JSStructuredCloneWriter* aWriter, + const nsNSSShutDownPreventionLock& aLockProof) const +{ + JsonWebKey jwk; + nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey, jwk, aLockProof); + if (NS_FAILED(rv)) { + return false; + } + nsString json; + if (!jwk.ToJSON(json)) { + return false; + } + return WriteString(aWriter, json); +} + +bool +RTCCertificate::WriteCertificate(JSStructuredCloneWriter* aWriter, + const nsNSSShutDownPreventionLock& /*proof*/) const +{ + ScopedCERTCertificateList certs(CERT_CertListFromCert(mCertificate.get())); + if (!certs || certs->len <= 0) { + return false; + } + if (!JS_WriteUint32Pair(aWriter, certs->certs[0].len, 0)) { + return false; + } + return JS_WriteBytes(aWriter, certs->certs[0].data, certs->certs[0].len); +} + +bool +RTCCertificate::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown() || !mPrivateKey || !mCertificate) { + return false; + } + + return JS_WriteUint32Pair(aWriter, RTCCERTIFICATE_SC_VERSION, mAuthType) && + JS_WriteUint32Pair(aWriter, (mExpires >> 32) & 0xffffffff, + mExpires & 0xffffffff) && + WritePrivateKey(aWriter, locker) && + WriteCertificate(aWriter, locker); +} + +bool +RTCCertificate::ReadPrivateKey(JSStructuredCloneReader* aReader, + const nsNSSShutDownPreventionLock& aLockProof) +{ + nsString json; + if (!ReadString(aReader, json)) { + return false; + } + JsonWebKey jwk; + if (!jwk.Init(json)) { + return false; + } + mPrivateKey = CryptoKey::PrivateKeyFromJwk(jwk, aLockProof); + return !!mPrivateKey; +} + +bool +RTCCertificate::ReadCertificate(JSStructuredCloneReader* aReader, + const nsNSSShutDownPreventionLock& /*proof*/) +{ + CryptoBuffer cert; + if (!ReadBuffer(aReader, cert) || cert.Length() == 0) { + return false; + } + + SECItem der = { siBuffer, cert.Elements(), + static_cast(cert.Length()) }; + mCertificate = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), + &der, nullptr, true, true); + return !!mCertificate; +} + +bool +RTCCertificate::ReadStructuredClone(JSStructuredCloneReader* aReader) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return false; + } + + uint32_t version, authType; + if (!JS_ReadUint32Pair(aReader, &version, &authType) || + version != RTCCERTIFICATE_SC_VERSION) { + return false; + } + mAuthType = static_cast(authType); + + uint32_t high, low; + if (!JS_ReadUint32Pair(aReader, &high, &low)) { + return false; + } + mExpires = static_cast(high) << 32 | low; + + return ReadPrivateKey(aReader, locker) && + ReadCertificate(aReader, locker); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/media/webrtc/RTCCertificate.h b/dom/media/webrtc/RTCCertificate.h new file mode 100644 index 000000000000..17fb42ddff19 --- /dev/null +++ b/dom/media/webrtc/RTCCertificate.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_RTCCertificate_h +#define mozilla_dom_RTCCertificate_h + +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "nsIGlobalObject.h" +#include "nsNSSShutDown.h" +#include "prtime.h" +#include "sslt.h" +#include "ScopedNSSTypes.h" + +#include "mozilla/ErrorResult.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/Date.h" +#include "mozilla/dom/CryptoKey.h" +#include "mtransport/dtlsidentity.h" +#include "js/StructuredClone.h" +#include "js/TypeDecls.h" + +namespace mozilla { +namespace dom { + +class ObjectOrString; + +class RTCCertificate final + : public nsISupports, + public nsWrapperCache, + public nsNSSShutDownObject +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(RTCCertificate) + + // WebIDL method that implements RTCPeerConnection.generateCertificate. + static already_AddRefed GenerateCertificate( + const GlobalObject& global, const ObjectOrString& keygenAlgorithm, + ErrorResult& aRv, JSCompartment* aCompartment = nullptr); + + explicit RTCCertificate(nsIGlobalObject* aGlobal); + RTCCertificate(nsIGlobalObject* aGlobal, SECKEYPrivateKey* aPrivateKey, + CERTCertificate* aCertificate, SSLKEAType aAuthType, + PRTime aExpires); + + nsIGlobalObject* GetParentObject() const { return mGlobal; } + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // WebIDL expires attribute. Note: JS dates are milliseconds since epoch; + // NSPR PRTime is in microseconds since the same epoch. + int64_t Expires() const { return mExpires / PR_USEC_PER_MSEC; } + + // Accessors for use by PeerConnectionImpl. + RefPtr CreateDtlsIdentity() const; + CERTCertificate* Certificate() const { return mCertificate; } + + // For nsNSSShutDownObject + virtual void virtualDestroyNSSReference() override; + void destructorSafeDestroyNSSReference(); + + // Structured clone methods + bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const; + bool ReadStructuredClone(JSStructuredCloneReader* aReader); + +private: + ~RTCCertificate(); + void operator=(const RTCCertificate&) = delete; + RTCCertificate(const RTCCertificate&) = delete; + + bool ReadCertificate(JSStructuredCloneReader* aReader, + const nsNSSShutDownPreventionLock& /*lockproof*/); + bool ReadPrivateKey(JSStructuredCloneReader* aReader, + const nsNSSShutDownPreventionLock& aLockProof); + bool WriteCertificate(JSStructuredCloneWriter* aWriter, + const nsNSSShutDownPreventionLock& /*lockproof*/) const; + bool WritePrivateKey(JSStructuredCloneWriter* aWriter, + const nsNSSShutDownPreventionLock& aLockProof) const; + + nsRefPtr mGlobal; + ScopedSECKEYPrivateKey mPrivateKey; + ScopedCERTCertificate mCertificate; + SSLKEAType mAuthType; + PRTime mExpires; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_RTCCertificate_h