Skip to content
This repository has been archived by the owner on Jun 19, 2023. It is now read-only.

Fix noise prologue generation #18

Merged
merged 9 commits into from
Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@
"@libp2p/logger": "^2.0.0",
"@libp2p/multistream-select": "^3.0.0",
"@libp2p/peer-id": "^1.1.15",
"@multiformats/multiaddr": "../js-multiaddr/",
"@multiformats/multiaddr": "file:../js-multiaddr",
"abortable-iterator": "^4.0.2",
"it-merge": "^1.0.4",
"multiformats": "^9.7.1",
"multihashes": "^4.0.3",
"p-defer": "^4.0.0",
"socket.io-client": "^4.1.2",
"timeout-abort-controller": "^3.0.0",
Expand Down
26 changes: 17 additions & 9 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
export class WebRTCTransportError extends Error {
constructor(msg: string) {
super(msg);
this.name = 'WebRTCTransportError';
}
constructor(msg: string) {
super(msg);
this.name = 'WebRTCTransportError';
}
}

export class InvalidArgumentError extends WebRTCTransportError {
constructor(msg: string) {
super(msg);
this.name = 'WebRTC/InvalidArgumentError';
}
}
constructor(msg: string) {
super(msg);
this.name = 'WebRTC/InvalidArgumentError';
}
}

export class UnsupportedHashAlgorithmError extends WebRTCTransportError {
constructor(algo: string) {
let msg = `unsupported hash algorithm: ${algo}`;
super(msg);
this.name = 'WebRTC/UnsupportedHashAlgorithmError';
}
}
56 changes: 41 additions & 15 deletions src/sdp.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { InvalidArgumentError } from './error.js'
import { InvalidArgumentError, UnsupportedHashAlgorithmError } from './error.js';
import { logger } from '@libp2p/logger';
import { Multiaddr } from '@multiformats/multiaddr';
import { base64 } from 'multiformats/bases/base64';
import * as multihashes from 'multihashes';

const log = logger('libp2p:webrtc:sdp');

// const mbdecoder = base64.decoder.or(base58btc.decoder).or(base32.decoder).or(base16.decoder);

const CERTHASH_CODE: number = 466;
const ANSWER_SDP_FORMAT: string = `
v=0
Expand Down Expand Up @@ -39,19 +43,38 @@ function port(ma: Multiaddr): number {
}
function certhash(ma: Multiaddr): string {
let tups = ma.stringTuples();
let certhash_value = tups
.filter((tup) => tup[0] == CERTHASH_CODE)
.map((tup) => tup[1])[0];
if (certhash_value) {
return certhash_value;
} else {
throw new Error("Couldn't find a certhash component of multiaddr:" + ma.toString());
let certhash_value = tups.filter((tup) => tup[0] == CERTHASH_CODE).map((tup) => tup[1])[0];
if (!certhash_value) {
throw new InvalidArgumentError('certhash not found in multiaddress');
}

// certhash_value is a multibase encoded multihash encoded string
// the multiformats PR always encodes in base64
ckousik marked this conversation as resolved.
Show resolved Hide resolved
let mbdecoded = base64.decode(certhash_value);
let 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 new UnsupportedHashAlgorithmError(mhdecoded.name);
}

let fp = mhdecoded.digest.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
fp = fp.match(/.{1,2}/g)!.join(':');

return `${prefix} ${fp}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certhash contains a space?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This writes the fingerprint in SDP format <hash algorithm> <fingerprint>.

}

