blob: 39af2e392da7b9be14600605f9baa0997d7ae172 [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
Mate Toth-Palbdb475e2022-04-24 12:11:22 +020016from pycose.attributes import CoseAttrs
Mate Toth-Pal51b61982022-03-17 14:19:30 +010017from pycose.sign1message import Sign1Message
18from pycose.mac0message import Mac0Message
Mate Toth-Palbb187d02022-04-26 16:01:51 +020019from iatverifier.attest_token_verifier import AttestationTokenVerifier
Mate Toth-Pal51b61982022-03-17 14:19:30 +010020from cbor2 import CBORTag
21
22_logger = logging.getLogger("util")
23
Mate Toth-Palbdb475e2022-04-24 12:11:22 +020024def sign_eat(token, verifier, *, add_p_header, key=None):
25 protected_header = CoseAttrs()
26 if add_p_header and key:
27 protected_header['alg'] = verifier.cose_alg
28 signed_msg = Sign1Message(p_header=protected_header)
Mate Toth-Pal51b61982022-03-17 14:19:30 +010029 signed_msg.payload = token
30 if key:
31 signed_msg.key = key
Mate Toth-Pala7a97172022-03-24 16:43:22 +010032 signed_msg.signature = signed_msg.compute_signature(alg=verifier.cose_alg)
Mate Toth-Pal51b61982022-03-17 14:19:30 +010033 return signed_msg.encode()
34
35
Mate Toth-Palbdb475e2022-04-24 12:11:22 +020036def hmac_eat(token, verifier, *, add_p_header, key=None):
37 protected_header = CoseAttrs()
38 if add_p_header and key:
39 protected_header['alg'] = verifier.cose_alg
40 hmac_msg = Mac0Message(payload=token, key=key, p_header=protected_header)
Mate Toth-Pala7a97172022-03-24 16:43:22 +010041 hmac_msg.compute_auth_tag(alg=verifier.cose_alg)
Mate Toth-Pal51b61982022-03-17 14:19:30 +010042 return hmac_msg.encode()
43
44
Mate Toth-Palbdb475e2022-04-24 12:11:22 +020045def convert_map_to_token_files(mapfile, keyfile, verifier, outfile, add_p_header):
Mate Toth-Pal51b61982022-03-17 14:19:30 +010046 token_map = read_token_map(mapfile)
47
48 if verifier.method == 'sign':
49 with open(keyfile) as fh:
50 signing_key = SigningKey.from_pem(fh.read())
51 else:
52 with open(keyfile, 'rb') as fh:
53 signing_key = fh.read()
54
55 with open(outfile, 'wb') as wfh:
Mate Toth-Palbdb475e2022-04-24 12:11:22 +020056 convert_map_to_token(token_map, signing_key, verifier, wfh, add_p_header)
Mate Toth-Pal51b61982022-03-17 14:19:30 +010057
58
Mate Toth-Palbdb475e2022-04-24 12:11:22 +020059def convert_map_to_token(token_map, signing_key, verifier, wfh, add_p_header):
Mate Toth-Pal51b61982022-03-17 14:19:30 +010060 wrapping_tag = verifier.get_wrapping_tag()
61 if wrapping_tag is not None:
62 token = cbor2.dumps(CBORTag(wrapping_tag, token_map))
63 else:
64 token = cbor2.dumps(token_map)
65
66 if verifier.method == AttestationTokenVerifier.SIGN_METHOD_RAW:
67 signed_token = token
68 elif verifier.method == AttestationTokenVerifier.SIGN_METHOD_SIGN1:
Mate Toth-Palbdb475e2022-04-24 12:11:22 +020069 signed_token = sign_eat(token, verifier, add_p_header=add_p_header, key=signing_key)
Mate Toth-Pal51b61982022-03-17 14:19:30 +010070 elif verifier.method == AttestationTokenVerifier.SIGN_METHOD_MAC0:
Mate Toth-Palbdb475e2022-04-24 12:11:22 +020071 signed_token = hmac_eat(token, verifier, add_p_header=add_p_header, key=signing_key)
Mate Toth-Pal51b61982022-03-17 14:19:30 +010072 else:
73 err_msg = 'Unexpected method "{}"; must be one of: raw, sign, mac'
74 raise ValueError(err_msg.format(method))
75
76 wfh.write(signed_token)
77
78
79def convert_token_to_map(raw_data, verifier):
Mate Toth-Palbdb475e2022-04-24 12:11:22 +020080 payload = get_cose_payload(raw_data, verifier, check_p_header=False)
Mate Toth-Pal51b61982022-03-17 14:19:30 +010081 token_map = cbor2.loads(payload)
82 return _relabel_keys(token_map)
83
84
85def read_token_map(f):
86 if hasattr(f, 'read'):
87 raw = yaml.safe_load(f)
88 else:
89 with open(f) as fh:
90 raw = yaml.safe_load(fh)
91
92 return _parse_raw_token(raw)
93
94
Mate Toth-Palbdb475e2022-04-24 12:11:22 +020095def extract_iat_from_cose(keyfile, tokenfile, verifier, check_p_header):
Mate Toth-Pal51b61982022-03-17 14:19:30 +010096 key = read_keyfile(keyfile, verifier.method)
97
98 try:
99 with open(tokenfile, 'rb') as wfh:
Mate Toth-Palbdb475e2022-04-24 12:11:22 +0200100 return get_cose_payload(wfh.read(), verifier, check_p_header=check_p_header, key=key)
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100101 except Exception as e:
102 msg = 'Bad COSE file "{}": {}'
103 raise ValueError(msg.format(tokenfile, e))
104
105
Mate Toth-Palbdb475e2022-04-24 12:11:22 +0200106def get_cose_payload(cose, verifier, *, check_p_header, key=None):
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100107 if verifier.method == AttestationTokenVerifier.SIGN_METHOD_SIGN1:
Mate Toth-Palbdb475e2022-04-24 12:11:22 +0200108 return get_cose_sign1_payload(cose, verifier, check_p_header=check_p_header, key=key)
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100109 if verifier.method == AttestationTokenVerifier.SIGN_METHOD_MAC0:
Mate Toth-Palbdb475e2022-04-24 12:11:22 +0200110 return get_cose_mac0_payload(cose, verifier, check_p_header=check_p_header, key=key)
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100111 err_msg = 'Unexpected method "{}"; must be one of: sign, mac'
Mate Toth-Palbdb475e2022-04-24 12:11:22 +0200112 raise ValueError(err_msg.format(verifier.method))
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100113
Mate Toth-Palbdb475e2022-04-24 12:11:22 +0200114def parse_protected_header(msg, alg):
115 try:
116 msg_alg = msg.protected_header['alg']
117 except KeyError:
118 raise ValueError('Missing alg from protected header (expected {})'.format(alg))
119 if alg != msg_alg:
120 raise ValueError('Unexpected alg in protected header (expected {} instead of {})'.format(alg, msg_alg))
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100121
Mate Toth-Palbdb475e2022-04-24 12:11:22 +0200122def get_cose_sign1_payload(cose, verifier, *, check_p_header, key=None):
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100123 msg = Sign1Message.decode(cose)
124 if key:
Mate Toth-Palbdb475e2022-04-24 12:11:22 +0200125 if check_p_header:
126 parse_protected_header(msg, verifier.cose_alg)
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100127 msg.key = key
128 msg.signature = msg.signers
129 try:
130 msg.verify_signature(alg=verifier.cose_alg)
131 except Exception as e:
132 raise ValueError('Bad signature ({})'.format(e))
133 return msg.payload
134
135
Mate Toth-Palbdb475e2022-04-24 12:11:22 +0200136def get_cose_mac0_payload(cose, verifier, *, check_p_header, key=None):
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100137 msg = Mac0Message.decode(cose)
138 if key:
Mate Toth-Palbdb475e2022-04-24 12:11:22 +0200139 if check_p_header:
140 parse_protected_header(msg, verifier.cose_alg)
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100141 msg.key = key
142 try:
143 msg.verify_auth_tag(alg=verifier.cose_alg)
144 except Exception as e:
145 raise ValueError('Bad signature ({})'.format(e))
146 return msg.payload
147
148def recursive_bytes_to_strings(d, in_place=False):
149 if in_place:
150 result = d
151 else:
152 result = deepcopy(d)
153
154 if hasattr(result, 'items'):
155 for k, v in result.items():
156 result[k] = recursive_bytes_to_strings(v, in_place=True)
157 elif (isinstance(result, Iterable) and
158 not isinstance(result, (str, bytes))):
159 result = [recursive_bytes_to_strings(r, in_place=True)
160 for r in result]
161 elif isinstance(result, bytes):
162 result = str(base64.b16encode(result))
163
164 return result
165
166
167def read_keyfile(keyfile, method=AttestationTokenVerifier.SIGN_METHOD_SIGN1):
168 if keyfile:
169 if method == AttestationTokenVerifier.SIGN_METHOD_SIGN1:
170 return read_sign1_key(keyfile)
171 if method == AttestationTokenVerifier.SIGN_METHOD_MAC0:
172 return read_hmac_key(keyfile)
173 err_msg = 'Unexpected method "{}"; must be one of: sign, mac'
174 raise ValueError(err_msg.format(method))
175
176 return None
177
178
179def read_sign1_key(keyfile):
180 try:
181 key = SigningKey.from_pem(open(keyfile, 'rb').read())
182 except Exception as e:
183 signing_key_error = str(e)
184
185 try:
186 key = VerifyingKey.from_pem(open(keyfile, 'rb').read())
187 except Exception as e:
188 verifying_key_error = str(e)
189
190 msg = 'Bad key file "{}":\n\tpubkey error: {}\n\tprikey error: {}'
191 raise ValueError(msg.format(keyfile, verifying_key_error, signing_key_error))
192 return key
193
194
195def read_hmac_key(keyfile):
196 return open(keyfile, 'rb').read()
197
198def _get_known_claims():
199 if logging.DEBUG >= logging.root.level:
200 _logger.debug("Known claims are:")
201 for _, claim_class in AttestationTokenVerifier.all_known_claims.items():
202 _logger.debug(f" {claim_class.get_claim_key():8} '{claim_class.get_claim_name()}'")
203 for _, claim_class in AttestationTokenVerifier.all_known_claims.items():
204 yield claim_class
205
206def _parse_raw_token(raw):
207 result = {}
208 field_names = {cc.get_claim_name(): cc for cc in _get_known_claims()}
209 for raw_key, raw_value in raw.items():
210 if isinstance(raw_key, int):
211 key = raw_key
212 else:
213 field_name = raw_key.upper()
214 try:
215 claim_class = field_names[field_name]
216 key = claim_class.get_claim_key()
217 except KeyError:
218 msg = 'Unknown field "{}" in token.'.format(field_name)
219 raise ValueError(msg)
220
221 if hasattr(raw_value, 'items'):
222 value = _parse_raw_token(raw_value)
223 elif (isinstance(raw_value, Iterable) and
224 not isinstance(raw_value, (str, bytes))):
Mate Toth-Pal2578fd52022-03-24 16:44:29 +0100225 value = []
226 for v in raw_value:
227 if hasattr(v, 'items'):
228 value.append(_parse_raw_token(v))
229 else:
230 value.append(claim_class.parse_raw(v))
Mate Toth-Pal51b61982022-03-17 14:19:30 +0100231 else:
232 value = claim_class.parse_raw(raw_value)
233
234 result[key] = value
235
236 return result
237
238def _format_value(names, key, value):
239 if key in names:
240 value = names[key].get_formatted_value(value)
241 return value
242
243def _relabel_keys(token_map):
244 result = {}
245 while not hasattr(token_map, 'items'):
246 # TODO: token map is not a map. We are assuming that it is a tag
247 token_map = token_map.value
248 names = {v.get_claim_key(): v for v in _get_known_claims()}
249 for key, value in token_map.items():
250 if hasattr(value, 'items'):
251 value = _relabel_keys(value)
252 elif (isinstance(value, Iterable) and
253 not isinstance(value, (str, bytes))):
254 new_value = []
255 for item in value:
256 if hasattr(item, 'items'):
257 new_value.append(_relabel_keys(item))
258 else:
259 new_value.append(_format_value(names, key, item))
260 value = new_value
261 else:
262 value = _format_value(names, key, value)
263
264 if key in names:
265 new_key = names[key].get_claim_name().lower()
266 else:
267 new_key = key
268 result[new_key] = value
269 return result