Bug 1172785 - Using RTCCertificate for WebRTC, r=ekr
authorMartin Thomson <martin.thomson@gmail.com>
Mon, 06 Jul 2015 10:40:04 -0700
changeset 251593 9c2fb609e5d8b6775ee681cc7057151c0429c5b6
parent 251592 d9bd3e512cd05066ca26895bd2386b50977db2e6
child 251594 a75d15eefefc03df75c4fc9bd9cf39d124bf447a
push id29007
push userryanvm@gmail.com
push dateTue, 07 Jul 2015 18:38:06 +0000
treeherdermozilla-central@9340658848d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersekr
bugs1172785
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1172785 - Using RTCCertificate for WebRTC, r=ekr
dom/media/PeerConnection.js
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/test_peerConnection_certificates.html
dom/media/webrtc/moz.build
media/mtransport/dtlsidentity.cpp
media/mtransport/dtlsidentity.h
media/mtransport/transportlayerdtls.cpp
media/webrtc/moz.build
media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -383,29 +383,54 @@ RTCPeerConnection.prototype = {
     this.__DOM_IMPL__._innerObject = this;
     this._observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
 
     // Add a reference to the PeerConnection to global list (before init).
     _globalPCList.addPC(this);
 
     this._impl.initialize(this._observer, this._win, rtcConfig,
                           Services.tm.currentThread);
+    this._initCertificate(rtcConfig.certificates);
     this._initIdp();
     _globalPCList.notifyLifecycleObservers(this, "initialized");
   },
 
   get _impl() {
     if (!this._pc) {
       throw new this._win.DOMException(
           "RTCPeerConnection is gone (did you enter Offline mode?)",
           "InvalidStateError");
     }
     return this._pc;
   },
 
+  _initCertificate: function(certificates) {
+    let certPromise;
+    if (certificates && certificates.length > 0) {
+      if (certificates.length > 1) {
+        throw new this._win.DOMException(
+          "RTCPeerConnection does not currently support multiple certificates",
+          "NotSupportedError");
+      }
+      let cert = certificates.find(c => c.expires.getTime() > Date.now());
+      if (!cert) {
+        throw new this._win.DOMException(
+          "Unable to create RTCPeerConnection with an expired certificate",
+          "InvalidParameterError");
+      }
+      certPromise = Promise.resolve(cert);
+    } else {
+      certPromise = this._win.mozRTCPeerConnection.generateCertificate({
+        name: "ECDSA", namedCurve: "P-256"
+      });
+    }
+    this._certificateReady = certPromise
+      .then(cert => this._impl.certificate = cert);
+  },
+
   _initIdp: function() {
     this._peerIdentity = new this._win.Promise((resolve, reject) => {
       this._resolvePeerIdentity = resolve;
       this._rejectPeerIdentity = reject;
     });
     this._lastIdentityValidation = this._win.Promise.resolve();
 
     let prefName = "media.peerconnection.identity.timeout";
@@ -650,57 +675,60 @@ RTCPeerConnection.prototype = {
         this.logWarning(
           "Mandatory/optional in createOffer options is deprecated! Use " +
             JSON.stringify(options) + " instead (note the case difference)!",
           null, 0);
       }
 
       let origin = Cu.getWebIDLCallerPrincipal().origin;
       return this._chain(() => {
-        let p = new this._win.Promise((resolve, reject) => {
-          this._onCreateOfferSuccess = resolve;
-          this._onCreateOfferFailure = reject;
-          this._impl.createOffer(options);
-        });
+        let p = this._certificateReady.then(
+          () => new this._win.Promise((resolve, reject) => {
+            this._onCreateOfferSuccess = resolve;
+            this._onCreateOfferFailure = reject;
+            this._impl.createOffer(options);
+          })
+        );
         p = this._addIdentityAssertion(p, origin);
         return p.then(
           sdp => new this._win.mozRTCSessionDescription({ type: "offer", sdp: sdp }));
       });
     });
   },
 
   createAnswer: function(onSuccess, onError) {
     return this._legacyCatch(onSuccess, onError, () => {
       let origin = Cu.getWebIDLCallerPrincipal().origin;
       return this._chain(() => {
-        let p = new this._win.Promise((resolve, reject) => {
-        // We give up line-numbers in errors by doing this here, but do all
-        // state-checks inside the chain, to support the legacy feature that
-        // callers don't have to wait for setRemoteDescription to finish.
-        if (!this.remoteDescription) {
-          throw new this._win.DOMException("setRemoteDescription not called",
-                                           "InvalidStateError");
-        }
-        if (this.remoteDescription.type != "offer") {
-          throw new this._win.DOMException("No outstanding offer",
-                                           "InvalidStateError");
-        }
-        this._onCreateAnswerSuccess = resolve;
-        this._onCreateAnswerFailure = reject;
-        this._impl.createAnswer();
-        });
+        let p = this._certificateReady.then(
+          () => new this._win.Promise((resolve, reject) => {
+            // We give up line-numbers in errors by doing this here, but do all
+            // state-checks inside the chain, to support the legacy feature that
+            // callers don't have to wait for setRemoteDescription to finish.
+            if (!this.remoteDescription) {
+              throw new this._win.DOMException("setRemoteDescription not called",
+                                               "InvalidStateError");
+            }
+            if (this.remoteDescription.type != "offer") {
+              throw new this._win.DOMException("No outstanding offer",
+                                               "InvalidStateError");
+            }
+            this._onCreateAnswerSuccess = resolve;
+            this._onCreateAnswerFailure = reject;
+            this._impl.createAnswer();
+          })
+        );
         p = this._addIdentityAssertion(p, origin);
         return p.then(sdp => {
           return new this._win.mozRTCSessionDescription({ type: "answer", sdp: sdp });
         });
       });
     });
   },
 
