@pamoja/update
v0.1.18
Published
Signed firmware manifests, streaming image verification, and A/B slots that fall back on their own.
Readme
@pamoja/update
Signed firmware manifests, streaming image verification, and A/B slots that fall back on their own. One capability of pamoja, one memory-safe Rust core with bindings for TypeScript, Python, and C#.
Install
npm install @pamoja/updateThis pulls in @pamoja/native, the compiled engine, and @pamoja/security. npm install pamoja is the whole framework in one package.
Example
The test that runs in CI, spliced here as it ran.
From bindings/node/guides/update.ts:
import { DeviceIdentity } from '@pamoja/security'
import {
BootAction,
SlotState,
Updater,
imageDigest,
signManifest,
verifyEnvelope,
} from '@pamoja/update'
// The publisher's key signs releases; devices in the field are anchored to its public half
// and will take firmware from nobody else.
const publisher = DeviceIdentity.fromSeed(Buffer.alloc(32, 7))
const vendor = Buffer.alloc(16, 0x0a)
const deviceClass = Buffer.alloc(16, 0x0b)
// The release. A manifest says who the image is for, which slot it belongs in, how big it
// is and what it hashes to; nothing about the image itself is taken on trust.
const image = Buffer.from('firmware for a flow meter, version two')
const manifest = {
sequence: 2,
vendorId: vendor,
classId: deviceClass,
storage: 1,
digest: imageDigest(image),
size: image.length,
}
const envelope = signManifest(manifest, publisher)
console.log(`published sequence ${manifest.sequence} in a ${envelope.length}-byte envelope`)
// On the device. It checks the envelope against the key it was anchored to before it
// accepts a single byte of the image.
const opened = verifyEnvelope(envelope, publisher.publicKey())
console.log(`accepted a release for slot ${opened.storage}`)
// It left the factory running sequence 1 from slot 0, so the release goes to the spare slot
// and the image it is running stays where it is.
const fleet = new Updater(vendor, deviceClass, publisher.publicKey(), 2, 4096)
fleet.provision(0, 1)
fleet.begin(envelope)
for (let at = 0; at < image.length; at += 16) {
fleet.write(image.subarray(at, at + 16))
}
console.log(`staged ${fleet.progress().written} of ${image.length} bytes`)
const slot = fleet.finish()
console.log(`written to slot ${slot}, leaving the running image alone`)
// The first boot into a new image is a trial. It reverts on the next boot unless the device
// confirms that it came up, which is what makes a bad release survivable.
console.log(`booting ${fleet.onBoot().action}`)
fleet.confirm()
console.log(`confirmed slot ${slot} is now ${fleet.slotRecord(slot).state}`)
// The same release signed by a key this device is not anchored to gets nowhere.
const impostor = DeviceIdentity.fromSeed(Buffer.alloc(32, 90))
try {
fleet.stage(signManifest(manifest, impostor), image)
console.log('a forged release was accepted, which should never happen')
} catch (error) {
console.log(`forged refused: ${(error as Error).message}`)
}The same capability in every language
| Language | Package | Reference |
| --- | --- | --- |
| Rust | pamoja-update | reference, docs.rs, install |
| TypeScript | @pamoja/update | reference, install |
| Python | pamoja-update | reference, install |
| C# | Pamoja.Update | reference, install |
Documentation
@pamoja/updatereference, every class, function, and type this package exports.- The Signed updates guide, with the same example in Rust, Python, and C#.
- Every capability, and the install page.
License
MIT