function ma2sdp(ma: Multiaddr, ufrag: string): string {
return ANSWER_SDP_FORMAT
.replace('%s', ipv(ma))
return ANSWER_SDP_FORMAT.replace('%s', ipv(ma))
.replace('%s', ip(ma))
.replace('%s', ipv(ma))
.replace('%s', ip(ma))
Expand All @@ -70,12 +93,15 @@ export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescripti

export function munge(desc: RTCSessionDescriptionInit, ufrag: string): RTCSessionDescriptionInit {
if (desc.sdp) {
desc.sdp = desc.sdp
.replace(/\na=ice-ufrag:[^\n]*\n/, '\na=ice-ufrag:' + ufrag + '\n')
.replace(/\na=ice-pwd:[^\n]*\n/, '\na=ice-pwd:' + ufrag + '\n')
;
return desc;
desc.sdp = desc.sdp.replace(/\na=ice-ufrag:[^\n]*\n/, '\na=ice-ufrag:' + ufrag + '\n').replace(/\na=ice-pwd:[^\n]*\n/, '\na=ice-pwd:' + ufrag + '\n');
John-LittleBearLabs marked this conversation as resolved.
Show resolved Hide resolved
return desc;
} else {
throw new InvalidArgumentError("Can't munge a missing SDP");
}
}

export function getCerthashFromMultiaddr(ma: Multiaddr): string | undefined {
ckousik marked this conversation as resolved.
Show resolved Hide resolved
let tups = ma.stringTuples();
let certhash_value = tups.filter((tup) => tup[0] == CERTHASH_CODE).map((tup) => tup[1])[0];
return certhash_value;
}
51 changes: 47 additions & 4 deletions src/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import { logger } from '@libp2p/logger';
import { Multiaddr } from '@multiformats/multiaddr';
import { v4 as genUuid } from 'uuid';
import defer, { DeferredPromise } from 'p-defer';
import { base64 } from 'multiformats/bases/base64';
import { fromString as uint8arrayFromString } from 'uint8arrays/from-string';
import { concat } from 'uint8arrays/concat';
import * as multihashes from 'multihashes';
import { InvalidArgumentError, UnsupportedHashAlgorithmError } from './error';

const log = logger('libp2p:webrtc:transport');
const utf8 = new TextEncoder();

export class WebRTCTransport implements Transport, Initializable {
private componentsPromise: DeferredPromise<void> = defer();
Expand Down Expand Up @@ -87,15 +91,15 @@ export class WebRTCTransport implements Transport, Initializable {
let myPeerId = this.components!.getPeerId();
let rps = ma.getPeerId();
if (!rps) {
throw new Error('TODO Do we really need a peer ID ?');
throw new Error('could not get remote peerId');
ckousik marked this conversation as resolved.
Show resolved Hide resolved
}
let theirPeerId = p.peerIdFromString(rps);

// 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.
let fingerprintsPrologue = [myPeerId.multihash, theirPeerId.multihash].sort().join('');
let noise = new Noise(myPeerId.privateKey, undefined, stablelib, utf8.encode(fingerprintsPrologue));
let fingerprintsPrologue = this.generateNoisePrologue(peerConnection, ma);
let noise = new Noise(myPeerId.privateKey, undefined, stablelib, fingerprintsPrologue);
let wrappedChannel = new WebRTCStream({ channel: handshakeDataChannel, stat: { direction: 'outbound', timeline: { open: 1 } } });
let wrappedDuplex = {
...wrappedChannel,
Expand All @@ -120,4 +124,43 @@ export class WebRTCTransport implements Transport, Initializable {
remotePeer: theirPeerId,
});
}

private generateNoisePrologue(pc: RTCPeerConnection, ma: Multiaddr): Uint8Array {
let remoteCerthash = sdp.getCerthashFromMultiaddr(ma);
if (!remoteCerthash) {
throw new InvalidArgumentError('no remote tls fingerprint in multiaddr');
ckousik marked this conversation as resolved.
Show resolved Hide resolved
}
let remote = base64.decode(remoteCerthash);
if (pc.getConfiguration().certificates?.length === 0) {
throw new InvalidArgumentError('no local certificate');
}
let localCert = pc.getConfiguration().certificates![0];
if (localCert.getFingerprints().length === 0) {
throw new InvalidArgumentError('no fingerprint on local certificate');
}

let localFingerprint = localCert.getFingerprints()[0];
let localFpString = localFingerprint.value!.replaceAll(':', '');
let localFpArray = uint8arrayFromString(localFpString, 'hex');
let local: Uint8Array;
switch (localFingerprint.algorithm!) {
case 'md5':
local = multihashes.encode(localFpArray, multihashes.names['md5']);
break;
case 'sha-256':
local = multihashes.encode(localFpArray, multihashes.names['sha2-256']);
break;
case 'sha-512':
local = multihashes.encode(localFpArray, multihashes.names['sha2-512']);
break;
default:
throw new UnsupportedHashAlgorithmError(localFingerprint.algorithm || 'none');
}

let prefix = uint8arrayFromString('libp2p-webrtc-noise:');
let fps = [local, remote].sort();

let result = concat([prefix, ...fps]);
return result;
}
}
2 changes: 1 addition & 1 deletion test/connection.browser.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-env mocha */

import {createConnectionPair, echoHandler} from "./util";
import {createConnectionPair, echoHandler} from "../test/util.js";
import { expect } from 'aegir/chai';
import { pipe } from 'it-pipe';
import all from 'it-all';
Expand Down
6 changes: 3 additions & 3 deletions test/sdp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ a=mid:0
a=ice-options:ice2
a=ice-ufrag:MyUserFragment
a=ice-pwd:MyUserFragment
a=fingerprint:mTXVsdGliYXNlIGlzIGF3ZXNvbWUhIFxvLw
a=fingerprint:sha-256 b9:2e:11:cf:23:ff:da:31:bb:bb:5c:0a:9d:d9:0e:20:07:e2:bb:61:2f:1f:94:cf:e5:2e:0e:05:5c:4e:8a:88
a=setup:actpass
a=sctp-port:5000
a=max-message-size:100000
`;

describe('SDP creation', () => {
it('handles simple blue sky easily enough', async () => {
let ma = new Multiaddr('/ip4/192.168.0.152/udp/2345/webrtc/certhash/zYAjKoNbau5KiqmHPmSxYCvn66dA1vLmwbt');
let ma = new Multiaddr('/ip4/192.168.0.152/udp/2345/webrtc/certhash/uEiC5LhHPI__aMbu7XAqd2Q4gB-K7YS8flM_lLg4FXE6KiA');
let ufrag = 'MyUserFragment';
let sdp = underTest.fromMultiAddr(ma, ufrag);
expect(sdp.sdp).to.equal(an_sdp);
Expand All @@ -42,7 +42,7 @@ a=mid:0
a=ice-options:ice2
a=ice-ufrag:someotheruserfragmentstring
a=ice-pwd:someotheruserfragmentstring
a=fingerprint:mTXVsdGliYXNlIGlzIGF3ZXNvbWUhIFxvLw
a=fingerprint:sha-256 b9:2e:11:cf:23:ff:da:31:bb:bb:5c:0a:9d:d9:0e:20:07:e2:bb:61:2f:1f:94:cf:e5:2e:0e:05:5c:4e:8a:88
a=setup:actpass
a=sctp-port:5000
a=max-message-size:100000
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"outDir": "dist",
"emitDeclarationOnly": false,
"module": "ES2020",
"importsNotUsedAsValues": "preserve"
"importsNotUsedAsValues": "preserve",
"moduleResolution": "node"
},
"include": [
"src",
Expand Down