-
Notifications
You must be signed in to change notification settings - Fork 218
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add API to allow setting unprotected headers (#6586)
Co-authored-by: Max <[email protected]>
- Loading branch information
1 parent
da1b2ad
commit 1bf76dd
Showing
14 changed files
with
611 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
ccf-cose-root-signature-tagged = #6.18(ccf-cose-root-signature) | ||
|
||
ccf-cose-root-signature = [ | ||
phdr : bstr .cbor protected-headers, ; bstr-wrapped protected headers | ||
uhdr : unprotected-headers, ; unwrappeed (plain map) unprotected headers | ||
payload : nil, ; signed Merkle tree root hash, *detached* payload | ||
signature : bstr ; COSE-signature | ||
] | ||
|
||
unprotected-headers = { | ||
&(vdp: 396) => verifiable-proofs | ||
} | ||
|
||
inclusion-proofs = [ + bstr .cbor ccf-inclusion-proof ] | ||
|
||
verifiable-proofs = { | ||
&(inclusion-proof: -1) => inclusion-proofs | ||
} | ||
|
||
protected-headers = { | ||
&(alg: 1) => int, ; signing algoritm ID, as per RFC8152 | ||
&(kid: 4) => bstr, ; signing key hash | ||
&(cwt: 15) => cwt-map, ; CWT claims, as per RFC8392 | ||
&(vds: 395) => int, ; verifiable data structure, as per COSE Receipts (draft) RFC (https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs/) | ||
"ccf.v1" => ccf-map ; a set of CCF-specific parameters | ||
} | ||
|
||
cwt-map = { | ||
&(iat: 6) => int ; "issued at", number of seconds since the epoch | ||
} | ||
|
||
ccf-map = { | ||
&(last-signed-txid: "txid") => tstr ; last committed transaction ID this COSE-signature signs | ||
} | ||
|
||
ccf-inclusion-proof = { | ||
&(leaf: 1) => ccf-leaf | ||
&(path: 2) => [+ ccf-proof-element] | ||
} | ||
|
||
ccf-leaf = [ | ||
internal-transaction-hash: bstr .size 32 ; a string of HASH_SIZE(32) bytes | ||
internal-evidence: tstr .size (1..1024) ; a string of at most 1024 bytes | ||
data-hash: bstr .size 32 ; a string of HASH_SIZE(32) bytes | ||
] | ||
|
||
ccf-proof-element = [ | ||
left: bool ; position of the element | ||
hash: bstr .size 32 ; hash of the proof element (string of HASH_SIZE(32) bytes) | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the Apache 2.0 License. | ||
#pragma once | ||
|
||
#include <cstdint> | ||
#include <span> | ||
#include <variant> | ||
#include <vector> | ||
|
||
namespace ccf::cose::edit | ||
{ | ||
namespace pos | ||
{ | ||
struct InArray | ||
{}; | ||
|
||
struct AtKey | ||
{ | ||
int64_t key; | ||
}; | ||
|
||
using Type = std::variant<InArray, AtKey>; | ||
} | ||
|
||
/** | ||
* Set the unprotected header of a COSE_Sign1 message, to a map containing | ||
* @p key and depending on the value of @p position, either an array | ||
* containing | ||
* @p value, or a map with key @p subkey and value @p value. | ||
* | ||
* Useful to add a proof to a signature to turn it into a receipt, or to | ||
* add a receipt to a signed statement to turn it into a transparent | ||
* statement. | ||
* | ||
* @param cose_input The COSE_Sign1 message to edit. | ||
* @param key The key at which to insert either an array or a map. | ||
* @param position Either InArray or AtKey, to determine whether to insert an | ||
* array or a map. | ||
* | ||
* @return The COSE_Sign1 message with the new unprotected header. | ||
*/ | ||
std::vector<uint8_t> set_unprotected_header( | ||
const std::span<const uint8_t>& cose_input, | ||
int64_t key, | ||
pos::Type position, | ||
const std::vector<uint8_t> value); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the Apache 2.0 License. | ||
|
||
#include "ccf/crypto/cose.h" | ||
|
||
#include <optional> | ||
#include <qcbor/qcbor_decode.h> | ||
#include <qcbor/qcbor_encode.h> | ||
#include <qcbor/qcbor_spiffy_decode.h> | ||
#include <stdexcept> | ||
|
||
namespace ccf::cose::edit | ||
{ | ||
std::vector<uint8_t> set_unprotected_header( | ||
const std::span<const uint8_t>& cose_input, | ||
int64_t key, | ||
pos::Type pos, | ||
const std::vector<uint8_t> value) | ||
{ | ||
UsefulBufC buf{cose_input.data(), cose_input.size()}; | ||
|
||
QCBORError err; | ||
QCBORDecodeContext ctx; | ||
QCBORDecode_Init(&ctx, buf, QCBOR_DECODE_MODE_NORMAL); | ||
|
||
size_t pos_start = 0; | ||
size_t pos_end = 0; | ||
|
||
QCBORDecode_EnterArray(&ctx, nullptr); | ||
err = QCBORDecode_GetError(&ctx); | ||
if (err != QCBOR_SUCCESS) | ||
{ | ||
throw std::logic_error("Failed to parse COSE_Sign1 outer array"); | ||
} | ||
|
||
auto tag = QCBORDecode_GetNthTagOfLast(&ctx, 0); | ||
if (tag != CBOR_TAG_COSE_SIGN1) | ||
{ | ||
throw std::logic_error("Failed to parse COSE_Sign1 tag"); | ||
} | ||
|
||
QCBORItem item; | ||
err = QCBORDecode_GetNext(&ctx, &item); | ||
if (err != QCBOR_SUCCESS || item.uDataType != QCBOR_TYPE_BYTE_STRING) | ||
{ | ||
throw std::logic_error( | ||
"Failed to parse COSE_Sign1 protected header as bstr"); | ||
} | ||
UsefulBufC phdr = {item.val.string.ptr, item.val.string.len}; | ||
|
||
// Skip unprotected header | ||
QCBORDecode_VGetNextConsume(&ctx, &item); | ||
|
||
err = QCBORDecode_PartialFinish(&ctx, &pos_start); | ||
if (err != QCBOR_ERR_ARRAY_OR_MAP_UNCONSUMED) | ||
{ | ||
throw std::logic_error("Failed to find start of payload"); | ||
} | ||
QCBORDecode_VGetNextConsume(&ctx, &item); | ||
err = QCBORDecode_PartialFinish(&ctx, &pos_end); | ||
if (err != QCBOR_ERR_ARRAY_OR_MAP_UNCONSUMED) | ||
{ | ||
throw std::logic_error("Failed to find end of payload"); | ||
} | ||
UsefulBufC payload = {cose_input.data() + pos_start, pos_end - pos_start}; | ||
|
||
// QCBORDecode_PartialFinish() before and after should allow constructing a | ||
// span of the encoded payload, which can perhaps then be passed to | ||
// QCBOREncode_AddEncoded and would allow blindly copying the payload | ||
// without parsing it. | ||
|
||
err = QCBORDecode_GetNext(&ctx, &item); | ||
if (err != QCBOR_SUCCESS && item.uDataType != QCBOR_TYPE_BYTE_STRING) | ||
{ | ||
throw std::logic_error("Failed to parse COSE_Sign1 signature"); | ||
} | ||
UsefulBufC signature = {item.val.string.ptr, item.val.string.len}; | ||
|
||
QCBORDecode_ExitArray(&ctx); | ||
err = QCBORDecode_Finish(&ctx); | ||
if (err != QCBOR_SUCCESS) | ||
{ | ||
throw std::logic_error("Failed to parse COSE_Sign1"); | ||
} | ||
|
||
// Maximum expected size of the additional map, sub-map is the | ||
// worst-case scenario | ||
const size_t additional_map_size = QCBOR_HEAD_BUFFER_SIZE + // map | ||
QCBOR_HEAD_BUFFER_SIZE + // key | ||
sizeof(key) + // key | ||
QCBOR_HEAD_BUFFER_SIZE + // submap | ||
QCBOR_HEAD_BUFFER_SIZE + // subkey | ||
sizeof(pos::AtKey::key) + // subkey | ||
QCBOR_HEAD_BUFFER_SIZE + // value | ||
value.size(); // value | ||
|
||
// We add one extra QCBOR_HEAD_BUFFER_SIZE, because we parse and re-encode | ||
// the protected header bstr, which involves variable integer encoding, just | ||
// in case the library does not pick the most compact encoding. | ||
std::vector<uint8_t> output( | ||
cose_input.size() + additional_map_size + QCBOR_HEAD_BUFFER_SIZE); | ||
UsefulBuf output_buf{output.data(), output.size()}; | ||
|
||
QCBOREncodeContext ectx; | ||
QCBOREncode_Init(&ectx, output_buf); | ||
QCBOREncode_AddTag(&ectx, CBOR_TAG_COSE_SIGN1); | ||
QCBOREncode_OpenArray(&ectx); | ||
QCBOREncode_AddBytes(&ectx, phdr); | ||
QCBOREncode_OpenMap(&ectx); | ||
|
||
if (std::holds_alternative<pos::InArray>(pos)) | ||
{ | ||
QCBOREncode_OpenArrayInMapN(&ectx, key); | ||
QCBOREncode_AddBytes(&ectx, {value.data(), value.size()}); | ||
QCBOREncode_CloseArray(&ectx); | ||
} | ||
else if (std::holds_alternative<pos::AtKey>(pos)) | ||
{ | ||
QCBOREncode_OpenMapInMapN(&ectx, key); | ||
auto subkey = std::get<pos::AtKey>(pos).key; | ||
QCBOREncode_OpenArrayInMapN(&ectx, subkey); | ||
QCBOREncode_AddBytes(&ectx, {value.data(), value.size()}); | ||
QCBOREncode_CloseArray(&ectx); | ||
QCBOREncode_CloseMap(&ectx); | ||
} | ||
else | ||
{ | ||
throw std::logic_error("Invalid COSE_Sign1 edit operation"); | ||
} | ||
|
||
QCBOREncode_CloseMap(&ectx); | ||
QCBOREncode_AddEncoded(&ectx, payload); | ||
QCBOREncode_AddBytes(&ectx, signature); | ||
QCBOREncode_CloseArray(&ectx); | ||
|
||
UsefulBufC cose_output; | ||
err = QCBOREncode_Finish(&ectx, &cose_output); | ||
if (err != QCBOR_SUCCESS) | ||
{ | ||
throw std::logic_error("Failed to encode COSE_Sign1"); | ||
} | ||
output.resize(cose_output.len); | ||
output.shrink_to_fit(); | ||
return output; | ||
}; | ||
} |
Oops, something went wrong.