Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 1 | # ----------------------------------------------------------------------------- |
| 2 | # Copyright (c) 2019-2022, Arm Limited. All rights reserved. |
Thomas Fossati | 833ca65 | 2024-04-26 08:47:09 +0000 | [diff] [blame] | 3 | # Copyright (c) 2024, Linaro Limited. All rights reserved. |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 4 | # |
| 5 | # SPDX-License-Identifier: BSD-3-Clause |
| 6 | # |
| 7 | # ----------------------------------------------------------------------------- |
| 8 | |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 9 | """Helper utilities for CLI tools and tests""" |
| 10 | |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 11 | from collections.abc import Iterable |
| 12 | from copy import deepcopy |
| 13 | import logging |
| 14 | |
| 15 | import base64 |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 16 | import yaml |
Thomas Fossati | 833ca65 | 2024-04-26 08:47:09 +0000 | [diff] [blame] | 17 | import yaml_include |
Thomas Fossati | f4e1ca3 | 2024-08-16 16:01:31 +0000 | [diff] [blame^] | 18 | from pycose.keys import CoseKey |
| 19 | from pycose.keys.curves import P256, P384, P521 |
| 20 | from pycose.keys.keytype import KtyEC2 |
| 21 | from pycose.algorithms import Es256, Es384, Es512, HMAC256 |
Mate Toth-Pal | bb187d0 | 2022-04-26 16:01:51 +0200 | [diff] [blame] | 22 | from iatverifier.attest_token_verifier import AttestationTokenVerifier |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 23 | from cbor2 import CBOREncoder |
Thomas Fossati | f4e1ca3 | 2024-08-16 16:01:31 +0000 | [diff] [blame^] | 24 | from pycose.keys import CoseKey |
| 25 | from ecdsa.util import number_to_string |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 26 | |
| 27 | _logger = logging.getLogger("util") |
| 28 | |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 29 | _known_curves = { |
Thomas Fossati | f4e1ca3 | 2024-08-16 16:01:31 +0000 | [diff] [blame^] | 30 | P256: Es256, |
| 31 | P384: Es384, |
| 32 | P521: Es512, |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 33 | } |
| 34 | |
Thomas Fossati | f4e1ca3 | 2024-08-16 16:01:31 +0000 | [diff] [blame^] | 35 | |
| 36 | def es384_cose_key_from_raw_ecdsa(raw_key): |
| 37 | d = { |
| 38 | 'KTY': 'EC2', |
| 39 | 'CURVE': 'P_384', |
| 40 | 'ALG': 'ES384', |
| 41 | 'X': number_to_string(raw_key.pubkey.point.x(), raw_key.curve.generator.order()), |
| 42 | 'Y': number_to_string(raw_key.pubkey.point.y(), raw_key.curve.generator.order()), |
| 43 | } |
| 44 | |
| 45 | return CoseKey.from_dict(d) |
| 46 | |
Mate Toth-Pal | e305e55 | 2022-10-07 14:04:53 +0200 | [diff] [blame] | 47 | def convert_map_to_token(token_map, verifier, wfh, *, name_as_key, parse_raw_value): |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 48 | """ |
| 49 | Convert a map to token and write the result to a file. |
| 50 | """ |
| 51 | encoder = CBOREncoder(wfh) |
| 52 | verifier.convert_map_to_token( |
| 53 | encoder, |
| 54 | token_map, |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 55 | name_as_key=name_as_key, |
| 56 | parse_raw_value=parse_raw_value, |
| 57 | root=True) |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 58 | |
| 59 | |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 60 | def read_token_map(file): |
| 61 | """ |
| 62 | Read a yaml file and return a map |
| 63 | """ |
Thomas Fossati | 833ca65 | 2024-04-26 08:47:09 +0000 | [diff] [blame] | 64 | yaml.add_constructor("!inc", yaml_include.Constructor(base_dir='.'), Loader=yaml.SafeLoader) |
| 65 | |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 66 | if hasattr(file, 'read'): |
Thomas Fossati | 833ca65 | 2024-04-26 08:47:09 +0000 | [diff] [blame] | 67 | raw = yaml.load(file, Loader=yaml.SafeLoader) |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 68 | else: |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 69 | with open(file, encoding="utf8") as file_obj: |
Thomas Fossati | 833ca65 | 2024-04-26 08:47:09 +0000 | [diff] [blame] | 70 | raw = yaml.load(file_obj, Loader=yaml.SafeLoader) |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 71 | |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 72 | return raw |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 73 | |
| 74 | |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 75 | def recursive_bytes_to_strings(token, in_place=False): |
| 76 | """ |
| 77 | Transform the map in 'token' by changing changing bytes to base64 encoded form. |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 78 | |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 79 | if 'in_place' is True, 'token' is modified, a new map is returned otherwise. |
| 80 | """ |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 81 | if in_place: |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 82 | result = token |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 83 | else: |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 84 | result = deepcopy(token) |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 85 | |
| 86 | if hasattr(result, 'items'): |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 87 | for key, value in result.items(): |
| 88 | result[key] = recursive_bytes_to_strings(value, in_place=True) |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 89 | elif (isinstance(result, Iterable) and |
| 90 | not isinstance(result, (str, bytes))): |
| 91 | result = [recursive_bytes_to_strings(r, in_place=True) |
| 92 | for r in result] |
| 93 | elif isinstance(result, bytes): |
| 94 | result = str(base64.b16encode(result)) |
| 95 | |
| 96 | return result |
| 97 | |
| 98 | |
| 99 | def read_keyfile(keyfile, method=AttestationTokenVerifier.SIGN_METHOD_SIGN1): |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 100 | """ |
| 101 | Read a keyfile and return the key |
| 102 | """ |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 103 | if keyfile: |
| 104 | if method == AttestationTokenVerifier.SIGN_METHOD_SIGN1: |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 105 | return _read_sign1_key(keyfile) |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 106 | if method == AttestationTokenVerifier.SIGN_METHOD_MAC0: |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 107 | return _read_hmac_key(keyfile) |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 108 | err_msg = 'Unexpected method "{}"; must be one of: sign, mac' |
| 109 | raise ValueError(err_msg.format(method)) |
| 110 | |
| 111 | return None |
| 112 | |
Mate Toth-Pal | 5ebca51 | 2022-03-24 16:45:51 +0100 | [diff] [blame] | 113 | def get_cose_alg_from_key(key, default): |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 114 | """Extract the algorithm from the key if possible |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 115 | |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 116 | Returns the signature algorithm ID defined by COSE |
Mate Toth-Pal | 5ebca51 | 2022-03-24 16:45:51 +0100 | [diff] [blame] | 117 | If key is None, default is returned. |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 118 | """ |
Mate Toth-Pal | 5ebca51 | 2022-03-24 16:45:51 +0100 | [diff] [blame] | 119 | if key is None: |
| 120 | return default |
Thomas Fossati | f4e1ca3 | 2024-08-16 16:01:31 +0000 | [diff] [blame^] | 121 | if key.kty is not KtyEC2: |
| 122 | err_msg = 'Unexpected key type "{}"; must be KtyEC2' |
| 123 | raise ValueError(err_msg.format(key.kty)) |
| 124 | if key.alg is not None: |
| 125 | return key.alg |
| 126 | return _known_curves[key.crv] |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 127 | |
| 128 | def _read_sign1_key(keyfile): |
| 129 | with open(keyfile, 'rb') as file_obj: |
Thomas Fossati | f4e1ca3 | 2024-08-16 16:01:31 +0000 | [diff] [blame^] | 130 | s = file_obj.read() |
| 131 | raw_key = s.decode("utf-8") # assume PEM-encoded key |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 132 | try: |
Thomas Fossati | f4e1ca3 | 2024-08-16 16:01:31 +0000 | [diff] [blame^] | 133 | key = CoseKey.from_pem_private_key(raw_key) |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 134 | except Exception as exc: |
| 135 | signing_key_error = str(exc) |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 136 | |
| 137 | try: |
Thomas Fossati | f4e1ca3 | 2024-08-16 16:01:31 +0000 | [diff] [blame^] | 138 | key = CoseKey.from_pem_public_key(raw_key) |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 139 | except Exception as vexc: |
| 140 | verifying_key_error = str(vexc) |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 141 | |
| 142 | msg = 'Bad key file "{}":\n\tpubkey error: {}\n\tprikey error: {}' |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 143 | raise ValueError(msg.format(keyfile, verifying_key_error, signing_key_error)) from vexc |
Mate Toth-Pal | 51b6198 | 2022-03-17 14:19:30 +0100 | [diff] [blame] | 144 | return key |
| 145 | |
| 146 | |
Mate Toth-Pal | b9057ff | 2022-04-29 16:03:21 +0200 | [diff] [blame] | 147 | def _read_hmac_key(keyfile): |
Thomas Fossati | f4e1ca3 | 2024-08-16 16:01:31 +0000 | [diff] [blame^] | 148 | k = open(keyfile, 'rb').read() |
| 149 | d = { |
| 150 | 'KTY': 'SYMMETRIC', |
| 151 | 'ALG': HMAC256, |
| 152 | 'K': k, |
| 153 | } |
| 154 | return CoseKey.from_dict(d) |