OverviewFilesCommitsIssuesDocsReleasesPeers

pearbrowser-desktop / backend / capability-verify.cjs

57 lines · javascript ref:
// Vendored CJS mirror of p2p-hiverelay/core/capability-doc.js `verifyCapabilityDoc`
// (the relay package is ESM and can't be required under Bare). The index room is
// an INDEX, not an authority — a relay-directory row carries the relay's full
// signed capability `doc`, and the client RE-VERIFIES it (Design Risk #5). This
// verifies the doc's ed25519 signature against the pubkey IN the doc (TOFU),
// over the SAME canonical signable payload the relay signs (all fields, sorted
// keys deeply, `signature` excluded) — kept byte-identical to the relay so a doc
// the relay signed verifies here even after JSON re-encoding in transit.

const sodium = require('sodium-universal')
const b4a = require('b4a')

const SIGNATURE_VERSION = 1

function sortKeysDeep (val) {
  if (Array.isArray(val)) return val.map(sortKeysDeep)
  if (val && typeof val === 'object') {
    const out = {}
    for (const k of Object.keys(val).sort()) out[k] = sortKeysDeep(val[k])
    return out
  }
  return val
}

function canonicalSignablePayload (doc) {
  const clone = {}
  for (const k of Object.keys(doc).sort()) {
    if (k === 'signature') continue
    clone[k] = sortKeysDeep(doc[k])
  }
  return b4a.from(JSON.stringify(clone), 'utf8')
}

function verifyCapabilityDocDetailed (doc) {
  if (!doc || typeof doc !== 'object') return { valid: false, reason: 'not an object' }
  if (!doc.signature) return { valid: false, reason: 'no signature on doc' }
  if (doc.signature.v !== SIGNATURE_VERSION) return { valid: false, reason: 'unsupported signature version' }
  if (typeof doc.pubkey !== 'string' || !/^[0-9a-f]{64}$/i.test(doc.pubkey)) return { valid: false, reason: 'no valid pubkey on doc' }
  if (typeof doc.signature.sig !== 'string' || !/^[0-9a-f]{128}$/i.test(doc.signature.sig)) return { valid: false, reason: 'malformed signature' }
  try {
    const payload = canonicalSignablePayload(doc)
    const sig = b4a.from(doc.signature.sig, 'hex')
    const pub = b4a.from(doc.pubkey, 'hex')
    return sodium.crypto_sign_verify_detached(sig, payload, pub)
      ? { valid: true }
      : { valid: false, reason: 'signature verification failed' }
  } catch (err) {
    return { valid: false, reason: 'verify error: ' + (err && err.message) }
  }
}

// Boolean form for the IndexRoomClient verify hook — which truthy-checks the
// result, so a `{ valid: false }` object would wrongly pass. Always return a bool.
function verifyCapabilityDoc (doc) { return verifyCapabilityDocDetailed(doc).valid === true }

module.exports = { verifyCapabilityDoc, verifyCapabilityDocDetailed, canonicalSignablePayload, SIGNATURE_VERSION }