Some questions about generateCertificate()

At the ORTC CG meeting, there was some brief discussion about how generateCertificate (added in the recent WebRTC 1.0 Editor's draft) should be integrated into the ORTC API.  I believe those discussions centered on several questions: 

a. What object is the generateCertificate() API attached to? 
b. Should implementation of the Certificate Management API be mandatory or optional in ORTC (it is optional in WebRTC 1.0)? 
c. How solid is the Certificate Management API? 

On question a), Peter Thatcher suggested having generateCertificate be a static method on the RTCDtlsTransport object, so that it would look like this: 


partial interface RTCDtlsTransport : RTCStatsProvider {
    static Promise<RTCCertificate> generateCertificate (AlgorithmIdentifier keygenAlgorithm);
};

In contrast, Robin Raymond suggested that it be a standalone object, like this: 

interface RTCCertificate {
    readonly    attribute Date               expires;
    static Promise<RTCCertificate> generateCertificate (AlgorithmIdentifier keygenAlgorithm);
};

On question b), Robin's suggestion was it be mandatory, since DTLS forking cannot be supported without certificate management, and forking is a core goal of the ORTC API.   However, Peter requested that sample code be worked through for both alternatives. 

In terms of sample code, with the certificate management API, we have a Promise for generateCertificate(), whereas without it, we need a Promise for dtlsTransport.getLocalParameters().   So the complexity of sample code is pretty similar from that perspective.  However, without generateCertificate() the sample code examples illustrating DTLS forking do not function without some alternative approach such as a cloneDtlsTransport() method that people felt would be pretty yucky.  

If the certificate management API becomes mandatory, there is also the question of how certificates are represented in the ORTC API.  For example, the May Editor's draft has: 

partial interface RTCDtlsTransport : RTCStatsProvider {
    sequence<ArrayBuffer> getRemoteCertificates ();
};

If the Certificate Management becomes mandatory, should this become: 

partial interface RTCDtlsTransport : RTCStatsProvider {
    sequence<RTCCertificate> getRemoteCertificates ();
};

On question c), attempts to write sample code revealed several issues with the Certificate Management API: 

a) AlgorithmIdentifier doesn't provide enough information to cover all aspects of certificate creation.  For example, there is not only the keygenAlgorithm, but also the fingerprint algorithm, the key length, etc.  So the API cu4rrently doesn't seem to provide enough information to generate the desired certificate. 
b) The RTCCertificate interface currently only provides for the duration, but there are other potentially useful attributes, such as the fingerprint that might be needed. This came up in the example below where we are trying to handle forking with both ICE and DTLS.  While it makes sense to create a pool of IceTransports, this does not necessarily make sense for DtlsTransports since there is no easy way to locate the DtlsTransport corresponding to an IceTransport returned by getOrCreateTransport().  So instead of creating a pool of DtlsTransport objects only to have to wade through them to find the right one, it is easier to create the DtlsTransport on the fly.  In doing things that way, it is nice to have a fingerprint attribute within RTCCertificate.  
c) When using the certificate management API RTP and RTCP DtlsTransports have the same certificates and fingerprints, so there is no need to signal both of them. 

Example 6

