Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CWT support for C++ #4804

Merged
merged 14 commits into from
Feb 19, 2024
Merged

Add CWT support for C++ #4804

merged 14 commits into from
Feb 19, 2024

Conversation

ipetr0v
Copy link
Contributor

@ipetr0v ipetr0v commented Feb 15, 2024

This PR:

  • Adds support for COSE and CWT
  • Adds libcppbor as a WORKSPACE dependency
  • Adds the logic for parsing CWT certificates
    • Only extracts SUBJECT_PUBLIC_KEY for now and doesn't provide custom claims

This logic is required to implement C++ integration tests that use DICE attestation evidence.

Ref #4074
Ref #4627
Ref #4818

@ipetr0v ipetr0v marked this pull request as ready for review February 15, 2024 17:13
@ipetr0v
Copy link
Contributor Author

ipetr0v commented Feb 15, 2024

I think the only problem that remains in this PR is using the libcppbor get function with a template (where I have to explicitly add get<int, int>(...)):

template <typename Key,
          typename = std::enable_if_t<std::is_integral_v<Key> || std::is_enum_v<Key> ||
                                      details::is_text_type_v<Key>::value>>
const std::unique_ptr<Item>& Map::get(Key key) const {

This is what happens if I don't have explicit get<int, int>(...):

cc/attestation/cose.cc: In static member function 'static absl::lts_20230125::StatusOr<oak::attestation::CoseKey> oak::attestation::CoseKey::Deserialize(const std::vector<unsigned char>&)':
cc/attestation/cose.cc:79:58: error: no matching function for call to 'cppbor::Map::get(oak::attestation::CoseKey::Parameter) const'
   79 |   const std::unique_ptr<cppbor::Item>& kty = map->get(KTY);
      |                                                          ^
In file included from ./cc/attestation/cose.h:26,
                 from cc/attestation/cose.cc:17:
bazel-out/k8-fastbuild/bin/external/libcppbor/_virtual_includes/libcppbor/libcppbor/include/cppbor/cppbor.h:728:34: note: candidate: 'template<class Key, class Enable> const std::unique_ptr<cppbor::Item>& cppbor::Map::get(Key) const'
  728 |     const std::unique_ptr<Item>& get(Key key) const;
      |                                  ^~~
bazel-out/k8-fastbuild/bin/external/libcppbor/_virtual_includes/libcppbor/libcppbor/include/cppbor/cppbor.h:728:34: note:   template argument deduction/substitution failed:
cc/attestation/cose.cc:79:58: note:   couldn't deduce template parameter 'Enable'
   79 |   const std::unique_ptr<cppbor::Item>& kty = map->get(KTY);

@k-naliuka
Copy link
Contributor

I think the only problem that remains in this PR is using the libcppbor get function with a template (where I have to explicitly add get<int, int>(...)):

template <typename Key,
          typename = std::enable_if_t<std::is_integral_v<Key> || std::is_enum_v<Key> ||
                                      details::is_text_type_v<Key>::value>>
const std::unique_ptr<Item>& Map::get(Key key) const {

This is what happens if I don't have explicit get<int, int>(...):

cc/attestation/cose.cc: In static member function 'static absl::lts_20230125::StatusOr<oak::attestation::CoseKey> oak::attestation::CoseKey::Deserialize(const std::vector<unsigned char>&)':
cc/attestation/cose.cc:79:58: error: no matching function for call to 'cppbor::Map::get(oak::attestation::CoseKey::Parameter) const'
   79 |   const std::unique_ptr<cppbor::Item>& kty = map->get(KTY);
      |                                                          ^
In file included from ./cc/attestation/cose.h:26,
                 from cc/attestation/cose.cc:17:
bazel-out/k8-fastbuild/bin/external/libcppbor/_virtual_includes/libcppbor/libcppbor/include/cppbor/cppbor.h:728:34: note: candidate: 'template<class Key, class Enable> const std::unique_ptr<cppbor::Item>& cppbor::Map::get(Key) const'
  728 |     const std::unique_ptr<Item>& get(Key key) const;
      |                                  ^~~
bazel-out/k8-fastbuild/bin/external/libcppbor/_virtual_includes/libcppbor/libcppbor/include/cppbor/cppbor.h:728:34: note:   template argument deduction/substitution failed:
cc/attestation/cose.cc:79:58: note:   couldn't deduce template parameter 'Enable'
   79 |   const std::unique_ptr<cppbor::Item>& kty = map->get(KTY);

I think you're hitting C++ 14/17 compatibilityissue (see b/314141962)

cc/attestation/BUILD Outdated Show resolved Hide resolved
cc/attestation/cose.cc Outdated Show resolved Hide resolved
cc/attestation/cose.cc Outdated Show resolved Hide resolved
cc/attestation/cwt.h Outdated Show resolved Hide resolved
cc/attestation/cose.h Outdated Show resolved Hide resolved
cc/attestation/cose.h Outdated Show resolved Hide resolved
}
}

