dsh 0.2.0-rc.2: in-container update (experimental channel), path-agnostic compose, upstream patches adapted to 0.1.x/0.2.x
This commit is contained in:
commit
e6dcef9bf5
302 files changed
+75178
No files matched your search
@@ -0,0 +1,162 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { mkdtemp, readFile, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import test from 'node:test'
|
||||
import { TrustLedger } from '../stage0/lib/ledger.mjs'
|
||||
import { document, officialDshPolicy, keyPair, keyring, registryCandidate, registryKeyPair, signature, target } from './helpers.mjs'
|
||||
|
||||
async function fixture() {
|
||||
const recovery = keyPair()
|
||||
return {
|
||||
recovery,
|
||||
ledger: new TrustLedger(await mkdtemp(join(tmpdir(), 'dsh-trust-ledger-')), recovery.publicKey),
|
||||
}
|
||||
}
|
||||
|
||||
test('persists only increasing keyring generations and identical repeats', async () => {
|
||||
const { ledger, recovery } = await fixture()
|
||||
const current = keyPair()
|
||||
const next = keyPair()
|
||||
const first = document(keyring(1, current, next))
|
||||
await ledger.acceptKeyring(first, signature(first, recovery))
|
||||
await ledger.acceptKeyring(first, signature(first, recovery))
|
||||
const conflicting = document({ ...keyring(1, current, next), issuedAt: '2026-08-19T02:00:00.000Z' })
|
||||
await assert.rejects(ledger.acceptKeyring(conflicting, signature(conflicting, recovery)))
|
||||
const older = document(keyring(1, current, next))
|
||||
const third = keyPair()
|
||||
const rotated = document(keyring(2, next, third, [current.keyId]))
|
||||
await ledger.acceptKeyring(rotated, signature(rotated, recovery))
|
||||
await assert.rejects(ledger.acceptKeyring(older, signature(older, recovery)), { code: 'TRUST_ROLLBACK' })
|
||||
})
|
||||
|
||||
test('accepts targets only from current release key and prevents sequence rollback', async () => {
|
||||
const { ledger, recovery } = await fixture()
|
||||
const current = keyPair()
|
||||
const next = keyPair()
|
||||
const ring = document(keyring(1, current, next))
|
||||
await ledger.acceptKeyring(ring, signature(ring, recovery))
|
||||
|
||||
const first = document(target(1, 1))
|
||||
await assert.rejects(ledger.acceptTarget(first, signature(first, next)), { code: 'TRUST_UNKNOWN_KEY' })
|
||||
await ledger.acceptTarget(first, signature(first, current))
|
||||
await ledger.acceptTarget(first, signature(first, current))
|
||||
|
||||
const conflicting = document({ ...target(1, 1), target: { changed: true } })
|
||||
await assert.rejects(ledger.acceptTarget(conflicting, signature(conflicting, current)))
|
||||
const second = document(target(1, 2))
|
||||
await ledger.acceptTarget(second, signature(second, current))
|
||||
await assert.rejects(ledger.acceptTarget(first, signature(first, current)), { code: 'TRUST_ROLLBACK' })
|
||||
})
|
||||
|
||||
test('requires an accepted keyring before accepting a target', async () => {
|
||||
const { ledger } = await fixture()
|
||||
const release = keyPair()
|
||||
const bytes = document(target(1, 1))
|
||||
await assert.rejects(ledger.acceptTarget(bytes, signature(bytes, release)), /keyring/)
|
||||
})
|
||||
|
||||
test('records official DSH identity monotonically without choosing an update channel', async () => {
|
||||
const { ledger, recovery } = await fixture()
|
||||
const current = keyPair()
|
||||
const next = keyPair()
|
||||
const registry = registryKeyPair()
|
||||
const ring = document(keyring(1, current, next))
|
||||
await ledger.acceptKeyring(ring, signature(ring, recovery))
|
||||
const stable = document(target(1, 4, officialDshPolicy(registry)))
|
||||
await ledger.acceptTarget(stable, signature(stable, current))
|
||||
|
||||
const content = Buffer.from('rc8')
|
||||
const first = registryCandidate(registry, '0.1.0-rc.8', content)
|
||||
const digest = createHash('sha256').update(content).digest('hex')
|
||||
await ledger.acceptOfficialDsh(first, digest, content.byteLength)
|
||||
await ledger.acceptOfficialDsh(first, digest, content.byteLength)
|
||||
assert.equal((await ledger.currentOfficialDsh()).value.version, first.version)
|
||||
|
||||
const older = registryCandidate(registry, '0.1.0-rc.6', content)
|
||||
await assert.rejects(ledger.acceptOfficialDsh(older, digest, content.byteLength), { code: 'TRUST_ROLLBACK' })
|
||||
const conflicting = registryCandidate(registry, first.version, Buffer.from('changed'))
|
||||
await assert.rejects(
|
||||
ledger.acceptOfficialDsh(conflicting, createHash('sha256').update('changed').digest('hex'), 7),
|
||||
/different content/,
|
||||
)
|
||||
})
|
||||
|
||||
test('rejects new official DSH imports after the target Release Key is revoked', async () => {
|
||||
const { ledger, recovery } = await fixture()
|
||||
const current = keyPair()
|
||||
const next = keyPair()
|
||||
const registry = registryKeyPair()
|
||||
const ring = document(keyring(1, current, next))
|
||||
await ledger.acceptKeyring(ring, signature(ring, recovery))
|
||||
const stable = document(target(1, 4, officialDshPolicy(registry)))
|
||||
await ledger.acceptTarget(stable, signature(stable, current))
|
||||
|
||||
const content = Buffer.from('rc8')
|
||||
const candidate = registryCandidate(registry, '0.1.0-rc.8', content)
|
||||
const digest = createHash('sha256').update(content).digest('hex')
|
||||
await ledger.acceptOfficialDsh(candidate, digest, content.byteLength)
|
||||
|
||||
const future = keyPair()
|
||||
const rotated = document(keyring(2, next, future, [current.keyId]))
|
||||
await ledger.acceptKeyring(rotated, signature(rotated, recovery))
|
||||
await assert.rejects(ledger.acceptOfficialDsh(candidate, digest, content.byteLength), { code: 'TRUST_REVOKED' })
|
||||
await assert.rejects(ledger.currentOfficialDsh(), { code: 'TRUST_REVOKED' })
|
||||
})
|
||||
|
||||
test('retains historical keyrings so an accepted target remains verifiable after rotation', async () => {
|
||||
const { ledger, recovery } = await fixture()
|
||||
const first = keyPair()
|
||||
const second = keyPair()
|
||||
const third = keyPair()
|
||||
const initialRing = document(keyring(1, first, second))
|
||||
await ledger.acceptKeyring(initialRing, signature(initialRing, recovery))
|
||||
const initialTarget = document(target(1, 7))
|
||||
await ledger.acceptTarget(initialTarget, signature(initialTarget, first))
|
||||
|
||||
const rotatedRing = document(keyring(2, second, third, [first.keyId]))
|
||||
await ledger.acceptKeyring(rotatedRing, signature(rotatedRing, recovery))
|
||||
assert.equal((await ledger.currentTarget()).value.targetSequence, 7)
|
||||
|
||||
const rotatedTarget = document(target(2, 8))
|
||||
await ledger.acceptTarget(rotatedTarget, signature(rotatedTarget, second))
|
||||
assert.equal((await ledger.currentTarget()).value.keyId, second.keyId)
|
||||
})
|
||||
|
||||
test('serializes concurrent target acceptance in call order', async () => {
|
||||
const { ledger, recovery } = await fixture()
|
||||
const current = keyPair()
|
||||
const next = keyPair()
|
||||
const ring = document(keyring(1, current, next))
|
||||
await ledger.acceptKeyring(ring, signature(ring, recovery))
|
||||
const first = document(target(1, 1))
|
||||
const second = document(target(1, 2))
|
||||
await Promise.all([
|
||||
ledger.acceptTarget(first, signature(first, current)),
|
||||
ledger.acceptTarget(second, signature(second, current)),
|
||||
])
|
||||
assert.equal((await ledger.currentTarget()).value.targetSequence, 2)
|
||||
})
|
||||
|
||||
test('does not overwrite a conflicting historical generation after interrupted promotion', async () => {
|
||||
const { ledger, recovery } = await fixture()
|
||||
const first = keyPair()
|
||||
const second = keyPair()
|
||||
const third = keyPair()
|
||||
const initial = document(keyring(1, first, second))
|
||||
await ledger.acceptKeyring(initial, signature(initial, recovery))
|
||||
const rotated = document(keyring(2, second, third, [first.keyId]))
|
||||
await ledger.acceptKeyring(rotated, signature(rotated, recovery))
|
||||
const rotatedRecord = await readFile(ledger.keyringPath('current.record.json'))
|
||||
const initialRecord = await readFile(ledger.keyringPath('generations/1.record.json'))
|
||||
await writeFile(ledger.keyringPath('current.record.json'), initialRecord)
|
||||
|
||||
const replacement = keyPair()
|
||||
const conflicting = document(keyring(2, second, replacement, [first.keyId, third.keyId]))
|
||||
await assert.rejects(
|
||||
ledger.acceptKeyring(conflicting, signature(conflicting, recovery)),
|
||||
/history conflicts/,
|
||||
)
|
||||
assert.deepEqual(await readFile(ledger.keyringPath('generations/2.record.json')), rotatedRecord)
|
||||
})
|
||||
Reference in new issue
Block a user