fix(turn): generate HMAC-SHA1 credentials for coturn use-auth-secret
coturn's use-auth-secret mode requires the TURN password to be HMAC-SHA1(secret, username), not the raw secret. Pass ice servers as a constructor argument so the async credential generation happens before RTCPeerConnection is created. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,13 +16,23 @@ const EKEY_PREFIX = 'yaw/2.1 ekey'
|
|||||||
const FS_TIMEOUT = 2000
|
const FS_TIMEOUT = 2000
|
||||||
const STUN = 'stun:stun.l.google.com:19302'
|
const STUN = 'stun:stun.l.google.com:19302'
|
||||||
|
|
||||||
function iceServers(): RTCIceServer[] {
|
async function turnCredential(secret: string, username: string): Promise<string> {
|
||||||
|
const key = await crypto.subtle.importKey(
|
||||||
|
'raw', new TextEncoder().encode(secret),
|
||||||
|
{ name: 'HMAC', hash: 'SHA-1' },
|
||||||
|
false, ['sign']
|
||||||
|
)
|
||||||
|
const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(username))
|
||||||
|
return btoa(String.fromCharCode(...new Uint8Array(sig)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function iceServers(): Promise<RTCIceServer[]> {
|
||||||
const cfg = (window as unknown as { WASTE_CONFIG?: { turnURL?: string; turnSecret?: string } }).WASTE_CONFIG
|
const cfg = (window as unknown as { WASTE_CONFIG?: { turnURL?: string; turnSecret?: string } }).WASTE_CONFIG
|
||||||
const servers: RTCIceServer[] = [{ urls: STUN }]
|
const servers: RTCIceServer[] = [{ urls: STUN }]
|
||||||
if (cfg?.turnURL && cfg?.turnSecret) {
|
if (cfg?.turnURL && cfg?.turnSecret) {
|
||||||
// TURN with time-limited credentials derived from static-auth-secret
|
|
||||||
const user = Math.floor(Date.now() / 1000) + 3600 + ':waste'
|
const user = Math.floor(Date.now() / 1000) + 3600 + ':waste'
|
||||||
servers.push({ urls: cfg.turnURL, username: user, credential: cfg.turnSecret })
|
const credential = await turnCredential(cfg.turnSecret, user)
|
||||||
|
servers.push({ urls: cfg.turnURL, username: user, credential })
|
||||||
}
|
}
|
||||||
return servers
|
return servers
|
||||||
}
|
}
|
||||||
@@ -267,14 +277,14 @@ class PeerConn {
|
|||||||
// push-initiated files waiting for file-accept: xid → File
|
// push-initiated files waiting for file-accept: xid → File
|
||||||
private _pushQueue: Map<string, File> = new Map()
|
private _pushQueue: Map<string, File> = new Map()
|
||||||
|
|
||||||
constructor(identity: Identity, sig: Signaling, peerId: string, on: PeerCallback, nick: string, share: Map<string, File>) {
|
constructor(identity: Identity, sig: Signaling, peerId: string, on: PeerCallback, nick: string, share: Map<string, File>, ice: RTCIceServer[]) {
|
||||||
this.identity = identity
|
this.identity = identity
|
||||||
this.sig = sig
|
this.sig = sig
|
||||||
this.peerId = peerId
|
this.peerId = peerId
|
||||||
this.on = on
|
this.on = on
|
||||||
this.nick = nick
|
this.nick = nick
|
||||||
this.share = share
|
this.share = share
|
||||||
this.pc = new RTCPeerConnection({ iceServers: iceServers() })
|
this.pc = new RTCPeerConnection({ iceServers: ice })
|
||||||
const kp = sodium.crypto_box_keypair()
|
const kp = sodium.crypto_box_keypair()
|
||||||
this._esk = kp.privateKey
|
this._esk = kp.privateKey
|
||||||
this._epk = kp.publicKey
|
this._epk = kp.publicKey
|
||||||
@@ -715,8 +725,9 @@ export class BrowserAdapter {
|
|||||||
if (existing.verified || open || (alive && fresh)) return
|
if (existing.verified || open || (alive && fresh)) return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ice = await iceServers()
|
||||||
const peer = new PeerConn(this.identity, this.sig!, pid,
|
const peer = new PeerConn(this.identity, this.sig!, pid,
|
||||||
(ev, data) => this._onPeerEvent(ev, data), this.identity.nick, this.sharedFiles)
|
(ev, data) => this._onPeerEvent(ev, data), this.identity.nick, this.sharedFiles, ice)
|
||||||
this.peers.set(pid, peer)
|
this.peers.set(pid, peer)
|
||||||
this._emitDiscovered(pid)
|
this._emitDiscovered(pid)
|
||||||
await peer.startOffer()
|
await peer.startOffer()
|
||||||
@@ -726,8 +737,9 @@ export class BrowserAdapter {
|
|||||||
if (!this.identity || from === this.identity.id) return
|
if (!this.identity || from === this.identity.id) return
|
||||||
let peer = this.peers.get(from)
|
let peer = this.peers.get(from)
|
||||||
if (!peer || peer.pc.connectionState === 'failed' || peer.pc.connectionState === 'closed') {
|
if (!peer || peer.pc.connectionState === 'failed' || peer.pc.connectionState === 'closed') {
|
||||||
|
const ice = await iceServers()
|
||||||
peer = new PeerConn(this.identity, this.sig!, from,
|
peer = new PeerConn(this.identity, this.sig!, from,
|
||||||
(ev, data) => this._onPeerEvent(ev, data), this.identity.nick, this.sharedFiles)
|
(ev, data) => this._onPeerEvent(ev, data), this.identity.nick, this.sharedFiles, ice)
|
||||||
this._emitDiscovered(from)
|
this._emitDiscovered(from)
|
||||||
this.peers.set(from, peer)
|
this.peers.set(from, peer)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user