Skip to content

Commit

Permalink
Add an alternative to RTCCertificate.getFingerprints on Firefox
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaka committed Oct 19, 2022
1 parent c6fa161 commit bc36d66
Showing 1 changed file with 34 additions and 12 deletions.
46 changes: 34 additions & 12 deletions bin/wasm-node/javascript/src/index-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,29 +184,51 @@ export function start(options?: ClientOptions): Client {
// set it explicitly as part of the configuration.
// According to <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-generatecertificate>,
// browsers are guaranteed to support `{ name: "ECDSA", namedCurve: "P-256" }`.
RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" } as EcKeyGenParams).then((localCertificate) => {
RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" } as EcKeyGenParams).then(async (localCertificate) => {
if (cancelOpening)
return;

// Create a new WebRTC connection.
pc = new RTCPeerConnection({ certificates: [localCertificate] });

// We need to build the multihash corresponding to the local certificate.
let localTlsCertificateMultihash: Uint8Array | null = null;
for (const { algorithm, value } of localCertificate.getFingerprints()) {
if (algorithm === 'sha-256') {
localTlsCertificateMultihash = new Uint8Array(34);
localTlsCertificateMultihash.set([0x12, 32], 0);
localTlsCertificateMultihash.set(value!.split(':').map((s) => parseInt(s, 16)), 2);
break;
// While there exists a `RTCPeerConnection.getFingerprints` function, Firefox notably
// doesn't support it.
// See <https://developer.mozilla.org/en-US/docs/Web/API/RTCCertificate#browser_compatibility>
// An alternative to `getFingerprints` is to ask the browser to generate an SDP offer and
// extract from fingerprint from it. Because we explicitly provide a certificate, we have
// the guarantee that the list of certificates will always be the same whenever an SDP offer
// is generated by the browser. However, while this alternative does work on Firefox, it
// doesn't on Chrome, as the SDP offer is for some reason missing the fingerprints.
// Therefore, our strategy is to use `getFingerprints` when it is available (i.e. every
// browser except Firefox), and parse the SDP offer when it is not (i.e. Firefox). In the
// future, only `getFingerprints` would be used.
let localTlsCertificateHex: string | undefined;
if (localCertificate.getFingerprints as any) {
for (const { algorithm, value } of localCertificate.getFingerprints()) {
if (algorithm === 'sha-256') {
localTlsCertificateHex = value!
break;
}
}
} else {
const localSdpOffer = await pc.createOffer();
// Note that this regex is not strict. The browser isn't a malicious actor, and the
// objective of this regex is not to detect invalid input.
const localSdpOfferFingerprintMatch = localSdpOffer.sdp!.match(/a(\s*)=(\s*)fingerprint:(\s*)(sha|SHA)-256(\s*)(([a-fA-F0-9]{2}(:)*){32})/);
if (localSdpOfferFingerprintMatch) {
localTlsCertificateHex = localSdpOfferFingerprintMatch[6]!;
}
}
if (localTlsCertificateMultihash === null) {
if (localTlsCertificateHex === undefined) {
// Because we've already returned from the `connect` function at this point, we pretend
// that the connection has failed to open.
config.onConnectionClose('Failed to obtain the browser certificate fingerprint');
return;
}

// Create a new WebRTC connection.
pc = new RTCPeerConnection({ certificates: [localCertificate] });
const localTlsCertificateMultihash = new Uint8Array(34);
localTlsCertificateMultihash.set([0x12, 32], 0);
localTlsCertificateMultihash.set(localTlsCertificateHex!.split(':').map((s) => parseInt(s, 16)), 2);

// `onconnectionstatechange` is used to detect when the connection has closed or has failed
// to open.
Expand Down

0 comments on commit bc36d66

Please sign in to comment.