Fix verified status to not depend on DTLS fingerprint hash matching
hello's fingerprint-bound signature was the actual gate for `verified` on both the CLI and PWA, and dtlsFP() only ever parses sha-256 fingerprints. Different WebRTC stacks report the DTLS cert hash under different algorithms (WebKit: sha-512, pion/Chromium: sha-256), so a legitimate cross-stack peer could never produce a verifiable hello — degrading to permanently "unverified", or on the Go side, never sending hello at all. Matches an interop gotcha waste-go's yaw2 docs recently called out explicitly. Real authentication already happens over the sealed signaling channel during the ekey exchange (crypto.Verify), same as waste-go. Added peerAuthed to the Go session (mirroring the PWA's existing field), set once a sealed box from the peer opens successfully, and gate `verified` on peerAuthed + hello.id matching instead. The fingerprint signature is still sent/checked when both sides have a parseable fingerprint, but only logged on mismatch — never blocking. No behavior change for today's pion<->Chromium pairings (both sha-256). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -435,15 +435,23 @@ export class PeerConn {
|
||||
}
|
||||
|
||||
private _sendHello() {
|
||||
// §6: sign prefix || local_fp || remote_fp when both are extractable.
|
||||
// Best-effort, not required — different WebRTC stacks report the DTLS
|
||||
// certificate hash under different algorithms (WebKit: sha-512,
|
||||
// pion/Chromium: sha-256), so a legitimate cross-stack peer may have no
|
||||
// fingerprint we know how to parse. Trust rests on the sealed signaling
|
||||
// exchange that already authenticated peerId (peerAuthed, set in onBox)
|
||||
// plus hello.id matching it, not on this signature — see _onControl.
|
||||
const msg: Record<string, string> = { type: 'hello', id: this.identity.id }
|
||||
try {
|
||||
const lfp = dtlsFP(this.pc.localDescription!.sdp)
|
||||
const rfp = dtlsFP(this.pc.remoteDescription!.sdp)
|
||||
// §6: sender signs prefix || local_fp || remote_fp
|
||||
const bindMsg = concat(enc(BIND_PREFIX), lfp, rfp)
|
||||
this._dc({ type: 'hello', id: this.identity.id, sig: sodium.to_hex(this.identity.sign(bindMsg)) })
|
||||
} catch (e) {
|
||||
console.error('flit: sendHello fingerprint error', e)
|
||||
msg['sig'] = sodium.to_hex(this.identity.sign(bindMsg))
|
||||
} catch {
|
||||
// no parseable fingerprint on one or both sides — hello still goes out
|
||||
}
|
||||
this._dc(msg)
|
||||
}
|
||||
|
||||
private _onControl(data: string) {
|
||||
@@ -452,17 +460,28 @@ export class PeerConn {
|
||||
|
||||
if (m['type'] === 'hello') {
|
||||
const helloId = m['id'] as string
|
||||
const helloSig = sodium.from_hex(m['sig'] as string)
|
||||
try {
|
||||
// §6: verifier reconstructs prefix || remote_fp || local_fp
|
||||
// (sender's local = our remote; sender's remote = our local)
|
||||
const rfp = dtlsFP(this.pc.remoteDescription!.sdp)
|
||||
const lfp = dtlsFP(this.pc.localDescription!.sdp)
|
||||
const bindMsg = concat(enc(BIND_PREFIX), rfp, lfp)
|
||||
const sigOk = Identity.verify(helloId, bindMsg, helloSig)
|
||||
this.verified = helloId === this.peerId && sigOk && this.peerAuthed
|
||||
} catch {
|
||||
this.verified = false
|
||||
// Trust rests on the sealed signaling exchange that already
|
||||
// authenticated peerId (peerAuthed, set in onBox) plus hello.id
|
||||
// matching it — not on the fingerprint-bound signature below, which
|
||||
// is only checkable when both sides report their DTLS cert hash
|
||||
// under an algorithm we can parse. See _sendHello for why that
|
||||
// can't be a hard requirement.
|
||||
this.verified = helloId === this.peerId && this.peerAuthed
|
||||
const sigHex = m['sig'] as string | undefined
|
||||
if (sigHex) {
|
||||
try {
|
||||
// §6: verifier reconstructs prefix || remote_fp || local_fp
|
||||
// (sender's local = our remote; sender's remote = our local)
|
||||
const rfp = dtlsFP(this.pc.remoteDescription!.sdp)
|
||||
const lfp = dtlsFP(this.pc.localDescription!.sdp)
|
||||
const bindMsg = concat(enc(BIND_PREFIX), rfp, lfp)
|
||||
const helloSig = sodium.from_hex(sigHex)
|
||||
if (!Identity.verify(helloId, bindMsg, helloSig)) {
|
||||
console.warn(`flit: hello: fingerprint-bind signature did not verify for ${helloId} (informational only)`)
|
||||
}
|
||||
} catch {
|
||||
// no parseable fingerprint on one or both sides — nothing to check
|
||||
}
|
||||
}
|
||||
this.events.connected?.(this.verified, this.peerId)
|
||||
} else if (m['type'] === 'file-offer') {
|
||||
|
||||
Reference in New Issue
Block a user