283 lines
10 KiB
HTML
283 lines
10 KiB
HTML
<!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 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>
|