| <!doctype html> |
| <meta charset="utf-8"> |
| <title>RTCCertificate Tests</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script> |
| 'use strict'; |
| |
| // Test is based on the Candidate Recommendation: |
| // https://www.w3.org/TR/webrtc/ |
| |
| /* |
| 4.2.1. RTCConfiguration Dictionary |
| dictionary RTCConfiguration { |
| sequence<RTCCertificate> certificates; |
| ... |
| }; |
| |
| certificates of type sequence<RTCCertificate> |
| If this value is absent, then a default set of certificates is |
| generated for each RTCPeerConnection instance. |
| |
| The value for this configuration option cannot change after its |
| value is initially selected. |
| |
| 4.10.2. RTCCertificate Interface |
| interface RTCCertificate { |
| readonly attribute DOMTimeStamp expires; |
| static sequence<AlgorithmIdentifier> getSupportedAlgorithms(); |
| sequence<RTCDtlsFingerprint> getFingerprints(); |
| }; |
| |
| 5.5.1 The RTCDtlsFingerprint Dictionary |
| dictionary RTCDtlsFingerprint { |
| DOMString algorithm; |
| DOMString value; |
| }; |
| |
| [RFC4572] Comedia over TLS in SDP |
| 5. Fingerprint Attribute |
| Figure 2. Augmented Backus-Naur Syntax for the Fingerprint Attribute |
| |
| attribute =/ fingerprint-attribute |
| |
| fingerprint-attribute = "fingerprint" ":" hash-func SP fingerprint |
| |
| hash-func = "sha-1" / "sha-224" / "sha-256" / |
| "sha-384" / "sha-512" / |
| "md5" / "md2" / token |
| ; Additional hash functions can only come |
| ; from updates to RFC 3279 |
| |
| fingerprint = 2UHEX *(":" 2UHEX) |
| ; Each byte in upper-case hex, separated |
| ; by colons. |
| |
| UHEX = DIGIT / %x41-46 ; A-F uppercase |
| */ |
| |
| // Helper function to generate certificate with a set of |
| // default parameters |
| function generateCertificate() { |
| return RTCPeerConnection.generateCertificate({ |
| name: 'ECDSA', |
| namedCurve: 'P-256' |
| }); |
| } |
| |
| // Helper function that takes in an RTCDtlsFingerprint |
| // and return an a=fingerprint SDP line |
| function fingerprintToSdpLine(fingerprint) { |
| return `\r\na=fingerprint:${fingerprint.algorithm} ${fingerprint.value.toUpperCase()}\r\n`; |
| } |
| |
| // Assert that an SDP string has fingerprint line for all the cert's fingerprints |
| function assert_sdp_has_cert_fingerprints(sdp, cert) { |
| for(const fingerprint of cert.getFingerprints()) { |
| const fingerprintLine = fingerprintToSdpLine(fingerprint); |
| assert_true(sdp.includes(fingerprintLine), |
| 'Expect fingerprint line to be found in SDP'); |
| } |
| } |
| |
| /* |
| 4.3.1. Operation |
| When the RTCPeerConnection() constructor is invoked |
| 2. If the certificates value in configuration is non-empty, |
| check that the expires on each value is in the future. |
| If a certificate has expired, throw an InvalidAccessError; |
| otherwise, store the certificates. If no certificates value |
| was specified, one or more new RTCCertificate instances are |
| generated for use with this RTCPeerConnection instance. |
| This may happen asynchronously and the value of certificates |
| remains undefined for the subsequent steps. |
| */ |
| promise_test(t => { |
| return RTCPeerConnection.generateCertificate({ |
| name: 'ECDSA', |
| namedCurve: 'P-256', |
| expires: 0 |
| }).then(cert => { |
| assert_less_than_equal(cert.expires, Date.now()); |
| assert_throws('InvalidAccessError', () => |
| new RTCPeerConnection({ certificates: [cert] })); |
| }); |
| }, 'Constructing RTCPeerConnection with expired certificate should reject with InvalidAccessError'); |
| |
| /* |
| 4.3.2 Interface Definition |
| setConfiguration |
| 4. If configuration.certificates is set and the set of |
| certificates differs from the ones used when connection |
| was constructed, throw an InvalidModificationError. |
| */ |
| promise_test(t => { |
| return Promise.all([ |
| generateCertificate(), |
| generateCertificate() |
| ]).then(([cert1, cert2]) => { |
| const pc = new RTCPeerConnection({ |
| certificates: [cert1] |
| }); |
| |
| // should not throw |
| pc.setConfiguration({ |
| certificates: [cert1] |
| }); |
| |
| assert_throws('InvalidModificationError', () => |
| pc.setConfiguration({ |
| certificates: [cert2] |
| })); |
| |
| assert_throws('InvalidModificationError', () => |
| pc.setConfiguration({ |
| certificates: [cert1, cert2] |
| })); |
| }); |
| }, 'Calling setConfiguration with different set of certs should reject with InvalidModificationError'); |
| |
| /* |
| 4.10.2. RTCCertificate Interface |
| getFingerprints |
| Returns the list of certificate fingerprints, one of which is |
| computed with the digest algorithm used in the certificate signature. |
| |
| 5.5.1 The RTCDtlsFingerprint Dictionary |
| algorithm of type DOMString |
| One of the the hash function algorithms defined in the 'Hash function |
| Textual Names' registry, initially specified in [RFC4572] Section 8. |
| As noted in [JSEP] Section 5.2.1, the digest algorithm used for the |
| fingerprint matches that used in the certificate signature. |
| |
| value of type DOMString |
| The value of the certificate fingerprint in lowercase hex string as |
| expressed utilizing the syntax of 'fingerprint' in [ RFC4572] Section 5. |
| |
| */ |
| promise_test(t => { |
| return generateCertificate() |
| .then(cert => { |
| assert_idl_attribute(cert, 'getFingerprints'); |
| |
| const fingerprints = cert.getFingerprints(); |
| assert_true(Array.isArray(fingerprints), |
| 'Expect fingerprints to return an array'); |
| |
| assert_greater_than_equal(fingerprints.length, 1, |
| 'Expect at last one fingerprint in array'); |
| |
| for(const fingerprint of fingerprints) { |
| assert_equals(typeof fingerprint, 'object', |
| 'Expect fingerprint to be an object (dictionary)'); |
| |
| // https://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xml |
| const algorithms = ['md2', 'md5', 'sha-1', 'sha-224', 'sha-256', 'sha-384', 'sha-512']; |
| assert_in_array(fingerprint.algorithm, algorithms, |
| 'Expect fingerprint.algorithm to be string of algorithm identifier'); |
| |
| assert_true(/^([0-9a-f]{2}\:)+[0-9a-f]{2}$/.test(fingerprint.value), |
| 'Expect fingerprint.value to be lowercase hexadecimal separated by colon'); |
| } |
| }); |
| }, 'RTCCertificate should have at least one fingerprint'); |
| |
| /* |
| 4.3.2 Interface Definition |
| createOffer |
| The value for certificates in the RTCConfiguration for the |
| RTCPeerConnection is used to produce a set of certificate |
| fingerprints. These certificate fingerprints are used in the |
| construction of SDP and as input to requests for identity |
| assertions. |
| |
| [JSEP] |
| 5.2.1. Initial Offers |
| For DTLS, all m= sections MUST use all the certificate(s) that have |
| been specified for the PeerConnection; as a result, they MUST all |
| have the same [I-D.ietf-mmusic-4572-update] fingerprint value(s), or |
| these value(s) MUST be session-level attributes. |
| |
| The following attributes, which are of category IDENTICAL or |
| TRANSPORT, MUST appear only in "m=" sections which either have a |
| unique address or which are associated with the bundle-tag. (In |
| initial offers, this means those "m=" sections which do not contain |
| an "a=bundle-only" attribute.) |
| |
| - An "a=fingerprint" line for each of the endpoint's certificates, |
| as specified in [RFC4572], Section 5; the digest algorithm used |
| for the fingerprint MUST match that used in the certificate |
| signature. |
| |
| Each m= section which is not bundled into another m= section, MUST |
| contain the following attributes (which are of category IDENTICAL or |
| TRANSPORT): |
| |
| - An "a=fingerprint" line for each of the endpoint's certificates, |
| as specified in [RFC4572], Section 5; the digest algorithm used |
| for the fingerprint MUST match that used in the certificate |
| signature. |
| */ |
| promise_test(t => { |
| return generateCertificate() |
| .then(cert => { |
| const pc = new RTCPeerConnection({ |
| certificates: [cert] |
| }); |
| pc.createDataChannel('test'); |
| |
| return pc.createOffer() |
| .then(offer => { |
| assert_sdp_has_cert_fingerprints(offer.sdp, cert); |
| }); |
| }); |
| }, 'RTCPeerConnection({ certificates }) should generate offer SDP with fingerprint of provided certificate'); |
| |
| promise_test(t => { |
| return Promise.all([ |
| generateCertificate(), |
| generateCertificate() |
| ]).then(certs => { |
| const pc = new RTCPeerConnection({ |
| certificates: certs |
| }); |
| pc.createDataChannel('test'); |
| |
| return pc.createOffer() |
| .then(offer => { |
| for(const cert of certs) { |
| assert_sdp_has_cert_fingerprints(offer.sdp, cert); |
| } |
| }); |
| }); |
| }, 'RTCPeerConnection({ certificates }) should generate offer SDP with fingerprint of all provided certificates'); |
| |
| /* |
| TODO |
| |
| 4.10.2. RTCCertificate Interface |
| getSupportedAlgorithms |
| Returns a sequence providing a representative set of supported |
| certificate algorithms. At least one algorithm MUST be returned. |
| |
| The RTCCertificate object can be stored and retrieved from persistent |
| storage by an application. When a user agent is required to obtain a |
| structured clone [HTML5] of a RTCCertificate object, it performs the |
| following steps: |
| 1. Let input and memory be the corresponding inputs defined by the |
| internal structured cloning algorithm, where input represents a |
| RTCCertificate object to be cloned. |
| 2. Let output be a newly constructed RTCCertificate object. |
| 3. Copy the value of the expires attribute from input to output. |
| 4. Let the [[certificate]] internal slot of output be set to the |
| result of invoking the internal structured clone algorithm |
| recursively on the corresponding internal slots of input, with |
| the slot contents as the new " input" argument and memory as |
| the new " memory" argument. |
| 5. Let the [[handle]] internal slot of output refer to the same |
| private keying material represented by the [[handle]] internal |
| slot of input. |
| */ |
| |
| </script> |