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:
@@ -172,6 +172,7 @@ type Session struct {
|
||||
dc *webrtc.DataChannel
|
||||
peerID string
|
||||
verified bool
|
||||
peerAuthed bool // set once a sealed box from peerID has been opened successfully
|
||||
esk, epk *[32]byte
|
||||
peerEPK *[32]byte
|
||||
ekeySent bool
|
||||
@@ -423,6 +424,9 @@ func (s *Session) onBox(from, box string) {
|
||||
if plain == nil {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.peerAuthed = true
|
||||
s.mu.Unlock()
|
||||
var obj map[string]any
|
||||
if err := json.Unmarshal(plain, &obj); err != nil {
|
||||
return
|
||||
@@ -573,20 +577,25 @@ func (s *Session) sendHello() {
|
||||
if pc == nil {
|
||||
return
|
||||
}
|
||||
lfp, err1 := dtlsFP(pc.LocalDescription().SDP)
|
||||
rfp, err2 := dtlsFP(pc.RemoteDescription().SDP)
|
||||
if err1 != nil || err2 != nil {
|
||||
log.Printf("flit: sendHello: fingerprint error: %v / %v", err1, err2)
|
||||
return
|
||||
}
|
||||
// §6: sender signs prefix || local_fp || remote_fp
|
||||
bindMsg := append([]byte(bindPrefix), lfp...)
|
||||
bindMsg = append(bindMsg, rfp...)
|
||||
s.controlSend(map[string]string{
|
||||
msg := map[string]string{
|
||||
"type": "hello",
|
||||
"id": s.identity.PeerID(),
|
||||
"sig": s.identity.Sign(bindMsg),
|
||||
})
|
||||
}
|
||||
// §6: sign prefix || local_fp || remote_fp when both are extractable.
|
||||
// This is 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 s.peerID (see onBox/peerAuthed)
|
||||
// plus hello.id matching it, not on this signature — see onControl.
|
||||
if lfp, err1 := dtlsFP(pc.LocalDescription().SDP); err1 == nil {
|
||||
if rfp, err2 := dtlsFP(pc.RemoteDescription().SDP); err2 == nil {
|
||||
bindMsg := append([]byte(bindPrefix), lfp...)
|
||||
bindMsg = append(bindMsg, rfp...)
|
||||
msg["sig"] = s.identity.Sign(bindMsg)
|
||||
}
|
||||
}
|
||||
s.controlSend(msg)
|
||||
}
|
||||
|
||||
func (s *Session) onControl(data []byte) {
|
||||
@@ -598,11 +607,18 @@ func (s *Session) onControl(data []byte) {
|
||||
case "hello":
|
||||
helloID, _ := m["id"].(string)
|
||||
sigHex, _ := m["sig"].(string)
|
||||
verified := false
|
||||
s.mu.Lock()
|
||||
pc := s.pc
|
||||
peerAuthed := s.peerAuthed
|
||||
s.mu.Unlock()
|
||||
if pc != nil && pc.RemoteDescription() != nil && pc.LocalDescription() != nil {
|
||||
// Trust rests on the sealed signaling exchange that already
|
||||
// authenticated s.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.
|
||||
verified := helloID == s.peerID && peerAuthed
|
||||
if sigHex != "" && pc != nil && pc.RemoteDescription() != nil && pc.LocalDescription() != nil {
|
||||
// §6: verifier reconstructs prefix || remote_fp || local_fp
|
||||
// (sender's local = our remote; sender's remote = our local)
|
||||
rfp, err1 := dtlsFP(pc.RemoteDescription().SDP)
|
||||
@@ -610,9 +626,9 @@ func (s *Session) onControl(data []byte) {
|
||||
if err1 == nil && err2 == nil {
|
||||
bindMsg := append([]byte(bindPrefix), rfp...)
|
||||
bindMsg = append(bindMsg, lfp...)
|
||||
verified = helloID == s.peerID && crypto.Verify(helloID, bindMsg, sigHex) == nil
|
||||
} else {
|
||||
log.Printf("flit: hello verify: fingerprint error: %v / %v", err1, err2)
|
||||
if err := crypto.Verify(helloID, bindMsg, sigHex); err != nil {
|
||||
log.Printf("flit: hello: fingerprint-bind signature did not verify for %s (informational only): %v", helloID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.mu.Lock()
|
||||
|
||||
Reference in New Issue
Block a user