blob: 45bce73804143ac691a2c597257609f4773d813a [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))):
207 # TODO -- asumes dict elements
208 value = [_parse_raw_token(v) for v in raw_value]
209 else:
210 value = claim_class.parse_raw(raw_value)
211
212 result[key] = value
213
214 return result
215
216def _format_value(names, key, value):
217 if key in names:
218 value = names[key].get_formatted_value(value)
219 return value
220
221def _relabel_keys(token_map):
222 result = {}
223 while not hasattr(token_map, 'items'):
224 # TODO: token map is not a map. We are assuming that it is a tag
225 token_map = token_map.value
226 names = {v.get_claim_key(): v for v in _get_known_claims()}
227 for key, value in token_map.items():
228 if hasattr(value, 'items'):
229 value = _relabel_keys(value)
230 elif (isinstance(value, Iterable) and
231 not isinstance(value, (str, bytes))):
232 new_value = []
233 for item in value:
234 if hasattr(item, 'items'):
235 new_value.append(_relabel_keys(item))
236 else:
237 new_value.append(_format_value(names, key, item))
238 value = new_value
239 else:
240 value = _format_value(names, key, value)
241
242 if key in names:
243 new_key = names[key].get_claim_name().lower()
244 else:
245 new_key = key
246 result[new_key] = value
247 return result