Skip to content

Commit

Permalink
Determine hash function from multiaddr
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ckousik committed Nov 3, 2022
1 parent 2b7b2c1 commit 9854ff3
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 34 deletions.
45 changes: 24 additions & 21 deletions src/sdp.ts
Original file line number Diff line number Diff line change
@@ -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');

Expand Down Expand Up @@ -41,32 +41,35 @@ 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(':');

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);
Expand Down
24 changes: 11 additions & 13 deletions src/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]});

Expand Down Expand Up @@ -112,7 +116,7 @@ export class WebRTCTransport implements Transport {
// do noise handshake
//set the Noise Prologue to libp2p-webrtc-noise:<FINGERPRINTS> before starting the actual Noise handshake.
// <FINGERPRINTS> 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);
Expand Down Expand Up @@ -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');
}
Expand All @@ -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:');
Expand All @@ -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;
Expand Down

0 comments on commit 9854ff3

Please sign in to comment.