blob: 1025355e8fd516eaf79c780e4fd6c1bb8aa1eac3 [file] [log] [blame]
Mate Toth-Pal51b61982022-03-17 14:19:30 +01001# -----------------------------------------------------------------------------
2# Copyright (c) 2019-2022, Arm Limited. All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5#
6# -----------------------------------------------------------------------------
7
8from collections.abc import Iterable
9from copy import deepcopy
10import logging
11
12import base64
13import cbor2
14import yaml
15from ecdsa import SigningKey, VerifyingKey
16from pycose.sign1message import Sign1Message
17from pycose.mac0message import Mac0Message
18from iatverifier.verifiers import AttestationTokenVerifier
19from cbor2 import CBORTag
20
21_logger = logging.getLogger("util")
22
Mate Toth-Pala7a97172022-03-24 16:43:22 +010023def sign_eat(token, verifier, key=None):
Mate Toth-Pal51b61982022-03-17 14:19:30 +010024 signed_msg = Sign1Message()
25 signed_msg.payload = token
26 if key:
27 signed_msg.key = key
Mate Toth-Pala7a97172022-03-24 16:43:22 +010028 signed_msg.signature = signed_msg.compute_signature(alg=verifier.cose_alg)
Mate Toth-Pal51b61982022-03-17 14:19:30 +010029 return signed_msg.encode()
30
31
32def hmac_eat(token, verifier, key=None):
33 hmac_msg = Mac0Message(payload=token, key=key)
Mate Toth-Pala7a97172022-03-24 16:43:22 +010034 hmac_msg.compute_auth_tag(alg=verifier.cose_alg)
Mate Toth-Pal51b61982022-03-17 14:19:30 +010035 return hmac_msg.encode()
36
37
38def convert_map_to_token_files(mapfile, keyfile, verifier, outfile):
39 token_map = read_token_map(mapfile)
40
41 if verifier.method == 'sign':
42 with open(keyfile) as fh:
43 signing_key = SigningKey.from_pem(fh.read())
44 else:
45 with open(keyfile, 'rb') as fh:
46 signing_key = fh.read()
47
48 with open(outfile, 'wb') as wfh:
49 convert_map_to_token(token_map, signing_key, verifier, wfh)
50
51
52def convert_map_to_token(token_map, signing_key, verifier, wfh):
53 wrapping_tag = verifier.get_wrapping_tag()
54 if wrapping_tag is not None:
55 token = cbor2.dumps(CBORTag(wrapping_tag, token_map))
56 else:
57 token = cbor2.dumps(token_map)
58
59 if verifier.method == AttestationTokenVerifier.SIGN_METHOD_RAW:
60 signed_token = token
61 elif verifier.method == AttestationTokenVerifier.SIGN_METHOD_SIGN1:
Mate Toth-Pala7a97172022-03-24 16:43:22 +010062 signed_token = sign_eat(token, verifier, signing_key)
Mate Toth-Pal51b61982022-03-17 14:19:30 +010063 elif verifier.method == AttestationTokenVerifier.SIGN_METHOD_MAC0:
64 signed_token = hmac_eat(token, verifier, signing_key)
65 else:
66 err_msg = 'Unexpected method "{}"; must be one of: raw, sign, mac'
67 raise ValueError(err_msg.format(method))
68
69 wfh.write(signed_token)
70
71
72def convert_token_to_map(raw_data, verifier):
73 payload = get_cose_payload(raw_data, verifier)
74 token_map = cbor2.loads(payload)
75 return _relabel_keys(token_map)
76
77
78def read_token_map(f):
79 if hasattr(f, 'read'):
80 raw = yaml.safe_load(f)
81 else:
82 with open(f) as fh:
83 raw = yaml.safe_load(fh)
84
85 return _parse_raw_token(raw)
86
87
88def extract_iat_from_cose(keyfile, tokenfile, verifier):
89 key = read_keyfile(keyfile, verifier.method)
90
91 try:
92 with open(tokenfile, 'rb') as wfh:
93 return get_cose_payload(wfh.read(), verifier, key)
94 except Exception as e:
95 msg = 'Bad COSE file "{}": {}'
96 raise ValueError(msg.format(tokenfile, e))
97
98
99def get_cose_payload(cose, verifier, key=None):
100 if verifier.method == AttestationTokenVerifier.SIGN_METHOD_SIGN1:
101 return get_cose_sign1_payload(cose, verifier, key)
102 if verifier.method == AttestationTokenVerifier.SIGN_METHOD_MAC0:
103 return get_cose_mac0_pyload(cose, verifier, key)
104 err_msg = 'Unexpected method "{}"; must be one of: sign, mac'
105 raise ValueError(err_msg.format(method))
106
107
108def get_cose_sign1_payload(cose, verifier, key=None):
109 msg = Sign1Message.decode(cose)
110 if key:
111 msg.key = key
112 msg.signature = msg.signers
113 try:
114 msg.verify_signature(alg=verifier.cose_alg)
115 except Exception as e:
116 raise ValueError('Bad signature ({})'.format(e))
117 return msg.payload
118
119
120def get_cose_mac0_pyload(cose, verifier, key=None):
121 msg = Mac0Message.decode(cose)
122 if key:
123 msg.key = key
124 try:
125 msg.verify_auth_tag(alg=verifier.cose_alg)
126 except Exception as e:
127 raise ValueError('Bad signature ({})'.format(e))
128 return msg.payload
129
130def recursive_bytes_to_strings(d, in_place=False):
131 if in_place:
132 result = d
133 else:
134 result = deepcopy(d)
135
136 if hasattr(result, 'items'):
137 for k, v in result.items():
138 result[k] = recursive_bytes_to_strings(v, in_place=True)
139 elif (isinstance(result, Iterable) and
140 not isinstance(result, (str, bytes))):
141 result = [recursive_bytes_to_strings(r, in_place=True)
142 for r in result]
143 elif isinstance(result, bytes):
144 result = str(base64.b16encode(result))
145
146 return result
147
148
149def read_keyfile(keyfile, method=AttestationTokenVerifier.SIGN_METHOD_SIGN1):
150 if keyfile:
151 if method == AttestationTokenVerifier.SIGN_METHOD_SIGN1:
152 return read_sign1_key(keyfile)
153 if method == AttestationTokenVerifier.SIGN_METHOD_MAC0:
154 return read_hmac_key(keyfile)
155 err_msg = 'Unexpected method "{}"; must be one of: sign, mac'
156 raise ValueError(err_msg.format(method))
157
158 return None
159
160
161def read_sign1_key(keyfile):
162 try:
163 key = SigningKey.from_pem(open(keyfile, 'rb').read())
164 except Exception as e:
165 signing_key_error = str(e)
166
167 try:
168 key = VerifyingKey.from_pem(open(keyfile, 'rb').read())
169 except Exception as e:
170 verifying_key_error = str(e)
171
172 msg = 'Bad key file "{}":\n\tpubkey error: {}\n\tprikey error: {}'
173 raise ValueError(msg.format(keyfile, verifying_key_error, signing_key_error))
174 return key
175
176
177def read_hmac_key(keyfile):
178 return open(keyfile, 'rb').read()
179
180def _get_known_claims():
181 if logging.DEBUG >= logging.root.level:
182 _logger.debug("Known claims are:")
183 for _, claim_class in AttestationTokenVerifier.all_known_claims.items():
184 _logger.debug(f" {claim_class.get_claim_key():8} '{claim_class.get_claim_name()}'")
185 for _, claim_class in AttestationTokenVerifier.all_known_claims.items():
186 yield claim_class
187
188def _parse_raw_token(raw):
189 result = {}
190 field_names = {cc.get_claim_name(): cc for cc in _get_known_claims()}
191 for raw_key, raw_value in raw.items():
192 if isinstance(raw_key, int):
193 key = raw_key
194 else:
195 field_name = raw_key.upper()
196 try:
197 claim_class = field_names[field_name]
198 key = claim_class.get_claim_key()
199 except KeyError:
200 msg = 'Unknown field "{}" in token.'.format(field_name)
201 raise ValueError(msg)
202
203 if hasattr(raw_value, 'items'):
204 value = _parse_raw_token(raw_value)
205 elif (isinstance(raw_value, Iterable) and
206 not isinstance(raw_value, (str, bytes))):
Mate Toth-Pal2578fd52022-03-24 16:44:29 +0100207 value = []
208 for v in raw_value:
209 if hasattr(v, 'items'):
210 value.append(_parse_raw_token(v))
211 else:
212 value.append(claim_class.parse_raw(v))
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100213 else:
214 value = claim_class.parse_raw(raw_value)
215
216 result[key] = value
217
218 return result
219
220def _format_value(names, key, value):
221 if key in names:
222 value = names[key].get_formatted_value(value)
223 return value
224
225def _relabel_keys(token_map):
226 result = {}
227 while not hasattr(token_map, 'items'):
228 # TODO: token map is not a map. We are assuming that it is a tag
229 token_map = token_map.value
230 names = {v.get_claim_key(): v for v in _get_known_claims()}
231 for key, value in token_map.items():
232 if hasattr(value, 'items'):
233 value = _relabel_keys(value)
234 elif (isinstance(value, Iterable) and
235 not isinstance(value, (str, bytes))):
236 new_value = []
237 for item in value:
238 if hasattr(item, 'items'):
239 new_value.append(_relabel_keys(item))
240 else:
241 new_value.append(_format_value(names, key, item))
242 value = new_value
243 else:
244 value = _format_value(names, key, value)
245
246 if key in names:
247 new_key = names[key].get_claim_name().lower()
248 else:
249 new_key = key
250 result[new_key] = value
251 return result