88 lines
3.8 KiB
HTML
88 lines
3.8 KiB
HTML
<!doctype html>
|
|
<meta charset="utf-8">
|
|
<title>RTCCertificateStats describes the local and remote certificates</title>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="RTCPeerConnection-helper.js"></script>
|
|
<script>
|
|
'use strict';
|
|
|
|
// The following helpers come from RTCPeerConnection-helper.js:
|
|
// exchangeIceCandidates, exchangeOfferAnswer, trackFactories, waitForState
|
|
|
|
// Normalize an RFC 4572 fingerprint (colon-separated, any case) to bare
|
|
// lowercase hex so values from different sources can be compared.
|
|
function normalizeFingerprint(value) {
|
|
return value.replace(/:/g, '').toLowerCase();
|
|
}
|
|
|
|
// transport.localCertificateId and transport.remoteCertificateId are
|
|
// references to the RTCCertificateStats describing each end's DTLS
|
|
// certificate. By configuring both peers with certificates whose fingerprints
|
|
// we know in advance, we can assert that each id resolves to a certificate
|
|
// stat that actually describes the expected certificate -- not merely that
|
|
// the field is present. Served over HTTPS because generateCertificate() and
|
|
// getFingerprints() require a secure context.
|
|
promise_test(async t => {
|
|
const [localCert, remoteCert] = await Promise.all([
|
|
RTCPeerConnection.generateCertificate(
|
|
{name: 'ECDSA', namedCurve: 'P-256'}),
|
|
RTCPeerConnection.generateCertificate(
|
|
{name: 'ECDSA', namedCurve: 'P-256'}),
|
|
]);
|
|
const pc1 = new RTCPeerConnection({certificates: [localCert]});
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection({certificates: [remoteCert]});
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
pc1.addTrack(trackFactories.audio());
|
|
exchangeIceCandidates(pc1, pc2);
|
|
await exchangeOfferAnswer(pc1, pc2);
|
|
|
|
const dtlsTransport = pc1.getSenders()[0].transport;
|
|
await waitForState(dtlsTransport, 'connected');
|
|
|
|
const report = await pc1.getStats();
|
|
const transport = [...report.values()].find(s => s.type === 'transport');
|
|
assert_not_equals(transport, undefined, 'Expect a transport stat');
|
|
|
|
async function checkCertificate(id, cert, label) {
|
|
assert_not_equals(id, undefined,
|
|
`Expect transport.${label} to be set when connected`);
|
|
const stat = report.get(id);
|
|
assert_not_equals(stat, undefined,
|
|
`Expect transport.${label} to resolve to a stats object`);
|
|
assert_equals(stat.type, 'certificate',
|
|
`Expect transport.${label} to reference a certificate stat`);
|
|
|
|
// Both peers use a single self-signed certificate, so there is no issuer.
|
|
// See https://w3c.github.io/webrtc-stats/#dom-rtccertificatestats-issuercertificateid
|
|
assert_equals(stat.issuerCertificateId, undefined,
|
|
`Expect transport.${label} to be self-signed with no ` +
|
|
`issuerCertificateId`);
|
|
|
|
const fingerprint = cert.getFingerprints().find(
|
|
f => f.algorithm === stat.fingerprintAlgorithm);
|
|
assert_not_equals(fingerprint, undefined,
|
|
`Expect the certificate to have a ${stat.fingerprintAlgorithm} ` +
|
|
`fingerprint`);
|
|
assert_equals(normalizeFingerprint(stat.fingerprint),
|
|
normalizeFingerprint(fingerprint.value),
|
|
`Expect transport.${label} to describe the expected certificate`);
|
|
|
|
const der = Uint8Array.fromBase64(stat.base64Certificate);
|
|
const digest = new Uint8Array(
|
|
await crypto.subtle.digest(stat.fingerprintAlgorithm, der)).toHex();
|
|
assert_equals(digest, normalizeFingerprint(stat.fingerprint),
|
|
`Expect transport.${label} base64Certificate to hash to its ` +
|
|
`fingerprint`);
|
|
}
|
|
|
|
await checkCertificate(transport.localCertificateId, localCert,
|
|
'localCertificateId');
|
|
await checkCertificate(transport.remoteCertificateId, remoteCert,
|
|
'remoteCertificateId');
|
|
});
|
|
|
|
</script>
|