1414#include " ccf/crypto/verifier.h"
1515#include " crypto/certs.h"
1616#include " crypto/csr.h"
17+ #include " crypto/openssl/cose_sign.h"
18+ #include " crypto/openssl/cose_verifier.h"
1719#include " crypto/openssl/key_pair.h"
1820#include " crypto/openssl/rsa_key_pair.h"
1921#include " crypto/openssl/symmetric_key.h"
2628#include < ctime>
2729#include < doctest/doctest.h>
2830#include < optional>
31+ #include < qcbor/qcbor_spiffy_decode.h>
2932#include < span>
33+ #include < t_cose/t_cose_sign1_sign.h>
34+ #include < t_cose/t_cose_sign1_verify.h>
3035
3136using namespace std ;
3237using namespace ccf ::crypto;
@@ -190,6 +195,107 @@ ccf::crypto::Pem generate_self_signed_cert(
190195 kp, name, {}, valid_from, certificate_validity_period_days);
191196}
192197
198+ std::string qcbor_buf_to_string (const UsefulBufC& buf)
199+ {
200+ return std::string (reinterpret_cast <const char *>(buf.ptr ), buf.len );
201+ }
202+
203+ t_cose_err_t verify_detached (
204+ EVP_PKEY* key, std::span<const uint8_t > buf, std::span<const uint8_t > payload)
205+ {
206+ t_cose_key cose_key;
207+ cose_key.crypto_lib = T_COSE_CRYPTO_LIB_OPENSSL;
208+ cose_key.k .key_ptr = key;
209+
210+ t_cose_sign1_verify_ctx verify_ctx;
211+ t_cose_sign1_verify_init (&verify_ctx, T_COSE_OPT_TAG_REQUIRED);
212+ t_cose_sign1_set_verification_key (&verify_ctx, cose_key);
213+
214+ q_useful_buf_c buf_;
215+ buf_.ptr = buf.data ();
216+ buf_.len = buf.size ();
217+
218+ q_useful_buf_c payload_;
219+ payload_.ptr = payload.data ();
220+ payload_.len = payload.size ();
221+
222+ t_cose_err_t error = t_cose_sign1_verify_detached (
223+ &verify_ctx, buf_, NULL_Q_USEFUL_BUF_C, payload_, nullptr );
224+
225+ return error;
226+ }
227+
228+ void require_match_headers (
229+ const std::unordered_map<int64_t , std::string>& headers,
230+ const std::vector<uint8_t >& cose_sign)
231+ {
232+ UsefulBufC msg{cose_sign.data (), cose_sign.size ()};
233+
234+ // 0. Init and verify COSE tag
235+ QCBORDecodeContext ctx;
236+ QCBORDecode_Init (&ctx, msg, QCBOR_DECODE_MODE_NORMAL);
237+ QCBORDecode_EnterArray (&ctx, nullptr );
238+ REQUIRE_EQ (QCBORDecode_GetError (&ctx), QCBOR_SUCCESS);
239+ REQUIRE_EQ (QCBORDecode_GetNthTagOfLast (&ctx, 0 ), CBOR_TAG_COSE_SIGN1);
240+
241+ // 1. Protected headers
242+ struct q_useful_buf_c protected_parameters;
243+ QCBORDecode_EnterBstrWrapped (
244+ &ctx, QCBOR_TAG_REQUIREMENT_NOT_A_TAG, &protected_parameters);
245+ QCBORDecode_EnterMap (&ctx, NULL );
246+
247+ QCBORItem header_items[headers.size () + 2 ];
248+ size_t curr_id{0 };
249+ for (const auto & kv : headers)
250+ {
251+ header_items[curr_id].label .int64 = kv.first ;
252+ header_items[curr_id].uLabelType = QCBOR_TYPE_INT64;
253+ header_items[curr_id].uDataType = QCBOR_TYPE_TEXT_STRING;
254+
255+ curr_id++;
256+ }
257+
258+ // Verify 'alg' is default-encoded.
259+ header_items[curr_id].label .int64 = 1 ;
260+ header_items[curr_id].uLabelType = QCBOR_TYPE_INT64;
261+ header_items[curr_id].uDataType = QCBOR_TYPE_INT64;
262+
263+ header_items[++curr_id].uLabelType = QCBOR_TYPE_NONE;
264+
265+ QCBORDecode_GetItemsInMap (&ctx, header_items);
266+ REQUIRE_EQ (QCBORDecode_GetError (&ctx), QCBOR_SUCCESS);
267+
268+ curr_id = 0 ;
269+ for (const auto & kv : headers)
270+ {
271+ REQUIRE_NE (header_items[curr_id].uDataType , QCBOR_TYPE_NONE);
272+ REQUIRE_EQ (
273+ qcbor_buf_to_string (header_items[curr_id].val .string ), kv.second );
274+
275+ curr_id++;
276+ }
277+
278+ // 'alg'
279+ REQUIRE_NE (header_items[curr_id].uDataType , QCBOR_TYPE_NONE);
280+
281+ QCBORDecode_ExitMap (&ctx);
282+ QCBORDecode_ExitBstrWrapped (&ctx);
283+
284+ // 2. Unprotected headers (skip).
285+ QCBORItem item;
286+ QCBORDecode_VGetNextConsume (&ctx, &item);
287+
288+ // 3. Skip payload (detached);
289+ QCBORDecode_GetNext (&ctx, &item);
290+
291+ // 4. skip signature (should be verified by cose verifier).
292+ QCBORDecode_GetNext (&ctx, &item);
293+
294+ // 5. Decode can be completed.
295+ QCBORDecode_ExitArray (&ctx);
296+ REQUIRE_EQ (QCBORDecode_Finish (&ctx), QCBOR_SUCCESS);
297+ }
298+
193299TEST_CASE (" Check verifier handles nested certs for both PEM and DER inputs" )
194300{
195301 auto cert_der = ccf::crypto::raw_from_b64 (nested_cert);
@@ -1109,4 +1215,44 @@ TEST_CASE("Sign and verify with RSA key")
11091215 mdtype,
11101216 verify_salt_legth));
11111217 }
1112- }
1218+ }
1219+
1220+ TEST_CASE (" COSE sign & verify" )
1221+ {
1222+ std::shared_ptr<KeyPair_OpenSSL> kp =
1223+ std::dynamic_pointer_cast<KeyPair_OpenSSL>(
1224+ ccf::crypto::make_key_pair (CurveID::SECP384R1));
1225+
1226+ std::vector<uint8_t > payload{1 , 10 , 42 , 43 , 44 , 45 , 100 };
1227+ const std::unordered_map<int64_t , std::string> protected_headers = {
1228+ {36 , " thirsty six" }, {47 , " hungry seven" }};
1229+ auto cose_sign = cose_sign1 (*kp, protected_headers, payload);
1230+
1231+ if constexpr (false ) // enable to see the whole cose_sign as byte string
1232+ {
1233+ std::cout << " Public key: " << kp->public_key_pem ().str () << std::endl;
1234+ std::cout << " Serialised cose: " << std::hex << std::uppercase
1235+ << std::setw (2 ) << std::setfill (' 0' );
1236+ for (uint8_t x : cose_sign)
1237+ std::cout << static_cast <int >(x) << ' ' ;
1238+ std::cout << std::endl;
1239+ std::cout << " Raw payload: " ;
1240+ for (uint8_t x : payload)
1241+ std::cout << static_cast <int >(x) << ' ' ;
1242+ std::cout << std::endl;
1243+ }
1244+
1245+ require_match_headers (protected_headers, cose_sign);
1246+
1247+ REQUIRE_EQ (verify_detached (*kp, cose_sign, payload), T_COSE_SUCCESS);
1248+
1249+ // Wrong payload, must not pass verification.
1250+ REQUIRE_EQ (
1251+ verify_detached (*kp, cose_sign, std::vector<uint8_t >{1 , 2 , 3 }),
1252+ T_COSE_ERR_SIG_VERIFY);
1253+
1254+ // Empty headers and payload handled correctly
1255+ cose_sign = cose_sign1 (*kp, {}, {});
1256+ require_match_headers ({}, cose_sign);
1257+ REQUIRE_EQ (verify_detached (*kp, cose_sign, {}), T_COSE_SUCCESS);
1258+ }
0 commit comments