| <!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_dom('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_dom('InvalidModificationError', () => | |
| pc.setConfiguration({ | |
| certificates: [cert2] | |
| })); | |
| assert_throws_dom('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> |