-
   setLocalDescription: function(desc, onSuccess, onError) {
     return this._legacyCatch(onSuccess, onError, () => {
       this._localType = desc.type;
 
       let type;
       switch (desc.type) {
         case "offer":
           type = Ci.IPeerConnection.kActionOffer;
@@ -813,19 +841,21 @@ RTCPeerConnection.prototype = {
 
   setIdentityProvider: function(provider, protocol, username) {
     this._checkClosed();
     this._localIdp.setIdentityProvider(provider, protocol, username);
   },
 
   getIdentityAssertion: function() {
     let origin = Cu.getWebIDLCallerPrincipal().origin;
-    return this._chain(() => {
-      return this._localIdp.getIdentityAssertion(this._impl.fingerprint, origin);
-    });
+    return this._chain(
+      () => this._certificateReady.then(
+        () => this._localIdp.getIdentityAssertion(this._impl.fingerprint, origin)
+      )
+    );
   },
 
   updateIce: function(config) {
     throw new this._win.DOMException("updateIce not yet implemented",
                                      "NotSupportedError");
   },
 
   addIceCandidate: function(c, onSuccess, onError) {
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -89,16 +89,18 @@ skip-if = toolkit == 'gonk' # B2G emulat
 skip-if = buildapp == 'b2g' || buildapp == 'mulet' || os == 'android' # bug 1043403 # Bug 1141029 Mulet parity with B2G Desktop for TC
 [test_peerConnection_capturedVideo.html]
 tags=capturestream
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_captureStream_canvas_2d.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_captureStream_canvas_webgl.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+[test_peerConnection_certificates.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
 [test_peerConnection_close.html]
 [test_peerConnection_closeDuringIce.html]
 [test_peerConnection_errorCallbacks.html]
 [test_peerConnection_iceFailure.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # Disabling because of test failures on B2G emulator
 [test_peerConnection_forwarding_basicAudioVideoCombined.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_noTrickleAnswer.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_certificates.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "1172785",
+    title: "Certificate management"
+  });
+
+  function badCertificate(config, expectedError, message) {
+    return mozRTCPeerConnection.generateCertificate(config)
+      .then(() => ok(false, message),
+            e => is(e.name, expectedError, message));
+  }
+
+  // Checks a handful of obviously bad options to RTCCertificate.create().  Most
+  // of the checking is done by the WebCrypto code underpinning this, hence the
+  // baffling error codes, but a sanity check is still in order.
+  function checkBadParameters() {
+    return Promise.all([
+      badCertificate({
+        name: "RSASSA-PKCS1-v1_5",
+        hash: "SHA-256",
+        modulusLength: 1023,
+        publicExponent: new Uint8Array([1, 0, 1])
+      }, "NotSupportedError", "1023-bit is too small to succeed"),
+
+      badCertificate({
+        name: "ECDH",
+        namedCurve: "P-256"
+      }, "DataError", "otherwise valid ECDH config is rejected"),
+
+      badCertificate({
+        name: "not a valid algorithm"
+      }, "SyntaxError", "not a valid algorithm"),
+
+      badCertificate("ECDSA", "SyntaxError", "a bare name is not enough"),
+
+      badCertificate({
+        name: "ECDSA",
+        namedCurve: "not a curve"
+      }, "NotSupportedError", "ECDSA with an unknown curve")
+    ]);
+  }
+
+  function createDB() {
+    var openDB = indexedDB.open("genericstore");
+    openDB.onupgradeneeded = e => {
+      var db = e.target.result;
+      db.createObjectStore("data");
+    };
+    return new Promise(resolve => {
+      openDB.onsuccess = e => resolve(e.target.result);
+    });
+  }
+
+  function resultPromise(tx, op) {
+    return new Promise((resolve, reject) => {
+      op.onsuccess = e => resolve(e.target.result);
+      op.onerror = () => reject(op.error);
+      tx.onabort = () => reject(tx.error);
+    });
+  }
+
+  function store(db, value) {
+    var tx = db.transaction("data", "readwrite");
+    var store = tx.objectStore("data");
+    return resultPromise(tx, store.put(value, "value"));
+  }
+
+  function retrieve(db) {
+    var tx = db.transaction("data", "readonly");
+    var store = tx.objectStore("data");
+    return resultPromise(tx, store.get("value"));
+  }
+
+  // Creates a database, stores a value, retrieves it.
+  function storeAndRetrieve(value) {
+    return createDB().then(db => {
+      return store(db, value)
+        .then(() => retrieve(db))
+        .then(retrieved => {
+          db.close();
+          return retrieved;
+        });
+    });
+  }
+
+  var test;
+  runNetworkTest(function (options) {
+    var expiredCert;
+    return Promise.resolve()
+      .then(() => mozRTCPeerConnection.generateCertificate({
+        name: "ECDSA",
+        namedCurve: "P-256",
+        expires: 1 // smallest possible expiration window
+      }))
+      .then(cert => {
+        ok(cert.expires instanceof Date, 'cert has expiration time');
+        info('Expires at ' + cert.expires);
+        expiredCert = cert;
+      })
+
+      .then(() => checkBadParameters())
+
+      .then(() => {
+        var delay = expiredCert.expires.getTime() - Date.now();
+        // Hopefully this delay is never needed.
+        if (delay > 0) {
+          return new Promise(r => setTimeout(r, delay));
+        }
+      })
+      .then(() => {
+        ok(expiredCert.expires <= Date.now(), 'Cert should be at or past expiration');
+        try {
+          new mozRTCPeerConnection({ certificates: [expiredCert] });
+          ok(false, 'Constructing peer connection with an expired cert is not allowed');
+        } catch(e) {
+          is(e.name, 'InvalidParameterError',
+             'Constructing peer connection with an expired certs is not allowed');
+        }
+      })
+
+      .then(() => Promise.all([
+        mozRTCPeerConnection.generateCertificate({
+          name: "ECDSA",
+          namedCurve: "P-256"
+        }),
+        mozRTCPeerConnection.generateCertificate({
+          name: "RSASSA-PKCS1-v1_5",
+          hash: "SHA-256",
+          modulusLength: 2048,
+          publicExponent: new Uint8Array([1, 0, 1])
+        })
+      ]))
+
+    // A round trip through indexedDB should not do anything.
+      .then(storeAndRetrieve)
+      .then(certs => {
+        try {
+          new mozRTCPeerConnection({ certificates: certs });
+          ok(false, 'Constructing peer connection with multiple certs is not allowed');
+        } catch(e) {
+          is(e.name, 'NotSupportedError',
+             'Constructing peer connection with multiple certs is not allowed');
+        }
+        return certs;
+      })
+      .then(certs => {
+        test = new PeerConnectionTest({
+          config_local: {
+            certificates: [certs[0]]
+          },
+          config_remote: {
+            certificates: [certs[1]]
+          }
+        });
+        test.setMediaConstraints([{audio: true}], [{audio: true}]);
+        return test.run();
+      })
+      .catch(e => {
+        console.log('test failure', e);
+        ok(false, 'test failed: ' + e);
+      });
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/webrtc/moz.build
+++ b/dom/media/webrtc/moz.build
@@ -54,21 +54,25 @@ if CONFIG['MOZ_WEBRTC']:
 
 XPIDL_SOURCES += [
     'nsITabSource.idl'
 ]
 
 UNIFIED_SOURCES += [
     'MediaEngineDefault.cpp',
     'PeerIdentity.cpp',
+    'RTCCertificate.cpp',
 ]
 
 EXPORTS.mozilla += [
     'PeerIdentity.h',
 ]
+EXPORTS.mozilla.dom += [
+    'RTCCertificate.h',
+]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 # Suppress some GCC/clang warnings being treated as errors:
 #  - about attributes on forward declarations for types that are already
 #    defined, which complains about important MOZ_EXPORT attributes for
 #    android API types
 if CONFIG['GNU_CC'] or CONFIG['CLANG_CL']:
--- a/media/mtransport/dtlsidentity.cpp
+++ b/media/mtransport/dtlsidentity.cpp
@@ -1,38 +1,34 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=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 <iomanip>
-#include "logging.h"
-#include "nspr.h"
+#include "dtlsidentity.h"
+
+#include "cert.h"
 #include "cryptohi.h"
+#include "keyhi.h"
+#include "nsError.h"
+#include "pk11pub.h"
+#include "prprf.h"
+#include "sechash.h"
 #include "ssl.h"
-#include "keyhi.h"
-#include "pk11pub.h"
-#include "sechash.h"
-#include "nsError.h"
-#include "dtlsidentity.h"
 
 namespace mozilla {
 
 DtlsIdentity::~DtlsIdentity() {
-  // XXX: make cert_ a smart pointer to avoid this, after we figure
-  // out the linking problem.
-  if (cert_)
+  if (cert_) {
     CERT_DestroyCertificate(cert_);
+  }
 }
 
-const std::string DtlsIdentity::DEFAULT_HASH_ALGORITHM = "sha-256";
-
-already_AddRefed<DtlsIdentity> DtlsIdentity::Generate() {
-
+RefPtr<DtlsIdentity> DtlsIdentity::Generate() {
   ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
   if (!slot) {
     return nullptr;
   }
 
   uint8_t random_name[16];
 
   SECStatus rv = PK11_GenerateRandomOnSlot(slot, random_name,
@@ -48,28 +44,34 @@ already_AddRefed<DtlsIdentity> DtlsIdent
   }
 
   std::string subject_name_string = "CN=" + name;
   ScopedCERTName subject_name(CERT_AsciiToName(subject_name_string.c_str()));
   if (!subject_name) {
     return nullptr;
   }
 
-  PK11RSAGenParams rsaparams;
-  rsaparams.keySizeInBits = 1024; // TODO: make this stronger when we
-                                  // pre-generate.
-  rsaparams.pe = 65537; // We are too paranoid to use 3 as the exponent.
+  unsigned char paramBuf[12]; // OIDs are small
+  SECItem ecdsaParams = { siBuffer, paramBuf, sizeof(paramBuf) };
+  SECOidData* oidData = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1);
+  if (!oidData || (oidData->oid.len > (sizeof(paramBuf) - 2))) {
+    return nullptr;
+  }
+  ecdsaParams.data[0] = SEC_ASN1_OBJECT_ID;
+  ecdsaParams.data[1] = oidData->oid.len;
+  memcpy(ecdsaParams.data + 2, oidData->oid.data, oidData->oid.len);
+  ecdsaParams.len = oidData->oid.len + 2;
 
   ScopedSECKEYPrivateKey private_key;
   ScopedSECKEYPublicKey public_key;
   SECKEYPublicKey *pubkey;
 
   private_key =
       PK11_GenerateKeyPair(slot,
-                           CKM_RSA_PKCS_KEY_PAIR_GEN, &rsaparams, &pubkey,
+                           CKM_EC_KEY_PAIR_GEN, &ecdsaParams, &pubkey,
                            PR_FALSE, PR_TRUE, nullptr);
   if (private_key == nullptr)
     return nullptr;
   public_key = pubkey;
 
   ScopedCERTSubjectPublicKeyInfo spki(
       SECKEY_CreateSubjectPublicKeyInfo(pubkey));
   if (!spki) {
@@ -115,17 +117,17 @@ already_AddRefed<DtlsIdentity> DtlsIdent
       CERT_CreateCertificate(serial, subject_name, validity, certreq));
   if (!certificate) {
     return nullptr;
   }
 
   PLArenaPool *arena = certificate->arena;
 
   rv = SECOID_SetAlgorithmID(arena, &certificate->signature,
-                             SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, 0);
+                             SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, 0);
   if (rv != SECSuccess)
     return nullptr;
 
   // Set version to X509v3.
   *(certificate->version.data) = SEC_CERTIFICATE_VERSION_3;
   certificate->version.len = 1;
 
   SECItem innerDER;
@@ -139,42 +141,44 @@ already_AddRefed<DtlsIdentity> DtlsIdent
 
   SECItem *signedCert = PORT_ArenaZNew(arena, SECItem);
   if (!signedCert) {
     return nullptr;
   }
 
   rv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len,
                        private_key,
-                       SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION);
+                       SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
   if (rv != SECSuccess) {
     return nullptr;
   }
   certificate->derCert = *signedCert;
 
   RefPtr<DtlsIdentity> identity =
-    new DtlsIdentity(private_key.forget(), certificate.forget());
+      new DtlsIdentity(private_key.forget(), certificate.forget(), ssl_kea_ecdh);
   return identity.forget();
 }
 
+const std::string DtlsIdentity::DEFAULT_HASH_ALGORITHM = "sha-256";
 
 nsresult DtlsIdentity::ComputeFingerprint(const std::string algorithm,
-                                          unsigned char *digest,
-                                          std::size_t size,
-                                          std::size_t *digest_length) {
-  MOZ_ASSERT(cert_);
+                                          uint8_t *digest,
+                                          size_t size,
+                                          size_t *digest_length) const {
+  const CERTCertificate* c = cert();
+  MOZ_ASSERT(c);
 
-  return ComputeFingerprint(cert_, algorithm, digest, size, digest_length);
+  return ComputeFingerprint(c, algorithm, digest, size, digest_length);
 }
 
 nsresult DtlsIdentity::ComputeFingerprint(const CERTCertificate *cert,
                                           const std::string algorithm,
-                                          unsigned char *digest,
-                                          std::size_t size,
-                                          std::size_t *digest_length) {
+                                          uint8_t *digest,
+                                          size_t size,
+                                          size_t *digest_length) {
   MOZ_ASSERT(cert);
 
   HASH_HashType ht;
 
   if (algorithm == "sha-1") {
     ht = HASH_AlgSHA1;
   } else if (algorithm == "sha-224") {
     ht = HASH_AlgSHA224;
@@ -185,28 +189,31 @@ nsresult DtlsIdentity::ComputeFingerprin
   }  else if (algorithm == "sha-512") {
     ht = HASH_AlgSHA512;
   } else {
     return NS_ERROR_INVALID_ARG;
   }
 
   const SECHashObject *ho = HASH_GetHashObject(ht);
   MOZ_ASSERT(ho);
-  if (!ho)
+  if (!ho) {
     return NS_ERROR_INVALID_ARG;
+  }
 
   MOZ_ASSERT(ho->length >= 20);  // Double check
 
-  if (size < ho->length)
+  if (size < ho->length) {
     return NS_ERROR_INVALID_ARG;
+  }
 
   SECStatus rv = HASH_HashBuf(ho->type, digest,
                               cert->derCert.data,
                               cert->derCert.len);
-  if (rv != SECSuccess)
+  if (rv != SECSuccess) {
     return NS_ERROR_FAILURE;
+  }
 
   *digest_length = ho->length;
 
   return NS_OK;
 }
 
 }  // close namespace
--- a/media/mtransport/dtlsidentity.h
+++ b/media/mtransport/dtlsidentity.h
@@ -6,58 +6,66 @@
 #ifndef dtls_identity_h__
 #define dtls_identity_h__
 
 #include <string>
 
 #include "m_cpp_utils.h"
 #include "mozilla/RefPtr.h"
 #include "nsISupportsImpl.h"
+#include "sslt.h"
 #include "ScopedNSSTypes.h"
 
 // All code in this module requires NSS to be live.
 // Callers must initialize NSS and implement the nsNSSShutdownObject
 // protocol.
 namespace mozilla {
 
-class DtlsIdentity {
- private:
-  ~DtlsIdentity();
-
+class DtlsIdentity final {
  public:
-  // Generate an identity with a random name.
-  static already_AddRefed<DtlsIdentity> Generate();
+  // This constructor takes ownership of privkey and cert.
+  DtlsIdentity(SECKEYPrivateKey *privkey,
+               CERTCertificate *cert,
+               SSLKEAType authType)
+      : private_key_(privkey), cert_(cert), auth_type_(authType) {}
+
+  // This is only for use in tests, or for external linkage.  It makes a (bad)
+  // instance of this class.
+  static RefPtr<DtlsIdentity> Generate();
 
-  // Note: the following two functions just provide access. They
-  // do not transfer ownership. If you want a pointer that lasts
-  // past the lifetime of the DtlsIdentity, you must make
-  // a copy yourself.
-  CERTCertificate *cert() { return cert_; }
-  SECKEYPrivateKey *privkey() { return privkey_; }
+  // These don't create copies or transfer ownership. If you want these to live
+  // on, make a copy.
+  CERTCertificate *cert() const { return cert_; }
+  SECKEYPrivateKey *privkey() const { return private_key_; }
+  // Note: this uses SSLKEAType because that is what the libssl API requires.
+  // This is a giant confusing mess, but libssl indexes certificates based on a
+  // key exchange type, not authentication type (as you might have reasonably
+  // expected).
+  SSLKEAType auth_type() const { return auth_type_; }
 
   nsresult ComputeFingerprint(const std::string algorithm,
-                              unsigned char *digest,
-                              std::size_t size,
-                              std::size_t *digest_length);
-
+                              uint8_t *digest,
+                              size_t size,
+                              size_t *digest_length) const;
   static nsresult ComputeFingerprint(const CERTCertificate *cert,
                                      const std::string algorithm,
-                                     unsigned char *digest,
-                                     std::size_t size,
-                                     std::size_t *digest_length);
+                                     uint8_t *digest,
+                                     size_t size,
+                                     size_t *digest_length);
+
   static const std::string DEFAULT_HASH_ALGORITHM;
   enum {
     HASH_ALGORITHM_MAX_LENGTH = 64
   };
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DtlsIdentity)
 
-private:
-  DtlsIdentity(SECKEYPrivateKey *privkey, CERTCertificate *cert)
-      : privkey_(privkey), cert_(cert) {}
+ private:
+  ~DtlsIdentity();
   DISALLOW_COPY_ASSIGN(DtlsIdentity);
 
-  ScopedSECKEYPrivateKey privkey_;
+  ScopedSECKEYPrivateKey private_key_;
   CERTCertificate *cert_;  // TODO: Using a smart pointer here causes link
                            // errors.
+  SSLKEAType auth_type_;
 };
 }  // close namespace
 #endif
--- a/media/mtransport/transportlayerdtls.cpp
+++ b/media/mtransport/transportlayerdtls.cpp
@@ -492,17 +492,17 @@ bool TransportLayerDtls::Setup() {
       MOZ_MTLOG(ML_ERROR, "Couldn't set identity");
       return false;
     }
   } else {
     MOZ_MTLOG(ML_DEBUG, "Setting up DTLS as server");
     // Server side
     rv = SSL_ConfigSecureServer(ssl_fd, identity_->cert(),
                                 identity_->privkey(),
-                                kt_rsa);
+                                identity_->auth_type());
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_ERROR, "Couldn't set identity");
       return false;
     }
 
     // Insist on a certificate from the client
     rv = SSL_OptionSet(ssl_fd, SSL_REQUEST_CERTIFICATE, PR_TRUE);
     if (rv != SECSuccess) {
--- a/media/webrtc/moz.build
+++ b/media/webrtc/moz.build
@@ -103,9 +103,8 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk
         GYP_DIRS += ['signalingstandalone']
         GYP_DIRS['signalingstandalone'].input = 'signaling/signaling.gyp'
         GYP_DIRS['signalingstandalone'].variables = gyp_vars.copy()
         GYP_DIRS['signalingstandalone'].variables.update(
             build_for_test=0,
             build_for_standalone=1
         )
         GYP_DIRS['signalingstandalone'].non_unified_sources += signaling_non_unified_sources
-
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -172,17 +172,17 @@ MediaPipelineFactory::CreateOrGetTranspo
   // The media streams are made on STS so we need to defer setup.
   auto ice = MakeUnique<TransportLayerIce>(mPC->GetHandle());
   auto dtls = MakeUnique<TransportLayerDtls>();
   dtls->SetRole(aTransport.mDtls->GetRole() ==
                         JsepDtlsTransport::kJsepDtlsClient
                     ? TransportLayerDtls::CLIENT
                     : TransportLayerDtls::SERVER);
 
-  RefPtr<DtlsIdentity> pcid = mPC->GetIdentity();
+  RefPtr<DtlsIdentity> pcid = mPC->Identity();
   if (!pcid) {
     MOZ_MTLOG(ML_ERROR, "Failed to get DTLS identity.");
     return NS_ERROR_FAILURE;
   }
   dtls->SetIdentity(pcid);
 
   const SdpFingerprintAttributeList& fingerprints =
       aTransport.mDtls->GetFingerprints();
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -69,16 +69,17 @@
 #include "nsIScriptError.h"
 #include "nsPrintfCString.h"
 #include "nsURLHelper.h"
 #include "nsNetUtil.h"
 #include "nsIDOMDataChannel.h"
 #include "nsIDOMLocation.h"
 #include "nsNullPrincipal.h"
 #include "mozilla/PeerIdentity.h"
+#include "mozilla/dom/RTCCertificate.h"
 #include "mozilla/dom/RTCConfigurationBinding.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
 #include "mozilla/dom/RTCPeerConnectionBinding.h"
 #include "mozilla/dom/PeerConnectionImplBinding.h"
 #include "mozilla/dom/DataChannelBinding.h"
 #include "mozilla/dom/PluginCrashedEvent.h"
 #include "MediaStreamList.h"
 #include "MediaStreamTrack.h"
@@ -367,17 +368,21 @@ bool PCUuidGenerator::Generate(std::stri
 PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal)
 : mTimeCard(MOZ_LOG_TEST(signalingLogInfo(),LogLevel::Error) ?
             create_timecard() : nullptr)
   , mSignalingState(PCImplSignalingState::SignalingStable)
   , mIceConnectionState(PCImplIceConnectionState::New)
   , mIceGatheringState(PCImplIceGatheringState::New)
   , mDtlsConnected(false)
   , mWindow(nullptr)
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  , mCertificate(nullptr)
+#else
   , mIdentity(nullptr)
+#endif
   , mPrivacyRequested(false)
   , mSTSThread(nullptr)
   , mAllowIceLoopback(false)
   , mAllowIceLinkLocal(false)
   , mMedia(nullptr)
   , mUuidGen(MakeUnique<PCUuidGenerator>())
   , mNumAudioStreams(0)
   , mNumVideoStreams(0)
@@ -419,27 +424,16 @@ PeerConnectionImpl::~PeerConnectionImpl(
     CSFLogError(logTag, "PeerConnectionCtx is already gone. Ignoring...");
   }
 
   CSFLogInfo(logTag, "%s: PeerConnectionImpl destructor invoked for %s",
              __FUNCTION__, mHandle.c_str());
 
   Close();
 
-#if !defined(MOZILLA_EXTERNAL_LINKAGE)
-  {
-    // Deregister as an NSS Shutdown Object
-    nsNSSShutDownPreventionLock locker;
-    if (!isAlreadyShutDown()) {
-      destructorSafeDestroyNSSReference();
-      shutdown(calledFromObject);
-    }
-  }
-#endif
-
   // Since this and Initialize() occur on MainThread, they can't both be
   // running at once
 
   // Right now, we delete PeerConnectionCtx at XPCOM shutdown only, but we
   // probably want to shut it down more aggressively to save memory.  We
   // could shut down here when there are no uses.  It might be more optimal
   // to release off a timer (and XPCOM Shutdown) to avoid churn
 }
@@ -746,26 +740,16 @@ PeerConnectionImpl::Initialize(PeerConne
                      aConfiguration.getTurnServers());
   if (NS_FAILED(res)) {
     CSFLogError(logTag, "%s: Couldn't initialize media object", __FUNCTION__);
     return res;
   }
 
   PeerConnectionCtx::GetInstance()->mPeerConnections[mHandle] = this;
 
-  STAMP_TIMECARD(mTimeCard, "Generating DTLS Identity");
-  // Create the DTLS Identity
-  mIdentity = DtlsIdentity::Generate();
-  STAMP_TIMECARD(mTimeCard, "Done Generating DTLS Identity");
-
-  if (!mIdentity) {
-    CSFLogError(logTag, "%s: Generate returned NULL", __FUNCTION__);
-    return NS_ERROR_FAILURE;
-  }
-
   mJsepSession = MakeUnique<JsepSessionImpl>(mName,
                                              MakeUnique<PCUuidGenerator>());
 
   res = mJsepSession->Init();
   if (NS_FAILED(res)) {
     CSFLogError(logTag, "%s: Couldn't init JSEP Session, res=%u",
                         __FUNCTION__,
                         static_cast<unsigned>(res));
@@ -776,27 +760,33 @@ PeerConnectionImpl::Initialize(PeerConne
                                         mMedia->ice_ctx()->pwd());
   if (NS_FAILED(res)) {
     CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u",
                          __FUNCTION__,
                          static_cast<unsigned>(res));
     return res;
   }
 
-  const std::string& fpAlg = DtlsIdentity::DEFAULT_HASH_ALGORITHM;
-  std::vector<uint8_t> fingerprint;
-  res = CalculateFingerprint(fpAlg, fingerprint);
-  NS_ENSURE_SUCCESS(res, res);
-  res = mJsepSession->AddDtlsFingerprint(fpAlg, fingerprint);
-  if (NS_FAILED(res)) {
-    CSFLogError(logTag, "%s: Couldn't set DTLS credentials, res=%u",
-                        __FUNCTION__,
-                        static_cast<unsigned>(res));
-    return res;
+#if defined(MOZILLA_EXTERNAL_LINKAGE)
+  {
+    mIdentity = DtlsIdentity::Generate();
+    if (!mIdentity) {
+      return NS_ERROR_FAILURE;
+    }
+
+    std::vector<uint8_t> fingerprint;
+    res = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM,
+                               &fingerprint);
+    NS_ENSURE_SUCCESS(res, res);
+
+    res = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM,
+                                           fingerprint);
+    NS_ENSURE_SUCCESS(res, res);
   }
+#endif
 
   res = mJsepSession->SetBundlePolicy(aConfiguration.getBundlePolicy());
   if (NS_FAILED(res)) {
     CSFLogError(logTag, "%s: Couldn't set bundle policy, res=%u, error=%s",
                         __FUNCTION__,
                         static_cast<unsigned>(res),
                         mJsepSession->GetLastError().c_str());
     return res;
@@ -828,16 +818,63 @@ PeerConnectionImpl::Initialize(PeerConne
 
   if (!aConfiguration.mPeerIdentity.IsEmpty()) {
     mPeerIdentity = new PeerIdentity(aConfiguration.mPeerIdentity);
     mPrivacyRequested = true;
   }
 }
 #endif
 
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+void
+PeerConnectionImpl::SetCertificate(mozilla::dom::RTCCertificate& aCertificate)
+{
+  PC_AUTO_ENTER_API_CALL_NO_CHECK();
+  MOZ_ASSERT(!mCertificate, "This can only be called once");
+  mCertificate = &aCertificate;
+
+  std::vector<uint8_t> fingerprint;
+  nsresult rv = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM,
+                                     &fingerprint);
+  if (NS_FAILED(rv)) {
+    CSFLogError(logTag, "%s: Couldn't calculate fingerprint, rv=%u",
+                __FUNCTION__, static_cast<unsigned>(rv));
+    mCertificate = nullptr;
+    return;
+  }
+  rv = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM,
+                                        fingerprint);
+  if (NS_FAILED(rv)) {
+    CSFLogError(logTag, "%s: Couldn't set DTLS credentials, rv=%u",
+                __FUNCTION__, static_cast<unsigned>(rv));
+    mCertificate = nullptr;
+  }
+}
+
+const nsRefPtr<mozilla::dom::RTCCertificate>&
+PeerConnectionImpl::Certificate() const
+{
+  PC_AUTO_ENTER_API_CALL_NO_CHECK();
+  return mCertificate;
+}
+#endif
+
+mozilla::RefPtr<DtlsIdentity>
+PeerConnectionImpl::Identity() const
+{
+  PC_AUTO_ENTER_API_CALL_NO_CHECK();
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  MOZ_ASSERT(mCertificate);
+  return mCertificate->CreateDtlsIdentity();
+#else
+  mozilla::RefPtr<DtlsIdentity> id = mIdentity;
+  return id;
+#endif
+}
+
 class CompareCodecPriority {
   public:
     void SetPreferredCodec(int32_t preferredCodec) {
       // This pref really ought to be a string, preferably something like
       // "H264" or "VP8" instead of a payload type.
       // Bug 1101259.
       std::ostringstream os;
       os << preferredCodec;
@@ -1020,23 +1057,16 @@ PeerConnectionImpl::ConfigureJsepSession
     comparator.SetPreferredCodec(preferredCodec);
   }
 
   std::stable_sort(codecs.begin(), codecs.end(), comparator);
 #endif // !defined(MOZILLA_XPCOMRT_API)
   return NS_OK;
 }
 
