pamoja.update
Idiomatic signed-update facade.
A device that cannot be fixed in the field is a device that has to be visited, and some of them are a day's travel away. Signed updates make that a network operation instead: a release carries a manifest naming who it is for and what it hashes to, a device refuses anything not signed by the key it trusts, and an image that fails to confirm itself is rolled back to the one that worked.
1"""Idiomatic signed-update facade. 2 3A device that cannot be fixed in the field is a device that has to be visited, 4and some of them are a day's travel away. Signed updates make that a network 5operation instead: a release carries a manifest naming who it is for and what it 6hashes to, a device refuses anything not signed by the key it trusts, and an 7image that fails to confirm itself is rolled back to the one that worked. 8""" 9 10from __future__ import annotations 11 12import enum 13 14from pamoja._native import ( 15 BootDecision, 16 Delegation, 17 ImageVerifier, 18 Manifest, 19 Progress, 20 SlotRecord, 21 Updater, 22 decode_manifest, 23 encode_manifest, 24 envelope_body, 25 open_delegation, 26 verify_envelope, 27) 28from pamoja._native import sign_delegation as _sign_delegation 29from pamoja._native import image_digest as _image_digest 30from pamoja._native import sign_manifest as _sign_manifest 31from pamoja._native import update_format_raw as _format_raw 32from pamoja._native import update_structure_version as _structure_version 33from pamoja.security import DeviceIdentity 34 35__all__ = [ 36 "FORMAT_RAW", 37 "STRUCTURE_VERSION", 38 "BootAction", 39 "BootDecision", 40 "Delegation", 41 "ImageVerifier", 42 "Manifest", 43 "Progress", 44 "SlotRecord", 45 "SlotState", 46 "Updater", 47 "decode_manifest", 48 "encode_manifest", 49 "envelope_body", 50 "open_delegation", 51 "sign_delegation", 52 "image_digest", 53 "sign_manifest", 54 "verify_envelope", 55] 56 57#: The manifest structure version this build writes. 58STRUCTURE_VERSION = _structure_version() 59 60#: The payload format meaning the payload is the image itself, byte for byte. 61FORMAT_RAW = _format_raw() 62 63 64class SlotState(str, enum.Enum): 65 """What a device believes about one slot.""" 66 67 #: Nothing has been written here. 68 EMPTY = "Empty" 69 #: An image is arriving, and ``written`` says how much of it has. 70 RECEIVING = "Receiving" 71 #: A complete image that matched its manifest, not yet tried. 72 STAGED = "Staged" 73 #: Being tried for the first time; it reverts unless it confirms. 74 PENDING = "Pending" 75 #: Tried and confirmed working. 76 CONFIRMED = "Confirmed" 77 #: Tried and did not confirm, so it will not be tried again. 78 FAILED = "Failed" 79 80 81class BootAction(str, enum.Enum): 82 """What a bootloader should do with what it found.""" 83 84 #: Nothing new to try; run the confirmed image. 85 CONFIRMED = "Confirmed" 86 #: A staged image is being tried for the first time. 87 TRYING = "Trying" 88 #: A pending image never confirmed, so it was failed. 89 REVERTED = "Reverted" 90 91 92def image_digest(image: bytes) -> bytes: 93 """Hash a complete image, for a publisher filling in a manifest. 94 95 The manifest commits to a SHA-256 over the image, and this is that hash, so a 96 publisher does not need a hashing library of its own just to name the image it 97 is releasing. 98 99 :param image: The complete image the release carries. 100 :returns: The 32-byte digest to put in a :class:`Manifest`. 101 """ 102 return _image_digest(bytes(image)) 103 104 105def sign_manifest(manifest: Manifest, author: DeviceIdentity) -> bytes: 106 """Sign a manifest into the envelope that is offered to a device. 107 108 :param manifest: What the release says about itself. 109 :param author: The identity signing the release. 110 :returns: The signed envelope. 111 """ 112 return _sign_manifest(manifest, DeviceIdentity.native(author)) 113 114 115def sign_delegation(delegation: Delegation, anchor: DeviceIdentity) -> bytes: 116 """Sign a delegation, naming a release key the anchor stands behind. 117 118 Keeping the anchor offline and rotating a release key under it is the 119 arrangement to prefer, because the key that signs day to day is the one most 120 likely to be stolen. 121 122 :param delegation: The statement to sign. 123 :param anchor: The anchor identity, which is the root of the trust. 124 :returns: The signed delegation envelope. 125 """ 126 return _sign_delegation(delegation, DeviceIdentity.native(anchor))
82class BootAction(str, enum.Enum): 83 """What a bootloader should do with what it found.""" 84 85 #: Nothing new to try; run the confirmed image. 86 CONFIRMED = "Confirmed" 87 #: A staged image is being tried for the first time. 88 TRYING = "Trying" 89 #: A pending image never confirmed, so it was failed. 90 REVERTED = "Reverted"
What a bootloader should do with what it found.
The decision a device made at boot, already recorded before it was returned.
A statement, signed by the anchor, that a second key may sign releases.
Hashes an image as it arrives and settles it against its manifest.
What a release says about itself, and what a device checks it against.
How much of an image has arrived.
The record a device keeps about one slot, durable across a reboot.
65class SlotState(str, enum.Enum): 66 """What a device believes about one slot.""" 67 68 #: Nothing has been written here. 69 EMPTY = "Empty" 70 #: An image is arriving, and ``written`` says how much of it has. 71 RECEIVING = "Receiving" 72 #: A complete image that matched its manifest, not yet tried. 73 STAGED = "Staged" 74 #: Being tried for the first time; it reverts unless it confirms. 75 PENDING = "Pending" 76 #: Tried and confirmed working. 77 CONFIRMED = "Confirmed" 78 #: Tried and did not confirm, so it will not be tried again. 79 FAILED = "Failed"
What a device believes about one slot.
A device slots, and the rules applied to what is offered for them.
Records that a slot already holds a confirmed image at a sequence number.
This is how a device that shipped with firmware says what it is running, so the rollback rule has something to compare against.
Adopts a delegation, so releases signed by the key it names are accepted.
Checks a manifest and stages an image that is already held whole, returning the slot it went into.
Checks a manifest and opens the slot it names for a transfer in pieces.
Every check that can be made without the image runs here, so a release
that is not for this device, would roll it back, or does not fit is
refused before a byte of it is accepted. The envelope is remembered until
finish, and each call after this one reopens the transfer from what the
slot records, which is the same path a device takes after a reset.
Finishes an opened image and marks the slot bootable if it matched, returning the slot now holding it.
Reads a manifest body back from its bytes.
This reads what a manifest claims; it proves nothing about who wrote it. Use
verify_envelope to read one whose signature has been checked.
Encodes the body of a manifest, which is the part a signature covers.
Copies out the signed body of an envelope, without checking the signature.
This is what a gateway relays onward unchanged.
Opens a signed delegation against the anchor that should have signed it.
116def sign_delegation(delegation: Delegation, anchor: DeviceIdentity) -> bytes: 117 """Sign a delegation, naming a release key the anchor stands behind. 118 119 Keeping the anchor offline and rotating a release key under it is the 120 arrangement to prefer, because the key that signs day to day is the one most 121 likely to be stolen. 122 123 :param delegation: The statement to sign. 124 :param anchor: The anchor identity, which is the root of the trust. 125 :returns: The signed delegation envelope. 126 """ 127 return _sign_delegation(delegation, DeviceIdentity.native(anchor))
Sign a delegation, naming a release key the anchor stands behind.
Keeping the anchor offline and rotating a release key under it is the arrangement to prefer, because the key that signs day to day is the one most likely to be stolen.
Parameters
- delegation: The statement to sign.
- anchor: The anchor identity, which is the root of the trust. :returns: The signed delegation envelope.
93def image_digest(image: bytes) -> bytes: 94 """Hash a complete image, for a publisher filling in a manifest. 95 96 The manifest commits to a SHA-256 over the image, and this is that hash, so a 97 publisher does not need a hashing library of its own just to name the image it 98 is releasing. 99 100 :param image: The complete image the release carries. 101 :returns: The 32-byte digest to put in a :class:`Manifest`. 102 """ 103 return _image_digest(bytes(image))
Hash a complete image, for a publisher filling in a manifest.
The manifest commits to a SHA-256 over the image, and this is that hash, so a publisher does not need a hashing library of its own just to name the image it is releasing.
Parameters
- image: The complete image the release carries.
:returns: The 32-byte digest to put in a
Manifest.
106def sign_manifest(manifest: Manifest, author: DeviceIdentity) -> bytes: 107 """Sign a manifest into the envelope that is offered to a device. 108 109 :param manifest: What the release says about itself. 110 :param author: The identity signing the release. 111 :returns: The signed envelope. 112 """ 113 return _sign_manifest(manifest, DeviceIdentity.native(author))
Sign a manifest into the envelope that is offered to a device.
Parameters
- manifest: What the release says about itself.
- author: The identity signing the release. :returns: The signed envelope.
Verifies an envelope against a key and reads the manifest inside it.
Raises when the signature is not from that key.