CCF
Loading...
Searching...
No Matches
cose_common.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3
4#pragma once
5
6#include "ccf/ds/hex.h"
7#include "ccf/receipt.h"
8
9#include <crypto/cbor.h>
10#include <crypto/cose.h>
11#include <crypto/cose_utils.h>
12#include <optional>
13#include <stdexcept>
14#include <string>
15#include <variant>
16
17namespace ccf::cose
18{
19
20 using Signature = std::span<const uint8_t>;
21
22 static bool is_ecdsa_alg(int64_t cose_alg)
23 {
24 // https://www.iana.org/assignments/cose/cose.xhtml
25 constexpr int COSE_ALGORITHM_ES256 = -7;
26 constexpr int COSE_ALGORITHM_ES384 = -35;
27 constexpr int COSE_ALGORITHM_ES512 = -36;
28 return cose_alg == COSE_ALGORITHM_ES256 ||
29 cose_alg == COSE_ALGORITHM_ES384 || cose_alg == COSE_ALGORITHM_ES512;
30 }
31
32 static bool is_rsa_alg(int64_t cose_alg)
33 {
34 // https: // www.iana.org/assignments/cose/cose.xhtml
35 constexpr int COSE_ALGORITHM_PS256 = -37;
36 constexpr int COSE_ALGORITHM_PS384 = -38;
37 constexpr int COSE_ALGORITHM_PS512 = -39;
38 return cose_alg == COSE_ALGORITHM_PS256 ||
39 cose_alg == COSE_ALGORITHM_PS384 || cose_alg == COSE_ALGORITHM_PS512;
40 }
41
42 struct COSEDecodeError : public std::runtime_error
43 {
44 COSEDecodeError(const std::string& msg) : std::runtime_error(msg) {}
45 };
46
47 struct COSESignatureValidationError : public std::runtime_error
48 {
49 COSESignatureValidationError(const std::string& msg) :
50 std::runtime_error(msg)
51 {}
52 };
53
54 struct CwtClaims
55 {
56 std::optional<int64_t> iat;
57 std::string iss;
58 std::string sub;
59 std::optional<int64_t> svn;
60 };
61
63 {
64 int64_t alg{};
65 std::optional<std::variant<int64_t, std::string>> cty;
66 std::vector<std::vector<uint8_t>> x5chain;
68 };
69
70 static void decode_cwt_claims(const ccf::cbor::Value& cbor, CwtClaims& claims)
71 {
72 using namespace ccf::cbor;
73
74 const auto& cwt_claims = rethrow_with_msg(
75 [&]() -> auto& {
76 return cbor->map_at(make_signed(ccf::cose::header::iana::CWT_CLAIMS));
77 },
78 "Parse CWT claims map");
79
80 try
81 {
82 claims.iat = cwt_claims->map_at(make_signed(ccf::cwt::header::iana::IAT))
83 ->as_signed();
84 }
85 catch (const CBORDecodeError& err)
86 {
87 std::ignore = err; // optional field
88 }
89
90 claims.iss = rethrow_with_msg(
91 [&]() {
92 return cwt_claims->map_at(make_signed(ccf::cwt::header::iana::ISS))
93 ->as_string();
94 },
95 fmt::format(
96 "Parse CWT claim iss({}) field", ccf::cwt::header::iana::ISS));
97
98 claims.sub = rethrow_with_msg(
99 [&]() {
100 return cwt_claims->map_at(make_signed(ccf::cwt::header::iana::SUB))
101 ->as_string();
102 },
103 fmt::format(
104 "Parse CWT claim sub({}) field", ccf::cwt::header::iana::SUB));
105
106 try
107 {
108 claims.svn =
109 cwt_claims->map_at(make_string(ccf::cwt::header::custom::SVN))
110 ->as_signed();
111 }
112 catch (const CBORDecodeError& err)
113 {
114 std::ignore = err; // optional field
115 }
116 }
117
118 static Sign1ProtectedHeader decode_sign1_protected_header(
119 const ccf::cbor::Value& phdr)
120 {
121 using namespace ccf::cbor;
122 Sign1ProtectedHeader hdr;
123
124 hdr.alg = rethrow_with_msg(
125 [&]() {
126 return phdr->map_at(make_signed(ccf::cose::header::iana::ALG))
127 ->as_signed();
128 },
129 fmt::format(
130 "Parse protected header alg({})", ccf::cose::header::iana::ALG));
131
132 try
133 {
134 const auto& cty =
135 phdr->map_at(make_signed(ccf::cose::header::iana::CONTENT_TYPE));
136 try
137 {
138 hdr.cty = std::string(cty->as_string());
139 }
140 catch (const CBORDecodeError&)
141 {
142 hdr.cty = cty->as_signed();
143 }
144 }
145 catch (const CBORDecodeError& err)
146 {
147 std::ignore = err; // optional field
148 }
149
150 hdr.x5chain = rethrow_with_msg(
151 [&]() {
152 const auto& x5chain_val =
153 phdr->map_at(make_signed(ccf::cose::header::iana::X5CHAIN));
154 return ccf::cose::utils::parse_x5chain(x5chain_val);
155 },
156 fmt::format(
157 "Parse protected header x5chain({})",
158 ccf::cose::header::iana::X5CHAIN));
159
160 decode_cwt_claims(phdr, hdr.cwt);
161
162 return hdr;
163 }
164
166 {
167 std::string txid;
168 };
169
171 {
172 int alg{};
173 std::vector<uint8_t> kid;
176 int vds{};
177 };
178
179 struct Leaf
180 {
181 std::vector<uint8_t> write_set_digest;
182 std::string commit_evidence;
183 std::vector<uint8_t> claims_digest;
184 };
185
187 {
189 std::vector<std::pair<int64_t, std::vector<uint8_t>>> path;
190 };
191
193 {
195 std::vector<uint8_t> merkle_root;
196 std::vector<uint8_t> claims_digest;
197 };
198
199 static std::vector<uint8_t> recompute_merkle_root(const MerkleProof& proof)
200 {
201 auto ce_digest = ccf::crypto::Sha256Hash(proof.leaf.commit_evidence);
202
204 {
205 throw COSEDecodeError(fmt::format(
206 "Unsupported write set digest size in Merkle proof leaf: {}",
207 proof.leaf.write_set_digest.size()));
208 }
210 {
211 throw COSEDecodeError(fmt::format(
212 "Unsupported claims digest size in Merkle proof leaf: {}",
213 proof.leaf.claims_digest.size()));
214 }
215
216 std::span<const uint8_t, ccf::crypto::Sha256Hash::SIZE> wsd{
218 std::span<const uint8_t, ccf::crypto::Sha256Hash::SIZE> cd{
220 auto leaf_digest = ccf::crypto::Sha256Hash(
222 ce_digest,
224
225 for (const auto& element : proof.path)
226 {
227 if (element.first != 0)
228 {
229 std::span<const uint8_t, ccf::crypto::Sha256Hash::SIZE> sibling{
231 leaf_digest = ccf::crypto::Sha256Hash(
232 ccf::crypto::Sha256Hash::from_span(sibling), leaf_digest);
233 }
234 else
235 {
236 std::span<const uint8_t, ccf::crypto::Sha256Hash::SIZE> sibling{
238 leaf_digest = ccf::crypto::Sha256Hash(
239 leaf_digest, ccf::crypto::Sha256Hash::from_span(sibling));
240 }
241 }
242
243 return {leaf_digest.h.begin(), leaf_digest.h.end()};
244 }
245
246 static void decode_ccf_claims(const ccf::cbor::Value& cbor, CcfClaims& claims)
247 {
248 using namespace ccf::cbor;
249
250 const auto& ccf_claims = rethrow_with_msg(
251 [&]() -> auto& {
252 return cbor->map_at(make_string(ccf::cose::header::custom::CCF_V1));
253 },
254 "Parse CCF claims map");
255
256 claims.txid = rethrow_with_msg(
257 [&]() {
258 return ccf_claims->map_at(make_string(ccf::cose::header::custom::TX_ID))
259 ->as_string();
260 },
261 fmt::format(
262 "Parse CCF claims TxID ({}) field", ccf::cose::header::custom::TX_ID));
263 }
264
265 static CcfCoseReceiptPhdr decode_ccf_receipt_phdr(ccf::cbor::Value& cbor)
266 {
267 using namespace ccf::cbor;
268
269 CcfCoseReceiptPhdr phdr{};
270
271 phdr.alg = rethrow_with_msg(
272 [&]() {
273 return cbor->map_at(make_signed(ccf::cose::header::iana::ALG))
274 ->as_signed();
275 },
276 fmt::format(
277 "Parse protected header alg({})", ccf::cose::header::iana::ALG));
278
280 [&]() {
281 const auto& bytes =
282 cbor->map_at(make_signed(ccf::cose::header::iana::KID))->as_bytes();
283 phdr.kid.assign(bytes.begin(), bytes.end());
284 },
285 fmt::format(
286 "Parse protected header kid({})", ccf::cose::header::iana::KID));
287
288 phdr.vds = rethrow_with_msg(
289 [&]() {
290 return cbor->map_at(make_signed(ccf::cose::header::iana::VDS))
291 ->as_signed();
292 },
293 fmt::format(
294 "Parse protected header vds({})", ccf::cose::header::iana::VDS));
295
296 if (phdr.vds != ccf::cose::value::CCF_LEDGER_SHA256)
297 {
298 throw COSEDecodeError(fmt::format(
299 "Unsupported vds value ({}) in protected header", phdr.vds));
300 }
301
302 decode_cwt_claims(cbor, phdr.cwt);
303 decode_ccf_claims(cbor, phdr.ccf);
304
305 return phdr;
306 }
307
308 static std::vector<MerkleProof> decode_merkle_proofs(
309 const ccf::cbor::Value& cbor)
310 {
311 using namespace ccf::cbor;
312
313 const auto& uhdr = rethrow_with_msg(
314 [&]() -> auto& { return cbor->array_at(1); },
315 "Parse unprotected header map");
316
317 const auto& vdp = rethrow_with_msg(
318 [&]() -> auto& {
319 return uhdr->map_at(make_signed(ccf::cose::header::iana::VDP));
320 },
321 fmt::format("Parse vdp() map", ccf::cose::header::iana::VDP));
322
323 const auto& proofs_array = rethrow_with_msg(
324 [&]() -> auto& {
325 return vdp->map_at(
326 make_signed(ccf::cose::header::iana::INCLUSION_PROOFS));
327 },
328 "Parse inclusion proofs");
329
330 std::vector<MerkleProof> proofs;
331
333 [&]() {
334 if (proofs_array->size() == 0)
335 {
336 throw CBORDecodeError(Error::DECODE_FAILED, "Empty proofs array");
337 }
338 },
339 "Check proofs array");
340
341 for (size_t i = 0; i < proofs_array->size(); ++i)
342 {
343 auto cbor_proof = rethrow_with_msg(
344 [&]() { return parse(proofs_array->array_at(i)->as_bytes()); },
345 "Parse an encoded proof");
346
347 const auto& leaf = rethrow_with_msg(
348 [&]() -> auto& {
349 return cbor_proof->map_at(
351 },
352 "Parse proof: leaf");
353
354 MerkleProof proof;
355
357 [&]() {
358 const auto& bytes =
359 leaf->array_at(ccf::MerkleProofPathBranch::LEFT)->as_bytes();
360 proof.leaf.write_set_digest.assign(bytes.begin(), bytes.end());
361 },
362 "Parse leaf at wsd");
363
364 proof.leaf.commit_evidence = rethrow_with_msg(
365 [&]() {
366 return leaf->array_at(ccf::MerkleProofPathBranch::RIGHT)->as_string();
367 },
368 "Parse leaf at ce");
369
371 [&]() {
372 const auto& bytes = leaf->array_at(2)->as_bytes();
373 proof.leaf.claims_digest.assign(bytes.begin(), bytes.end());
374 },
375 "Parse leaf at cd");
376
377 const auto& cbor_path = rethrow_with_msg(
378 [&]() -> auto& {
379 return cbor_proof->map_at(
381 },
382 "Parse proof: path");
383
385 [&]() {
386 if (cbor_path->size() == 0)
387 {
388 throw CBORDecodeError(Error::DECODE_FAILED, "Empty path");
389 }
390 },
391 "Check proof: path");
392
393 for (size_t j = 0; j < cbor_path->size(); j++)
394 {
395 std::pair<int64_t, std::vector<uint8_t>> path_item;
396 const auto& link = rethrow_with_msg(
397 [&]() -> auto& { return cbor_path->array_at(j); }, "Parse path link");
398
399 path_item.first = static_cast<int64_t>(rethrow_with_msg(
400 [&]() { return simple_to_boolean(link->array_at(0)->as_simple()); },
401 "Parse path element at direction"));
403 [&]() {
404 const auto& bytes = link->array_at(1)->as_bytes();
405 path_item.second.assign(bytes.begin(), bytes.end());
406 },
407 "Parse path element at hash");
408 proof.path.push_back(path_item);
409 }
410
411 proofs.push_back(proof);
412 }
413
414 return proofs;
415 }
416
417 static CcfCoseReceipt decode_ccf_receipt(
418 const std::vector<uint8_t>& cose_sign1, bool recompute_root)
419 {
420 using namespace ccf::cbor;
421
422 auto cose_cbor =
423 rethrow_with_msg([&]() { return parse(cose_sign1); }, "Parse COSE CBOR");
424
425 const auto& cose_envelope = rethrow_with_msg(
426 [&]() -> auto& { return cose_cbor->tag_at(ccf::cbor::tag::COSE_SIGN_1); },
427 "Parse COSE tag");
428
429 const auto& phdr_raw = rethrow_with_msg(
430 [&]() -> auto& { return cose_envelope->array_at(0); },
431 "Parse raw protected header");
432
433 auto phdr = rethrow_with_msg(
434 [&]() { return parse(phdr_raw->as_bytes()); }, "Parse protected header");
435
436 CcfCoseReceipt receipt;
437
438 receipt.phdr = decode_ccf_receipt_phdr(phdr);
439
440 if (recompute_root)
441 {
442 auto proofs = decode_merkle_proofs(cose_envelope);
443 if (proofs.empty())
444 {
445 throw COSEDecodeError("No Merkle proofs found in COSE receipt");
446 }
447
448 receipt.merkle_root = recompute_merkle_root(proofs[0]);
449 receipt.claims_digest = proofs[0].leaf.claims_digest;
450 for (size_t i = 1; i < proofs.size(); ++i)
451 {
452 auto root = recompute_merkle_root(proofs[i]);
453 if (root != receipt.merkle_root)
454 {
455 throw COSEDecodeError(
456 "Inconsistent Merkle roots computed from COSE receipt proofs");
457 }
458 if (proofs[i].leaf.claims_digest != receipt.claims_digest)
459 {
460 throw COSEDecodeError(fmt::format(
461 "Claims from proofs don't match: {} != {}",
462 ds::to_hex(receipt.claims_digest),
463 ds::to_hex(proofs[i].leaf.claims_digest)));
464 }
465 }
466 }
467
468 return receipt;
469 }
470}
Definition cbor.h:84
Definition sha256_hash.h:16
static Sha256Hash from_span(const std::span< const uint8_t, SIZE > &sp)
Definition sha256_hash.cpp:69
static constexpr size_t SIZE
Definition sha256_hash.h:18
Definition cbor.cpp:503
decltype(auto) rethrow_with_msg(auto &&f, std::string_view msg={})
Definition cbor.h:123
std::shared_ptr< ValueImpl > Value
Definition cbor.h:26
std::vector< std::vector< uint8_t > > parse_x5chain(const ccf::cbor::Value &x5chain_value)
Definition cose_utils.h:10
Definition cose_signatures_config_interface.h:12
std::span< const uint8_t > Signature
Definition cose_common.h:20
uint64_t element
Definition sharing.cpp:20
Definition app_interface.h:13
@ RIGHT
Definition receipt.h:151
@ LEFT
Definition receipt.h:150
@ MERKLE_PROOF_LEAF_LABEL
Definition receipt.h:142
@ MERKLE_PROOF_PATH_LABEL
Definition receipt.h:143
STL namespace.
Definition cose_common.h:43
COSEDecodeError(const std::string &msg)
Definition cose_common.h:44
Definition cose_common.h:48
COSESignatureValidationError(const std::string &msg)
Definition cose_common.h:49
Definition cose_common.h:166
std::string txid
Definition cose_common.h:167
Definition cose_common.h:171
CwtClaims cwt
Definition cose_common.h:174
std::vector< uint8_t > kid
Definition cose_common.h:173
int vds
Definition cose_common.h:176
int alg
Definition cose_common.h:172
Definition cose_common.h:193
std::vector< uint8_t > merkle_root
Definition cose_common.h:195
std::vector< uint8_t > claims_digest
Definition cose_common.h:196
CcfCoseReceiptPhdr phdr
Definition cose_common.h:194
Definition cose_common.h:55
std::optional< int64_t > svn
Definition cose_common.h:59
std::string sub
Definition cose_common.h:58
std::optional< int64_t > iat
Definition cose_common.h:56
std::string iss
Definition cose_common.h:57
Definition cose_common.h:180
std::vector< uint8_t > claims_digest
Definition cose_common.h:183
std::string commit_evidence
Definition cose_common.h:182
std::vector< uint8_t > write_set_digest
Definition cose_common.h:181
Definition cose_common.h:187
std::vector< std::pair< int64_t, std::vector< uint8_t > > > path
Definition cose_common.h:189
Leaf leaf
Definition cose_common.h:188
Definition cose_common.h:63
std::vector< std::vector< uint8_t > > x5chain
Definition cose_common.h:66
CwtClaims cwt
Definition cose_common.h:67
std::optional< std::variant< int64_t, std::string > > cty
Definition cose_common.h:65
int64_t alg
Definition cose_common.h:64