1# Copyright 2021 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Facilities for generating TUF target metadata.""" 15 16import enum 17import hashlib 18from typing import Dict, Iterable 19 20from pw_software_update.tuf_pb2 import ( 21 CommonMetadata, 22 Hash, 23 HashFunction, 24 TargetFile, 25 TargetsMetadata, 26) 27 28HASH_FACTORIES = { 29 HashFunction.SHA256: hashlib.sha256, 30} 31DEFAULT_HASHES = (HashFunction.SHA256,) 32DEFAULT_SPEC_VERSION = "0.0.1" 33DEFAULT_METADATA_VERSION = 0 34 35 36class RoleType(enum.Enum): 37 """Set of allowed TUF metadata role types.""" 38 39 ROOT = 'root' 40 TARGETS = 'targets' 41 42 43def gen_common_metadata( 44 role: RoleType, 45 spec_version: str = DEFAULT_SPEC_VERSION, 46 version: int = DEFAULT_METADATA_VERSION, 47) -> CommonMetadata: 48 """Generates CommonMetadata.""" 49 return CommonMetadata( 50 role=role.value, spec_version=spec_version, version=version 51 ) 52 53 54def gen_target_file( 55 file_name: str, file_contents: bytes, hash_funcs=DEFAULT_HASHES 56) -> TargetFile: 57 return TargetFile( 58 file_name=file_name, 59 length=len(file_contents), 60 hashes=gen_hashes(file_contents, hash_funcs), 61 ) 62 63 64def gen_targets_metadata( 65 target_payloads: Dict[str, bytes], 66 hash_funcs: Iterable['HashFunction.V'] = DEFAULT_HASHES, 67 version: int = DEFAULT_METADATA_VERSION, 68) -> TargetsMetadata: 69 """Generates TargetsMetadata the given target payloads.""" 70 target_files = [] 71 for target_file_name, target_payload in target_payloads.items(): 72 new_target_file = gen_target_file( 73 file_name=target_file_name, 74 file_contents=target_payload, 75 hash_funcs=hash_funcs, 76 ) 77 78 target_files.append(new_target_file) 79 80 common_metadata = gen_common_metadata(RoleType.TARGETS, version=version) 81 return TargetsMetadata( 82 common_metadata=common_metadata, target_files=target_files 83 ) 84 85 86def gen_hashes( 87 data: bytes, hash_funcs: Iterable['HashFunction.V'] 88) -> Iterable[Hash]: 89 """Computes all the specified hashes over the data.""" 90 result = [] 91 for func in hash_funcs: 92 if func == HashFunction.UNKNOWN_HASH_FUNCTION: 93 raise ValueError( 94 'UNKNOWN_HASH_FUNCTION cannot be used to generate hashes.' 95 ) 96 digest = HASH_FACTORIES[func](data).digest() 97 result.append(Hash(function=func, hash=digest)) 98 99 return result 100