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

fix: Fetch local fingerprint from SDP #109

Merged
merged 12 commits into from
May 3, 2023
2 changes: 1 addition & 1 deletion examples/browser-to-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"relay": "node relay.js",
"test:firefox": "npm run build && playwright test --browser=firefox tests",
"test:chrome": "npm run build && playwright test tests",
"test": "npm run test:firefox && npm run test:chrome"
"test": "npm run build && playwright test tests && playwright test --browser firefox tests"
},
"dependencies": {
"@chainsafe/libp2p-noise": "^11.0.0",
Expand Down
4 changes: 3 additions & 1 deletion examples/browser-to-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"start": "vite",
"build": "vite build",
"go-libp2p-server": "cd ../go-libp2p-server && go run ./main.go",
"test": "npm run build && playwright test tests"
"test:chrome": "npm run build && playwright test tests",
"test:firefox": "npm run build && playwright test --browser firefox tests",
"test": "npm run build && playwright test tests && playwright test --browser firefox tests"
},
"dependencies": {
"@chainsafe/libp2p-noise": "^11.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,9 @@ play.describe('bundle ipfs with parceljs:', () => {
// Received message '${message}'
const connections = await page.textContent(output)


expect(connections).toContain(`Dialing '${serverAddr}'`)
expect(connections).toContain(`Peer connected '${serverAddr}'`)



expect(connections).toContain(`Sending message '${message}'`)
expect(connections).toContain(`Received message '${message}'`)
})
Expand Down
31 changes: 31 additions & 0 deletions src/sdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,37 @@ const log = logger('libp2p:webrtc:sdp')
// @ts-expect-error - Not easy to combine these types.
export const mbdecoder: any = Object.values(bases).map(b => b.decoder).reduce((d, b) => d.or(b))

export function getLocalFingerprint (pc: RTCPeerConnection): string | undefined {
// try to fetch fingerprint from local certificate
const localCert = pc.getConfiguration().certificates?.at(0)
if (localCert == null || localCert.getFingerprints == null) {
log.trace('fetching fingerprint from local SDP')
const localDescription = pc.localDescription
if (localDescription == null) {
return undefined
}
return getFingerprintFromSdp(localDescription.sdp)
}

log.trace('fetching fingerprint from local certificate')

if (localCert.getFingerprints().length === 0) {
return undefined
}

const fingerprint = localCert.getFingerprints()[0].value
if (fingerprint == null) {
throw invalidFingerprint('', 'no fingerprint on local certificate')
}

return fingerprint
}

const fingerprintRegex = /^a=fingerprint:(?:\w+-[0-9]+)\s(?<fingerprint>(:?[0-9a-fA-F]{2})+)$/m
export function getFingerprintFromSdp (sdp: string): string | undefined {
const searchResult = sdp.match(fingerprintRegex)
return searchResult?.groups?.fingerprint
}
/**
* Get base2 | identity decoders
*/
Expand Down
1 change: 1 addition & 0 deletions src/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ export class WebRTCStream implements Stream {

constructor (opts: StreamInitOpts) {
this.channel = opts.channel
this.channel.binaryType = 'arraybuffer'
MarcoPolo marked this conversation as resolved.
Show resolved Hide resolved
this.id = this.channel.label

this.stat = opts.stat
Expand Down
40 changes: 18 additions & 22 deletions src/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class WebRTCDirectTransport implements Transport {
namedCurve: 'P-256',
hash: sdp.toSupportedHashFunction(remoteCerthash.name)
} as any)

const peerConnection = new RTCPeerConnection({ certificates: [certificate] })

// create data channel for running the noise handshake. Once the data channel is opened,
Expand Down Expand Up @@ -181,12 +182,24 @@ export class WebRTCDirectTransport implements Transport {
source: {
[Symbol.asyncIterator]: async function * () {
for await (const list of wrappedChannel.source) {
yield list.subarray()
for (const buf of list) {
yield buf
}
}
}
}
}

// Creating the connection before completion of the noise
// handshake ensures that the stream opening callback is set up
const maConn = new WebRTCMultiaddrConnection({
peerConnection,
remoteAddr: ma,
timeline: {
open: Date.now()
}
})

const eventListeningName = isFirefox ? 'iceconnectionstatechange' : 'connectionstatechange'

peerConnection.addEventListener(eventListeningName, () => {
Expand All @@ -206,16 +219,6 @@ export class WebRTCDirectTransport implements Transport {
}
}, { signal })

// Creating the connection before completion of the noise
// handshake ensures that the stream opening callback is set up
const maConn = new WebRTCMultiaddrConnection({
peerConnection,
remoteAddr: ma,
timeline: {
open: Date.now()
}
})

const muxerFactory = new DataChannelMuxerFactory(peerConnection)

// For outbound connections, the remote is expected to start the noise handshake.
Expand All @@ -234,19 +237,12 @@ export class WebRTCDirectTransport implements Transport {
throw invalidArgument('no local certificate')
}

const localCert = pc.getConfiguration().certificates?.at(0)

if (localCert === undefined || localCert.getFingerprints().length === 0) {
throw invalidArgument('no fingerprint on local certificate')
}
Comment on lines -239 to -241
Copy link
Member

Choose a reason for hiding this comment

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

Not sure how this is handled in the js-libp2p codebase in general. Should we still use localCert.getFingerprints in case the API is available (e.g. on Chrome), or should we always use the hack through the SDP?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I would prefer the single code path across browsers.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think I would prefer trying to use the best option and falling back on the hack. But not blocking this.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with Marco here, but it would be good to know (debug logs) which option was used if we're going to have different paths.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@SgtPooki I've added the fallback to fetching from the SDP as requested.


const localFingerprint = localCert.getFingerprints()[0]

if (localFingerprint.value === undefined) {
throw invalidArgument('no fingerprint on local certificate')
const localFingerprint = sdp.getLocalFingerprint(pc)
if (localFingerprint == null) {
throw invalidArgument('no local fingerprint found')
}

const localFpString = localFingerprint.value.replace(/:/g, '')
const localFpString = localFingerprint.trim().toLowerCase().replaceAll(':', '')
const localFpArray = uint8arrayFromString(localFpString, 'hex')
const local = multihashes.encode(localFpArray, hashCode)
const remote: Uint8Array = sdp.mbdecoder.decode(sdp.certhash(ma))
Expand Down
5 changes: 5 additions & 0 deletions test/sdp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ describe('SDP', () => {
])
})

it('extracts a fingerprint from sdp', () => {
Copy link
Member

Choose a reason for hiding this comment

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

we should add a negative test here to validate the fallback behaviour

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 just tests the regex behaviour. While there are no explicit tests for the fallback behaviour, it gets tested when the tests are run in Firefox as part of CI.

const fingerprint = underTest.getFingerprintFromSdp(sampleSdp)
expect(fingerprint).to.eq('72:68:47:CD:48:B0:5E:C5:60:4D:15:9C:BF:40:1D:6F:00:A1:23:EC:90:17:0E:2C:D1:B3:8F:D2:9D:37:E5:B1')
})

it('munges the ufrag and pwd in a SDP', () => {
const result = underTest.munge({ type: 'answer', sdp: sampleSdp }, 'someotheruserfragmentstring')
const expected = `v=0
Expand Down