// This is an example of  how to offer ICE and DTLS parameters and 
// ICE candidates and get back ICE and DTLS parameters and ICE candidates,
// and start both ICE and DTLS, when RTP and RTCP are multiplexed. 
// Assume that we have a way to signal (mySignaller). 
// Include some helper functions
import "helper.js";
function initiate(mySignaller) {
  // Prepare the ICE gatherer
  var gatherOptions = new RTCIceGatherOptions();
  gatherOptions.gatherPolicy = RTCIceGatherPolicy.all; 
  gatherOptions.iceservers = [ { urls: "stun:stun1.example.net" } , { urls:"turn:turn.example.org", username: "user", credential:"myPassword"} ]; 
  var iceGatherer = new RTCIceGatherer(gatherOptions); 
  iceGatherer.onlocalcandidate = function(event) {
    mySignaller.mySendLocalCandidate(event.candidate);
  }; 
  // Initialize the ICE transport array
  var iceTransports = [];
  var dtlsTransports = []; 
  for (var i=0; i < 10; i++){
    iceTransports.push(new RTCIceTransport(iceGatherer)); 
  } 
  // Create the certificate array
  var certs = [];
  var dtlsParameters = new RTCDtlsParameters(); 
  var keygenAlgorithm = .... ; 
  RTCCertificate.generateCertificate(keygenAlgorithm).then(function(certificate){ 
    certs.push(certificate);
    // Obtain the fingerprint of the created certificate
    dtlsParameters.fingerprints[0] = certificate.fingerprint; 
  }, function(){
    trace('Certificate could not be created'); 
  }); 
  // Prepare to handle remote ICE candidates
  mySignaller.onRemoteCandidate = function(remote){
    // Retrieve the IceTransport corresponding to the remote usernameFragment
    var transport = iceGatherer.getOrCreateTransport(remote.parameters.usernameFragment);
    // Add the candidate to that IceTransport
    transport.addRemoteCandidate(remote.candidate); 
  }; 
  // ... create RtpSender/RtpReceiver objects as illustrated in Section 6.4 Example 7. 

  mySignaller.mySendInitiate({
    "ice": iceGatherer.getLocalParameters(), 
    "dtls": dtlsParameters, 
    // ... marshall RtpSender/RtpReceiver capabilities as illustrated in Section 6.4 Example 7. 
  }, function(remote) {
    // Start the ICE and DTLS transports
    var iceTransport = iceGatherer.getOrCreateTransport(remote.parameters.usernameFragment); 
    var i = dtlsTransports.push(new RTCDtlsTransport(iceTransport, certs)) - 1; 
    iceTransport.start(iceGatherer, remote.ice);
    dtlsTransports[i].start(remote.dtls);
    // ... configure RtpSender/RtpReceiver objects as illustrated in Section 6.4 Example 7.
  });
}
    


Example 7

// This is an example of how to answer with ICE and DTLS
// and DTLS parameters and ICE candidates and start both ICE and DTLS,
// assuming that RTP and RTCP are multiplexed.
// Include some helper functions
import "helper.js";
// Assume that remote info is signalled to us. 
function accept(mySignaller, remote) {
  var gatherOptions = new RTCIceGatherOptions();
  gatherOptions.gatherPolicy = RTCIceGatherPolicy.all;
  gatherOptions.iceservers = [ { urls: "stun:stun1.example.net" } , { urls:"turn:turn.example.org", username: "user", credential:"myPassword"} ] ;
  var iceGatherer = new RTCIceGatherer(gatherOptions);
  iceGatherer.onlocalcandidate = function(event) {
    mySignaller.mySendLocalCandidate(event.candidate);
  };
  // Create certs array
  var certs = [];  
  var keygenAlgorithm = ... ;
  RTCCertificate.generateCertificate(keygenAlgorithm).then(function(certificate){
    certs.push(certificate); 
    // Create ICE and DTLS transports
    var ice = new RTCIceTransport(iceGatherer);
    var dtls = new RTCDtlsTransport(ice, certs);
    // Prepare to handle remote candidates
  }, function(){
    trace('Certificate could not be created'); 
  }); 
  mySignaller.onRemoteCandidate = function(remote) {
    ice.addRemoteCandidate(remote.candidate);
  }; 
  // ... create RtpSender/RtpReceiver objects as illustrated in Section 6.4 Example 7.

  mySignaller.mySendAccept({
    "ice": iceGatherer.getLocalParameters(),
    "dtls": dtls.getLocalParameters()
    // ... marshall RtpSender/RtpReceiver capabilities as illustrated in Section 6.4 Example 7.
  });
  // Start the ICE transport with an implicit gather policy of "all"
  ice.start(iceGatherer, remote.ice, RTCIceRole.controlled);
  // Start the DTLS transport
  dtls.start(remote.dtls);
  // ... configure RtpSender/RtpReceiver objects as illustrated in Section 6.4 Example 7.
}

Received on Friday, 3 July 2015 20:23:40 UTC