absl::Status UnexpectedCborTypeError(std::string_view name, cppbor::MajorType expected,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this go to cose.cc ? It is not public.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is currently used in cwt.cc as well

absl::StatusOr<CoseSign1> cose_sign1 = CoseSign1::Deserialize(data);
if (!cose_sign1.ok()) {
return cose_sign1.status();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the usual macros available externally?

ASSIGN_OR_RETURN(absl::StatusOr cose_sign1, CoseSign1::Deserialize(data));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Macros are not available externally

cc/attestation/cwt_test.cc Outdated Show resolved Hide resolved
cc/attestation/cwt_test.cc Outdated Show resolved Hide resolved
cc/utils/cose/cose.cc Outdated Show resolved Hide resolved

namespace oak::utils::cose {

absl::StatusOr<CoseSign1> CoseSign1::Deserialize(const std::vector<uint8_t>& data) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer absl::Span, unless the cppbor code insists this be a reference to a vector.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libcppbor provides the following parse functions:

ParseResult parse(const uint8_t* begin, const uint8_t* end);
ParseResult parse(const std::vector<uint8_t>& encoding);
ParseResult parse(const uint8_t* begin, size_t size);
ParseResult parse(const Bstr* bstr);

link

We can potentially use (const uint8_t* begin, const uint8_t* end), but those are not iterators. Not sure whether it's ok to get raw pointers from an absl::Span.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine -- Span is a contiguous region of memory -- but we'll most likely deal with vectors anyway, so it's not a big deal. (Otherwise, you could call parse(span.data(), span.size())')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parsing protobufs will produce an std::string, is there a conversion between those? Or it's better to use absl::string_view instead of span? (I was hitting zero-byte related problems when trying to use string view)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed input arguments to absl::string_view

cc/utils/cose/cose.cc Outdated Show resolved Hide resolved
// Array of signatures. Each signature is represented as a COSE_Signature structure.
const cppbor::Bstr* signature;

CoseSign1(const cppbor::Bstr* protected_headers, const cppbor::Map* unprotected_headers,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do these pointers point to? Who owns the memory? How do you guarantee that the memory they point to does not go out of scope while sthis struct is alive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They point to fields from the item_ which is a unique pointer owned by this struct


CoseSign1(const cppbor::Bstr* protected_headers, const cppbor::Map* unprotected_headers,
const cppbor::Bstr* payload, const cppbor::Bstr* signature,
std::unique_ptr<cppbor::Item>&& item)
Copy link
Collaborator

@andrisaar andrisaar Feb 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I it fine for this unique_ptr to be a nullptr?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It cannot be nullptr, because the other pointers should point to the fields from this item

// Public key.
const cppbor::Bstr* x;

CoseKey(const cppbor::Uint* kty, const cppbor::Bstr* kid, const cppbor::Nint* alg,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking API-wise, and this is in line with my comments above...

I'd change these constructors to just take cppbor::Item directly. Once you've assigned it to item_, then, in the constructor you can build the pointers to inside the item.

Or: move the constructors to the private section so that the only way how to construct these structs is via the factory method, which enforces the invariants.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parsing an item could produce errors (i.e. fields not have corret types), so moving the constructor to the private section. And the logic that produces errors stays in the static method.

cc/utils/cose/cose.h Outdated Show resolved Hide resolved
@ipetr0v ipetr0v merged commit aa0e264 into project-oak:main Feb 19, 2024
17 checks passed
@ipetr0v ipetr0v deleted the cpp_cwt branch February 19, 2024 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants