From 9854ff381b70ab2557abe735401499aed876f343 Mon Sep 17 00:00:00 2001 From: Chinmay Kousik Date: Thu, 3 Nov 2022 18:27:12 +0530 Subject: [PATCH] Determine hash function from multiaddr This uses the hash found in the remote multiaddr certhash to generate a certificate. Currently, only hash functions supported are 'sha1', 'sha256', and 'sha512'. These are fetched from https://www.w3.org/TR/WebCryptoAPI/#sha-registration except sha-384. --- src/sdp.ts | 45 ++++++++++++++++++++++++--------------------- src/transport.ts | 24 +++++++++++------------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/sdp.ts b/src/sdp.ts index 9b96b51c3b..49890dc0ca 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -1,8 +1,8 @@ -import { inappropriateMultiaddr, invalidArgument, unsupportedHashAlgorithm } from './error.js'; -import { logger } from '@libp2p/logger'; -import { Multiaddr } from '@multiformats/multiaddr'; +import {inappropriateMultiaddr, invalidArgument, unsupportedHashAlgorithm} from './error.js'; +import {logger} from '@libp2p/logger'; +import {Multiaddr} from '@multiformats/multiaddr'; import * as multihashes from 'multihashes'; -import { bases } from 'multiformats/basics'; +import {bases} from 'multiformats/basics'; const log = logger('libp2p:webrtc:sdp'); @@ -41,25 +41,15 @@ export function certhash(ma: Multiaddr): string { } } +export function decodeCerthash(certhash: string) { + const mbdecoded = mbdecoder.decode(certhash); + return multihashes.decode(mbdecoded); +} + export function certhashToFingerprint(ma: Multiaddr): string[] { - const certhash_value = certhash(ma); // certhash_value is a multibase encoded multihash encoded string - const mbdecoded = mbdecoder.decode(certhash_value); - const mhdecoded = multihashes.decode(mbdecoded); - let prefix = ''; - switch (mhdecoded.name) { - case 'md5': - prefix = 'md5'; - break; - case 'sha2-256': - prefix = 'sha-256'; - break; - case 'sha2-512': - prefix = 'sha-512'; - break; - default: - throw unsupportedHashAlgorithm(mhdecoded.name); - } + const mhdecoded = decodeCerthash(certhash(ma)); + let prefix = toSupportedHashFunction(mhdecoded.name); const fp = mhdecoded.digest.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); const fpSdp = fp.match(/.{1,2}/g)!.join(':'); @@ -67,6 +57,19 @@ export function certhashToFingerprint(ma: Multiaddr): string[] { return [`${prefix.toUpperCase()} ${fpSdp.toUpperCase()}`, fp]; } +export function toSupportedHashFunction(name: multihashes.HashName): string { + switch (name) { + case 'sha1': + return 'sha-1' + case 'sha2-256': + return 'sha-256'; + case 'sha2-512': + return 'sha-512'; + default: + throw unsupportedHashAlgorithm(name); + } +} + function ma2sdp(ma: Multiaddr, ufrag: string): string { const IP = ip(ma); const IPVERSION = ipv(ma); diff --git a/src/transport.ts b/src/transport.ts index 49fa09ecaf..c922c2b674 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -13,12 +13,15 @@ import defer from 'p-defer'; import {fromString as uint8arrayFromString} from 'uint8arrays/from-string'; import {concat} from 'uint8arrays/concat'; import * as multihashes from 'multihashes'; -import {dataChannelError, inappropriateMultiaddr, unimplemented, invalidArgument, unsupportedHashAlgorithm} from './error.js'; +import {dataChannelError, inappropriateMultiaddr, unimplemented, invalidArgument} from './error.js'; import {WebRTCMultiaddrConnection} from './maconn.js'; import {DataChannelMuxerFactory} from './muxer.js'; const log = logger('libp2p:webrtc:transport'); const HANDSHAKE_TIMEOUT_MS = 10000; +const WEBRTC_CODE: number = 280; +const CERTHASH_CODE: number = 466; + export interface WebRTCTransportComponents { peerId: PeerId @@ -59,14 +62,15 @@ export class WebRTCTransport implements Transport { throw inappropriateMultiaddr("we need to have the remote's PeerId"); } + const remoteCerthash = sdp.decodeCerthash(sdp.certhash(ma)) // ECDSA is preferred over RSA here. From our testing we find that P-256 elliptic // curve is supported by Pion, webrtc-rs, as well as Chromium (P-228 and P-384 - // was not supported in Chromium). We fix the hash algorith to SHA-256 for - // reasons documented here: https://github.com/libp2p/specs/pull/412#discussion_r968327480 + // was not supported in Chromium). We use the same hash function as found in the + // multiaddr if it is supported. const certificate = await RTCPeerConnection.generateCertificate({ name: 'ECDSA', namedCurve: 'P-256', - hash: 'SHA-256', + hash: sdp.toSupportedHashFunction(remoteCerthash.name), } as any); const peerConnection = new RTCPeerConnection({certificates: [certificate]}); @@ -112,7 +116,7 @@ export class WebRTCTransport implements Transport { // do noise handshake //set the Noise Prologue to libp2p-webrtc-noise: before starting the actual Noise handshake. // is the concatenation of the of the two TLS fingerprints of A and B in their multihash byte representation, sorted in ascending order. - const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, ma); + const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, remoteCerthash.name, ma); // Since we use the default crypto interface and do not use a static key or early data, // we pass in undefined for these parameters. const noise = new Noise(undefined, undefined, undefined, fingerprintsPrologue); @@ -146,7 +150,7 @@ export class WebRTCTransport implements Transport { return upgraded } - private generateNoisePrologue(pc: RTCPeerConnection, ma: Multiaddr): Uint8Array { + private generateNoisePrologue(pc: RTCPeerConnection, hashName: multihashes.HashName, ma: Multiaddr): Uint8Array { if (pc.getConfiguration().certificates?.length === 0) { throw invalidArgument('no local certificate'); } @@ -158,10 +162,7 @@ export class WebRTCTransport implements Transport { const localFingerprint = localCert.getFingerprints()[0]; const localFpString = localFingerprint.value!.replaceAll(':', ''); const localFpArray = uint8arrayFromString(localFpString, 'hex'); - if (localFingerprint.algorithm! != 'sha-256') { - throw unsupportedHashAlgorithm(localFingerprint.algorithm || 'none'); - } - const local = multihashes.encode(localFpArray, multihashes.names['sha2-256']); + const local = multihashes.encode(localFpArray, multihashes.names[hashName]); const remote: Uint8Array = sdp.mbdecoder.decode(sdp.certhash(ma)); const prefix = uint8arrayFromString('libp2p-webrtc-noise:'); @@ -171,9 +172,6 @@ export class WebRTCTransport implements Transport { } } -const WEBRTC_CODE: number = 280; -const CERTHASH_CODE: number = 466; - function validMa(ma: Multiaddr): boolean { const codes = ma.protoCodes(); return codes.includes(WEBRTC_CODE) && codes.includes(CERTHASH_CODE) && ma.getPeerId() != null;