mirror of
https://fuchsia.googlesource.com/third_party/pigweed.googlesource.com/pigweed/pigweed
synced 2024-09-19 21:29:48 +00:00
c870070bc7
Adds verify.py to check update bundle contents and exercise typical verification paths. Change-Id: I50788910a7c662483c92721d950101f1f9550184 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/64982 Reviewed-by: Joe Ethier <jethier@google.com> Commit-Queue: Ali Zhang <alizhang@google.com>
223 lines
9.1 KiB
Python
223 lines
9.1 KiB
Python
# Copyright 2021 The Pigweed Authors
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
# use this file except in compliance with the License. You may obtain a copy of
|
|
# the License at
|
|
#
|
|
# https://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations under
|
|
# the License.
|
|
"""Unit tests for pw_software_update/dev_sign.py."""
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
import tempfile
|
|
from typing import NamedTuple
|
|
import unittest
|
|
|
|
from pw_software_update import dev_sign, root_metadata, update_bundle
|
|
from pw_software_update.verify import VerificationError, verify_bundle
|
|
from pw_software_update.tuf_pb2 import SignedRootMetadata
|
|
from pw_software_update.update_bundle_pb2 import UpdateBundle
|
|
|
|
|
|
def gen_unsigned_bundle(signed_root_metadata: SignedRootMetadata = None,
|
|
targets_metadata_version: int = 0) -> UpdateBundle:
|
|
"""Generates an unsigned test bundle."""
|
|
with tempfile.TemporaryDirectory() as tempdir_name:
|
|
targets_root = Path(tempdir_name)
|
|
foo_path = targets_root / 'foo.bin'
|
|
bar_path = targets_root / 'bar.bin'
|
|
baz_path = targets_root / 'baz.bin'
|
|
qux_path = targets_root / 'subdir' / 'qux.exe'
|
|
foo_bytes = b'\xf0\x0b\xa4'
|
|
bar_bytes = b'\x0b\xa4\x99'
|
|
baz_bytes = b'\xba\x59\x06'
|
|
qux_bytes = b'\x8a\xf3\x12'
|
|
foo_path.write_bytes(foo_bytes)
|
|
bar_path.write_bytes(bar_bytes)
|
|
baz_path.write_bytes(baz_bytes)
|
|
(targets_root / 'subdir').mkdir()
|
|
qux_path.write_bytes(qux_bytes)
|
|
targets = {
|
|
foo_path: 'foo',
|
|
bar_path: 'bar',
|
|
baz_path: 'baz',
|
|
qux_path: 'qux',
|
|
}
|
|
return update_bundle.gen_unsigned_update_bundle(
|
|
targets,
|
|
root_metadata=signed_root_metadata,
|
|
targets_metadata_version=targets_metadata_version)
|
|
|
|
|
|
class TestKey(NamedTuple):
|
|
"""A test key pair"""
|
|
public: bytes
|
|
private: bytes
|
|
|
|
|
|
@dataclass
|
|
class BundleOptions:
|
|
"""Parameters used in test bundle generations."""
|
|
root_key_version: int = 0
|
|
root_metadata_version: int = 0
|
|
targets_key_version: int = 0
|
|
targets_metadata_version: int = 0
|
|
|
|
|
|
def gen_signed_bundle(options: BundleOptions) -> UpdateBundle:
|
|
"""Generates a test bundle per given options."""
|
|
# Root keys look up table: version->TestKey
|
|
root_keys = {
|
|
0:
|
|
TestKey(
|
|
private=(
|
|
b'-----BEGIN PRIVATE KEY-----\n'
|
|
b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgyk3DEQdl'
|
|
b'346MS5N/quNEneJa4HxkJBETGzlEEKkCmZOhRANCAAThdY5PejbtM2p6'
|
|
b'HtgXs/7YSsvPMWZz9Ui1gAEKrDseHnPzC02MbKjQadRIFZ4hKDcsyz9a'
|
|
b'M6QKLCNrCOqYjw6t'
|
|
b'\n-----END PRIVATE KEY-----\n'),
|
|
public=(
|
|
b'-----BEGIN PUBLIC KEY-----\n'
|
|
b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4XWOT3o27TNqeh7YF7P+2'
|
|
b'ErLzzFmc/VItYABCqw7Hh5z8wtNjGyo0GnUSBWeISg3LMs/WjOkCiwjaw'
|
|
b'jqmI8OrQ=='
|
|
b'\n-----END PUBLIC KEY-----\n')),
|
|
1:
|
|
TestKey(
|
|
private=(
|
|
b'-----BEGIN PRIVATE KEY-----\n'
|
|
b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgE3MRbMxo'
|
|
b'Gv3I/Ok/0qE8GV/mQuIbZo9kk+AsJnYetQ6hRANCAAQ5UhycwdcfYe34'
|
|
b'NpmG32t0klnKlrUbk3LyvYLq5uDWG2MfP3L0ciNFsEnW7vHpqqjKsoru'
|
|
b'Qt30G10K7D+reC77'
|
|
b'\n-----END PRIVATE KEY-----\n'),
|
|
public=(
|
|
b'-----BEGIN PUBLIC KEY-----\n'
|
|
b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOVIcnMHXH2Ht+DaZht9rd'
|
|
b'JJZypa1G5Ny8r2C6ubg1htjHz9y9HIjRbBJ1u7x6aqoyrKK7kLd9BtdCu'
|
|
b'w/q3gu+w=='
|
|
b'\n-----END PUBLIC KEY-----\n'))
|
|
}
|
|
|
|
# Targets keys look up table: version->TestKey
|
|
targets_keys = {
|
|
0:
|
|
TestKey(
|
|
private=(
|
|
b'-----BEGIN PRIVATE KEY-----\n'
|
|
b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkMEZ0u84'
|
|
b'HzC51nhhf2ZykPj6WfAjBxXVWndjVdn6bh6hRANCAAT1QzqpFknSAhbA'
|
|
b'uOjy2NuusFOUpeC6TBWM6WeC5JKJgys3gwOoyU0OdomAu9wK6I1Qoe70'
|
|
b'6PUMbWLpyQ10ThVM'
|
|
b'\n-----END PRIVATE KEY-----\n'),
|
|
public=(
|
|
b'-----BEGIN PUBLIC KEY-----\n'
|
|
b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9UM6qRZJ0gIWwLjo8tjbr'
|
|
b'rBTlKXgukwVjOlnguSSiYMrN4MDqMlNDnaJgLvcCuiNUKHu9Oj1DG1i6c'
|
|
b'kNdE4VTA=='
|
|
b'\n-----END PUBLIC KEY-----\n')),
|
|
1:
|
|
TestKey(
|
|
private=(
|
|
b'-----BEGIN PRIVATE KEY-----\n'
|
|
b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+Q+u2KoO'
|
|
b'CwpY1HEKDTIjQXmTlxhoo3gVkE7nrtHhMemhRANCAASgc+0AHCfUxoHy'
|
|
b'+ZkSslLvMufiDqGPABvfuKzHd0wUWs2Y0eIvQc7tsBP0bcuJsFuxvL6a'
|
|
b'8Ek7y3kUmFWVL01v'
|
|
b'\n-----END PRIVATE KEY-----\n'),
|
|
public=(
|
|
b'-----BEGIN PUBLIC KEY-----\n'
|
|
b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoHPtABwn1MaB8vmZErJS7'
|
|
b'zLn4g6hjwAb37isx3dMFFrNmNHiL0HO7bAT9G3LibBbsby+mvBJO8t5FJ'
|
|
b'hVlS9Nbw=='
|
|
b'\n-----END PUBLIC KEY-----\n'))
|
|
}
|
|
|
|
unsigned_root = root_metadata.gen_root_metadata(
|
|
root_metadata.RootKeys([root_keys[options.root_key_version].public]),
|
|
root_metadata.TargetsKeys(
|
|
[targets_keys[options.targets_key_version].public]),
|
|
version=options.root_metadata_version)
|
|
|
|
serialized_root = unsigned_root.SerializeToString()
|
|
signed_root = SignedRootMetadata(serialized_root_metadata=serialized_root)
|
|
signed_root = dev_sign.sign_root_metadata(
|
|
signed_root, root_keys[options.root_key_version].private)
|
|
# Additionaly sign the root metadata with the previous version of root key
|
|
# to enable upgrading from the previous root.
|
|
if options.root_key_version > 0:
|
|
signed_root = dev_sign.sign_root_metadata(
|
|
signed_root, root_keys[options.root_key_version - 1].private)
|
|
|
|
unsigned_bundle = gen_unsigned_bundle(
|
|
signed_root_metadata=signed_root,
|
|
targets_metadata_version=options.targets_metadata_version)
|
|
signed_bundle = dev_sign.sign_update_bundle(
|
|
unsigned_bundle, targets_keys[options.targets_key_version].private)
|
|
|
|
return signed_bundle
|
|
|
|
|
|
class VerifyBundleTest(unittest.TestCase):
|
|
"""Bundle verification test cases."""
|
|
def test_self_verification(self): # pylint: disable=no-self-use
|
|
incoming = gen_signed_bundle(BundleOptions())
|
|
verify_bundle(incoming, trusted=incoming)
|
|
|
|
def test_root_key_rotation(self): # pylint: disable=no-self-use
|
|
trusted = gen_signed_bundle(BundleOptions(root_key_version=0))
|
|
incoming = gen_signed_bundle(BundleOptions(root_key_version=1))
|
|
verify_bundle(incoming, trusted)
|
|
|
|
def test_root_metadata_anti_rollback(self):
|
|
trusted = gen_signed_bundle(BundleOptions(root_metadata_version=1))
|
|
incoming = gen_signed_bundle(BundleOptions(root_metadata_version=0))
|
|
with self.assertRaises(VerificationError):
|
|
verify_bundle(incoming, trusted)
|
|
|
|
def test_root_metadata_anti_rollback_with_key_rotation(self):
|
|
trusted = gen_signed_bundle(
|
|
BundleOptions(root_key_version=0, root_metadata_version=1))
|
|
incoming = gen_signed_bundle(
|
|
BundleOptions(root_key_version=1, root_metadata_version=0))
|
|
# Anti-rollback enforced regardless of key rotation.
|
|
with self.assertRaises(VerificationError):
|
|
verify_bundle(incoming, trusted)
|
|
|
|
def test_missing_root(self):
|
|
incoming = gen_signed_bundle(BundleOptions())
|
|
incoming.ClearField('root_metadata')
|
|
with self.assertRaises(VerificationError):
|
|
verify_bundle(incoming, trusted=incoming)
|
|
|
|
def test_targets_key_rotation(self): # pylint: disable=no-self-use
|
|
trusted = gen_signed_bundle(BundleOptions(targets_key_version=0))
|
|
incoming = gen_signed_bundle(BundleOptions(targets_key_version=1))
|
|
verify_bundle(incoming, trusted)
|
|
|
|
def test_targets_metadata_anti_rollback(self):
|
|
trusted = gen_signed_bundle(BundleOptions(targets_metadata_version=1))
|
|
incoming = gen_signed_bundle(BundleOptions(targets_metadata_version=0))
|
|
with self.assertRaises(VerificationError):
|
|
verify_bundle(incoming, trusted)
|
|
|
|
def test_targets_fastforward_recovery(self): # pylint: disable=no-self-use
|
|
trusted = gen_signed_bundle(
|
|
BundleOptions(targets_key_version=0, targets_metadata_version=999))
|
|
# Revoke key and bring back the metadata version.
|
|
incoming = gen_signed_bundle(
|
|
BundleOptions(targets_key_version=1, targets_metadata_version=0))
|
|
# Anti-rollback is not enforced upon key rotation.
|
|
verify_bundle(incoming, trusted)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|