Skip to content

Commit

Permalink
Provide more flexible JWK parsing (#370)
Browse files Browse the repository at this point in the history
* Expose more JWK tools in the public API

* Add a test case for full JWK parsing

* clang-tidy

* clang-format

* Remove unnecessary forward declaration

---------

Co-authored-by: Richard Barnes <[email protected]>
  • Loading branch information
bifurcation and Richard Barnes authored Sep 12, 2023
1 parent 6a10f78 commit c6de4ca
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 20 deletions.
22 changes: 16 additions & 6 deletions include/mls/credential.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

namespace MLS_NAMESPACE {

namespace hpke {
struct UserInfoVC;
}

// struct {
// opaque identity<0..2^16-1>;
// SignaturePublicKey public_key;
Expand Down Expand Up @@ -57,17 +61,23 @@ operator>>(tls::istream& str, X509Credential& obj);
struct UserInfoVCCredential
{
UserInfoVCCredential() = default;
explicit UserInfoVCCredential(bytes userinfo_vc_jwt_in);
explicit UserInfoVCCredential(std::string userinfo_vc_jwt_in);

bytes userinfo_vc_jwt;
std::string userinfo_vc_jwt;

bool valid_for(const SignaturePublicKey& pub) const;
bool valid_from(const PublicJWK& pub) const;

TLS_SERIALIZABLE(userinfo_vc_jwt)
friend tls::ostream operator<<(tls::ostream& str,
const UserInfoVCCredential& obj);
friend tls::istream operator>>(tls::istream& str, UserInfoVCCredential& obj);
friend bool operator==(const UserInfoVCCredential& lhs,
const UserInfoVCCredential& rhs);
friend bool operator!=(const UserInfoVCCredential& lhs,
const UserInfoVCCredential& rhs);

private:
SignaturePublicKey _public_key;
SignatureScheme _signature_scheme;
std::shared_ptr<hpke::UserInfoVC> _vc;
};

bool
Expand Down Expand Up @@ -149,7 +159,7 @@ struct Credential

static Credential basic(const bytes& identity);
static Credential x509(const std::vector<bytes>& der_chain);
static Credential userinfo_vc(const bytes& userinfo_vc_jwt);
static Credential userinfo_vc(const std::string& userinfo_vc_jwt);
static Credential multi(
const std::vector<CredentialBindingInput>& binding_inputs,
const SignaturePublicKey& signature_key);
Expand Down
9 changes: 9 additions & 0 deletions include/mls/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,15 @@ struct SignaturePublicKey
TLS_SERIALIZABLE(data)
};

struct PublicJWK
{
SignatureScheme signature_scheme;
std::optional<std::string> key_id;
SignaturePublicKey public_key;

static PublicJWK parse(const std::string& jwk_json);
};

struct SignaturePrivateKey
{
static SignaturePrivateKey generate(CipherSuite suite);
Expand Down
3 changes: 2 additions & 1 deletion lib/hpke/include/hpke/userinfo_vc.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ struct UserInfoVC
UserInfoVC& operator=(const UserInfoVC& other) = default;
UserInfoVC& operator=(UserInfoVC&& other) = default;

const Signature& signature_algorithm() const;
std::string issuer() const;
std::string key_id() const;
std::optional<std::string> key_id() const;
std::chrono::system_clock::time_point not_before() const;
std::chrono::system_clock::time_point not_after() const;
const std::string& raw_credential() const;
Expand Down
19 changes: 15 additions & 4 deletions lib/hpke/src/userinfo_vc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ struct UserInfoVC::ParsedCredential
{
// Header fields
const Signature& signature_algorithm; // `alg`
std::string key_id; // `kid`
std::optional<std::string> key_id; // `kid`

// Top-level Payload fields
std::string issuer; // `iss`
Expand All @@ -176,7 +176,7 @@ struct UserInfoVC::ParsedCredential
bytes signature;

ParsedCredential(const Signature& signature_algorithm_in,
std::string key_id_in,
std::optional<std::string> key_id_in,
std::string issuer_in,
std::chrono::system_clock::time_point not_before_in,
std::chrono::system_clock::time_point not_after_in,
Expand Down Expand Up @@ -223,6 +223,11 @@ struct UserInfoVC::ParsedCredential
signature = jws_to_der_sig(signature);
}

auto kid = std::optional<std::string>{};
if (header.contains("kid")) {
kid = header.at("kid").get<std::string>();
}

// Verify the VC parts
const auto& vc = payload.at("vc");

Expand Down Expand Up @@ -254,7 +259,7 @@ struct UserInfoVC::ParsedCredential
// Extract the salient parts
return std::make_shared<ParsedCredential>(
sig,
header.at("kid"),
kid,

payload.at("iss"),
epoch_time(payload.at("nbf").get<int64_t>()),
Expand Down Expand Up @@ -336,13 +341,19 @@ UserInfoVC::UserInfoVC(std::string jwt)
{
}

const Signature&
UserInfoVC::signature_algorithm() const
{
return parsed_cred->signature_algorithm;
}

std::string
UserInfoVC::issuer() const
{
return parsed_cred->issuer;
}

std::string
std::optional<std::string>
UserInfoVC::key_id() const
{
return parsed_cred->key_id;
Expand Down
53 changes: 44 additions & 9 deletions src/credential.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,22 +115,57 @@ operator==(const X509Credential& lhs, const X509Credential& rhs)
///
/// UserInfoVCCredential
///
UserInfoVCCredential::UserInfoVCCredential(bytes userinfo_vc_jwt_in)
UserInfoVCCredential::UserInfoVCCredential(std::string userinfo_vc_jwt_in)
: userinfo_vc_jwt(std::move(userinfo_vc_jwt_in))
, _vc(std::make_shared<hpke::UserInfoVC>(userinfo_vc_jwt))
{
const auto vc = UserInfoVC(to_ascii(userinfo_vc_jwt));

const auto& pub = vc.public_key();
const auto pub_data = pub.sig.serialize(*pub.key);
_signature_scheme = tls_signature_scheme(pub.sig.id);
_public_key = SignaturePublicKey{ pub_data };
}

bool
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
UserInfoVCCredential::valid_for(const SignaturePublicKey& pub) const
{
return pub == _public_key;
const auto& vc_pub = _vc->public_key();
return pub.data == vc_pub.sig.serialize(*vc_pub.key);
}

bool
UserInfoVCCredential::valid_from(const PublicJWK& pub) const
{
const auto& sig = _vc->signature_algorithm();
if (pub.signature_scheme != tls_signature_scheme(sig.id)) {
return false;
}

const auto sig_pub = sig.deserialize(pub.public_key.data);
return _vc->valid_from(*sig_pub);
}

tls::ostream
operator<<(tls::ostream& str, const UserInfoVCCredential& obj)
{
return str << from_ascii(obj.userinfo_vc_jwt);
}

tls::istream
operator>>(tls::istream& str, UserInfoVCCredential& obj)
{
auto jwt = bytes{};
str >> jwt;
obj = UserInfoVCCredential(to_ascii(jwt));
return str;
}

bool
operator==(const UserInfoVCCredential& lhs, const UserInfoVCCredential& rhs)
{
return lhs.userinfo_vc_jwt == rhs.userinfo_vc_jwt;
}

bool
operator!=(const UserInfoVCCredential& lhs, const UserInfoVCCredential& rhs)
{
return !(lhs == rhs);
}

///
Expand Down Expand Up @@ -236,7 +271,7 @@ Credential::multi(const std::vector<CredentialBindingInput>& binding_inputs,
}

Credential
Credential::userinfo_vc(const bytes& userinfo_vc_jwt)
Credential::userinfo_vc(const std::string& userinfo_vc_jwt)
{
return { UserInfoVCCredential{ userinfo_vc_jwt } };
}
Expand Down
9 changes: 9 additions & 0 deletions src/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,15 @@ SignaturePublicKey::to_jwk(CipherSuite suite) const
return suite.sig().export_jwk(*pub);
}

PublicJWK
PublicJWK::parse(const std::string& jwk_json)
{
const auto parsed = Signature::parse_jwk(jwk_json);
const auto scheme = tls_signature_scheme(parsed.sig.id);
const auto pub_data = parsed.sig.serialize(*parsed.key);
return { scheme, parsed.key_id, { pub_data } };
}

SignaturePrivateKey
SignaturePrivateKey::generate(CipherSuite suite)
{
Expand Down
18 changes: 18 additions & 0 deletions test/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,24 @@ TEST_CASE("Signature Key JWK Import/Export")
const auto decoded_pub = SignaturePublicKey::from_jwk(suite, encoded_pub);
REQUIRE(decoded_pub == pub);
}

// Test PublicJWK parsing
const auto full_jwk = R"({
"kty": "OKP",
"crv": "Ed25519",
"kid": "059fc2ee-5ef6-456a-91d8-49c422c772b2",
"x": "miljqilAZV2yFkqIBhrxhvt2wIMvPtkNEFzuziEGOtI"
})"s;

const auto known_scheme = SignatureScheme::ed25519;
const auto known_key_id = std::string("059fc2ee-5ef6-456a-91d8-49c422c772b2");
const auto knwon_pub_data = from_hex(
"9a2963aa2940655db2164a88061af186fb76c0832f3ed90d105ceece21063ad2");

const auto jwk = PublicJWK::parse(full_jwk);
REQUIRE(jwk.signature_scheme == known_scheme);
REQUIRE(jwk.key_id == known_key_id);
REQUIRE(jwk.public_key == SignaturePublicKey{ knwon_pub_data });
}

TEST_CASE("Crypto Interop")
Expand Down

0 comments on commit c6de4ca

Please sign in to comment.