-RefPtr<DtlsIdentity> const
-PeerConnectionImpl::GetIdentity() const
-{
-  PC_AUTO_ENTER_API_CALL_NO_CHECK();
-  return mIdentity;
-}
-
 // Data channels won't work without a window, so in order for the C++ unit
 // tests to work (it doesn't have a window available) we ifdef the following
 // two implementations.
 NS_IMETHODIMP
 PeerConnectionImpl::EnsureDataConnection(uint16_t aNumstreams)
 {
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
 
@@ -2201,38 +2231,49 @@ PeerConnectionImpl::ReplaceTrack(MediaSt
   }
 
   return NS_OK;
 }
 
 nsresult
 PeerConnectionImpl::CalculateFingerprint(
     const std::string& algorithm,
-    std::vector<uint8_t>& fingerprint) const {
+    std::vector<uint8_t>* fingerprint) const {
   uint8_t buf[DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH];
   size_t len = 0;
-  nsresult rv = mIdentity->ComputeFingerprint(algorithm, &buf[0], sizeof(buf),
-                                              &len);
+  CERTCertificate* cert;
+
+  MOZ_ASSERT(fingerprint);
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  cert = mCertificate->Certificate();
+#else
+  cert = mIdentity->cert();
+#endif
+  nsresult rv = DtlsIdentity::ComputeFingerprint(cert, algorithm,
+                                                 &buf[0], sizeof(buf),
+                                                 &len);
   if (NS_FAILED(rv)) {
     CSFLogError(logTag, "Unable to calculate certificate fingerprint, rv=%u",
                         static_cast<unsigned>(rv));
     return rv;
   }
   MOZ_ASSERT(len > 0 && len <= DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH);
-  fingerprint.assign(buf, buf + len);
+  fingerprint->assign(buf, buf + len);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::GetFingerprint(char** fingerprint)
 {
   MOZ_ASSERT(fingerprint);
-  MOZ_ASSERT(mIdentity);
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  MOZ_ASSERT(mCertificate);
+#endif
   std::vector<uint8_t> fp;
-  nsresult rv = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, fp);
+  nsresult rv = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fp);
   NS_ENSURE_SUCCESS(rv, rv);
   std::ostringstream os;
   os << DtlsIdentity::DEFAULT_HASH_ALGORITHM << ' '
      << SdpFingerprintAttributeList::FormatFingerprint(fp);
   std::string fpStr = os.str();
 
   char* tmp = new char[fpStr.size() + 1];
   std::copy(fpStr.begin(), fpStr.end(), tmp);
@@ -2422,35 +2463,16 @@ PeerConnectionImpl::ShutdownMedia()
   }
 #endif
 
   // Forget the reference so that we can transfer it to
   // SelfDestruct().
   mMedia.forget().take()->SelfDestruct();
 }
 
-#if !defined(MOZILLA_EXTERNAL_LINKAGE)
-// If NSS is shutting down, then we need to get rid of the DTLS
-// identity right now; otherwise, we'll cause wreckage when we do
-// finally deallocate it in our destructor.
-void
-PeerConnectionImpl::virtualDestroyNSSReference()
-{
-  destructorSafeDestroyNSSReference();
-}
-
-void
-PeerConnectionImpl::destructorSafeDestroyNSSReference()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  CSFLogDebug(logTag, "%s: NSS shutting down; freeing our DtlsIdentity.", __FUNCTION__);
-  mIdentity = nullptr;
-}
-#endif
-
 void
 PeerConnectionImpl::SetSignalingState_m(PCImplSignalingState aSignalingState,
                                         bool rollback)
 {
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
   if (mSignalingState == aSignalingState ||
       mSignalingState == PCImplSignalingState::SignalingClosed) {
     return;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -33,17 +33,16 @@
 #include "mozilla/dom/PeerConnectionImplEnumsBinding.h"
 #include "StreamBuffer.h"
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
 #include "mozilla/TimeStamp.h"
 #include "mozilla/net/DataChannel.h"
 #include "VideoUtils.h"
 #include "VideoSegment.h"
-#include "nsNSSShutDown.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
 #include "nsIPrincipal.h"
 #include "mozilla/PeerIdentity.h"
 #ifndef USE_FAKE_MEDIA_STREAMS
 #include "DOMMediaStream.h"
 #endif
 #endif
 
@@ -73,16 +72,17 @@ class MediaPipeline;
 #ifdef USE_FAKE_MEDIA_STREAMS
 typedef Fake_DOMMediaStream DOMMediaStream;
 typedef Fake_MediaStreamTrack MediaStreamTrack;
 #else
 class DOMMediaStream;
 #endif
 
 namespace dom {
+class RTCCertificate;
 struct RTCConfiguration;
 struct RTCIceServer;
 struct RTCOfferOptions;
 #ifdef USE_FAKE_MEDIA_STREAMS
 typedef Fake_MediaStreamTrack MediaStreamTrack;
 #else
 class MediaStreamTrack;
 #endif
@@ -237,17 +237,16 @@ class RTCStatsQuery {
       nsresult res = CheckApiState(assert_ice_ready);             \
       if (NS_FAILED(res)) return; \
     } while(0)
 #define PC_AUTO_ENTER_API_CALL_NO_CHECK() CheckThread()
 
 class PeerConnectionImpl final : public nsISupports,
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
                                  public mozilla::DataChannelConnection::DataConnectionListener,
-                                 public nsNSSShutDownObject,
                                  public DOMMediaStream::PrincipalChangeObserver,
 #endif
                                  public sigslot::has_slots<>
 {
   struct Internal; // Avoid exposing c includes to bindings
 
 public:
   explicit PeerConnectionImpl(const mozilla::dom::GlobalObject* aGlobal = nullptr);
@@ -328,19 +327,16 @@ public:
   }
 
   // Get the STS thread
   nsCOMPtr<nsIEventTarget> GetSTSThread() {
     PC_AUTO_ENTER_API_CALL_NO_CHECK();
     return mSTSThread;
   }
 
-  // Get the DTLS identity (local side)
-  mozilla::RefPtr<DtlsIdentity> const GetIdentity() const;
-
   nsPIDOMWindow* GetWindow() const {
     PC_AUTO_ENTER_API_CALL_NO_CHECK();
     return mWindow;
   }
 
   // Initialize PeerConnection from a PeerConnectionConfiguration object
   // (used directly by unit-tests, and indirectly by the JS entry point)
   // This is necessary because RTCConfiguration can't be used by unit-tests
@@ -353,16 +349,23 @@ public:
   // Initialize PeerConnection from an RTCConfiguration object (JS entrypoint)
   void Initialize(PeerConnectionObserver& aObserver,
                   nsGlobalWindow& aWindow,
                   const RTCConfiguration& aConfiguration,
                   nsISupports* aThread,
                   ErrorResult &rv);
 #endif
 
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  void SetCertificate(mozilla::dom::RTCCertificate& aCertificate);
+  const nsRefPtr<mozilla::dom::RTCCertificate>& Certificate() const;
+#endif
+  // This is a hack to support external linkage.
+  mozilla::RefPtr<DtlsIdentity> Identity() const;
+
   NS_IMETHODIMP_TO_ERRORRESULT(CreateOffer, ErrorResult &rv,
                                const RTCOfferOptions& aOptions)
   {
     rv = CreateOffer(aOptions);
   }
 
   NS_IMETHODIMP CreateAnswer();
   void CreateAnswer(ErrorResult &rv)
@@ -615,17 +618,17 @@ public:
   static std::string GetStreamId(const DOMMediaStream& aStream);
   static std::string GetTrackId(const dom::MediaStreamTrack& track);
 
 private:
   virtual ~PeerConnectionImpl();
   PeerConnectionImpl(const PeerConnectionImpl&rhs);
   PeerConnectionImpl& operator=(PeerConnectionImpl);
   nsresult CalculateFingerprint(const std::string& algorithm,
-                                std::vector<uint8_t>& fingerprint) const;
+                                std::vector<uint8_t>* fingerprint) const;
   nsresult ConfigureJsepSessionCodecs();
 
   NS_IMETHODIMP EnsureDataConnection(uint16_t aNumstreams);
 
   nsresult CloseInt();
   nsresult CheckApiState(bool assert_ice_ready) const;
   void CheckThread() const {
     MOZ_ASSERT(CheckThreadInt(), "Wrong thread");
@@ -640,18 +643,16 @@ private:
     bool on;
     NS_ENSURE_SUCCESS(mThread->IsOnCurrentThread(&on), false);
     NS_ENSURE_TRUE(on, false);
 #endif
     return true;
   }
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
-  void virtualDestroyNSSReference() final;
-  void destructorSafeDestroyNSSReference();
   nsresult GetTimeSinceEpoch(DOMHighResTimeStamp *result);
 #endif
 
   // Shut down media - called on main thread only
   void ShutdownMedia();
 
   void CandidateReady(const std::string& candidate, uint16_t level);
   void SendLocalIceCandidateToContent(uint16_t level,
@@ -708,21 +709,24 @@ private:
   std::string mLocalRequestedSDP;
   std::string mRemoteRequestedSDP;
 
   // DTLS fingerprint
   std::string mFingerprint;
   std::string mRemoteFingerprint;
 
   // identity-related fields
-  mozilla::RefPtr<DtlsIdentity> mIdentity;
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   // The entity on the other end of the peer-to-peer connection;
   // void if they are not yet identified, and no identity setting has been set
   nsAutoPtr<PeerIdentity> mPeerIdentity;
+  // The certificate we are using.
+  nsRefPtr<mozilla::dom::RTCCertificate> mCertificate;
+#else
+  mozilla::RefPtr<DtlsIdentity> mIdentity;
 #endif
   // Whether an app should be prevented from accessing media produced by the PC
   // If this is true, then media will not be sent until mPeerIdentity matches
   // local streams PeerIdentity; and remote streams are protected from content
   //
   // This can be false if mPeerIdentity is set, in the case where identity is
   // provided, but the media is not protected from the app on either side
   bool mPrivacyRequested;