102 lines
2.8 KiB
JavaScript
102 lines
2.8 KiB
JavaScript
import test from 'brittle'
|
|
import {
|
|
deriveMacKey,
|
|
signCapability,
|
|
verifyCapability,
|
|
createAdminProof,
|
|
verifyAdminProof,
|
|
encodeInvite,
|
|
decodeInvite,
|
|
classifyConnectionInput,
|
|
safeEqual,
|
|
} from '../shared/crypto-auth.js'
|
|
import { Roles } from '../shared/protocol.js'
|
|
|
|
const SEED = 'ab'.repeat(32)
|
|
const SEED2 = 'cd'.repeat(32)
|
|
const PEER = '11'.repeat(32)
|
|
const SERVER_PK = '22'.repeat(32)
|
|
|
|
test('deriveMacKey is deterministic and differs by seed', (t) => {
|
|
const a = deriveMacKey(SEED)
|
|
const b = deriveMacKey(SEED)
|
|
const c = deriveMacKey(SEED2)
|
|
t.is(a.length, 32)
|
|
t.ok(safeEqual(a, b))
|
|
t.absent(safeEqual(a, c))
|
|
})
|
|
|
|
test('sign and verify capability', (t) => {
|
|
const { token, payload } = signCapability(SEED, {
|
|
role: Roles.operator,
|
|
ttlMs: 3600_000,
|
|
})
|
|
t.ok(token.includes('.'))
|
|
t.is(payload.role, Roles.operator)
|
|
const res = verifyCapability(SEED, token, { peerId: PEER })
|
|
t.ok(res.ok)
|
|
t.is(res.payload.role, Roles.operator)
|
|
})
|
|
|
|
test('tampered capability fails', (t) => {
|
|
const { token } = signCapability(SEED, { role: Roles.admin, ttlMs: 3600_000 })
|
|
const [body, mac] = token.split('.')
|
|
const flipped = body.slice(0, -1) + (body.endsWith('A') ? 'B' : 'A') + '.' + mac
|
|
const res = verifyCapability(SEED, flipped)
|
|
t.absent(res.ok)
|
|
t.is(res.code, 'CAPABILITY_INVALID')
|
|
})
|
|
|
|
test('expired capability fails', (t) => {
|
|
const { token } = signCapability(SEED, { role: Roles.viewer, ttlMs: 60_000 })
|
|
const res = verifyCapability(SEED, token, { now: Date.now() + 120_000 })
|
|
t.absent(res.ok)
|
|
t.is(res.code, 'CAPABILITY_EXPIRED')
|
|
})
|
|
|
|
test('persistent capability never expires', (t) => {
|
|
const { token, payload } = signCapability(SEED, {
|
|
role: Roles.operator,
|
|
forever: true,
|
|
})
|
|
t.is(payload.exp, null)
|
|
const res = verifyCapability(SEED, token, {
|
|
now: Date.now() + 100 * 365 * 24 * 3600 * 1000,
|
|
})
|
|
t.ok(res.ok)
|
|
})
|
|
|
|
test('admin proof round-trip', (t) => {
|
|
const proof = createAdminProof(SEED, {
|
|
peerId: PEER,
|
|
serverPublicKeyHex: SERVER_PK,
|
|
})
|
|
const ok = verifyAdminProof(SEED, proof, {
|
|
peerId: PEER,
|
|
serverPublicKeyHex: SERVER_PK,
|
|
})
|
|
t.ok(ok.ok)
|
|
const bad = verifyAdminProof(SEED2, proof, {
|
|
peerId: PEER,
|
|
serverPublicKeyHex: SERVER_PK,
|
|
})
|
|
t.absent(bad.ok)
|
|
})
|
|
|
|
test('invite encode/decode + classify', (t) => {
|
|
const { token } = signCapability(SEED, { role: Roles.operator, forever: true })
|
|
const invite = encodeInvite({
|
|
publicKeyHex: SERVER_PK,
|
|
capability: token,
|
|
role: Roles.operator,
|
|
})
|
|
t.ok(invite.startsWith('pd1.'))
|
|
const dec = decodeInvite(invite)
|
|
t.ok(dec.ok)
|
|
t.is(dec.package.publicKeyHex, SERVER_PK)
|
|
const cls = classifyConnectionInput(invite)
|
|
t.is(cls.kind, 'invite')
|
|
t.is(cls.publicKeyHex, SERVER_PK)
|
|
t.is(classifyConnectionInput(SERVER_PK).kind, 'publicKey')
|
|
})
|