From 099b02e202f34e4de5f25e71ea2aac21ae7e96b8 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sun, 10 Sep 2023 19:03:37 -0400 Subject: [PATCH] Expose more JWK tools in the public API --- include/mls/credential.h | 19 +++++++---- include/mls/crypto.h | 12 +++++++ lib/hpke/include/hpke/userinfo_vc.h | 3 +- lib/hpke/src/userinfo_vc.cpp | 19 ++++++++--- src/credential.cpp | 51 ++++++++++++++++++++++------- src/crypto.cpp | 9 +++++ 6 files changed, 91 insertions(+), 22 deletions(-) diff --git a/include/mls/credential.h b/include/mls/credential.h index a8bbd77f..cc11d67c 100644 --- a/include/mls/credential.h +++ b/include/mls/credential.h @@ -6,6 +6,10 @@ namespace MLS_NAMESPACE { +namespace hpke { + struct UserInfoVC; +} + // struct { // opaque identity<0..2^16-1>; // SignaturePublicKey public_key; @@ -57,17 +61,20 @@ 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& str, const UserInfoVCCredential& obj); + friend bool operator!=(const UserInfoVCCredential& str, const UserInfoVCCredential& obj); private: - SignaturePublicKey _public_key; - SignatureScheme _signature_scheme; + std::shared_ptr _vc; }; bool @@ -149,7 +156,7 @@ struct Credential static Credential basic(const bytes& identity); static Credential x509(const std::vector& 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& binding_inputs, const SignaturePublicKey& signature_key); diff --git a/include/mls/crypto.h b/include/mls/crypto.h index 00d513d6..7c2c49ff 100644 --- a/include/mls/crypto.h +++ b/include/mls/crypto.h @@ -208,8 +208,14 @@ extern const std::string group_info; extern const std::string multi_credential; } // namespace sign_label +struct PublicJWK; + struct SignaturePublicKey { + // XXX(RLB) It would be nice to wrap this return value as a struct, but that + // results in a compiler error "field has incomplete type". + static PublicJWK parse_jwk(const std::string& jwk_json); + static SignaturePublicKey from_jwk(CipherSuite suite, const std::string& json_str); @@ -225,6 +231,12 @@ struct SignaturePublicKey TLS_SERIALIZABLE(data) }; +struct PublicJWK { + SignatureScheme signature_scheme; + std::optional key_id; + SignaturePublicKey public_key; +}; + struct SignaturePrivateKey { static SignaturePrivateKey generate(CipherSuite suite); diff --git a/lib/hpke/include/hpke/userinfo_vc.h b/lib/hpke/include/hpke/userinfo_vc.h index 085fd4f5..902fed53 100644 --- a/lib/hpke/include/hpke/userinfo_vc.h +++ b/lib/hpke/include/hpke/userinfo_vc.h @@ -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 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; diff --git a/lib/hpke/src/userinfo_vc.cpp b/lib/hpke/src/userinfo_vc.cpp index 89860d12..65cbe5d9 100644 --- a/lib/hpke/src/userinfo_vc.cpp +++ b/lib/hpke/src/userinfo_vc.cpp @@ -160,7 +160,7 @@ struct UserInfoVC::ParsedCredential { // Header fields const Signature& signature_algorithm; // `alg` - std::string key_id; // `kid` + std::optional key_id; // `kid` // Top-level Payload fields std::string issuer; // `iss` @@ -176,7 +176,7 @@ struct UserInfoVC::ParsedCredential bytes signature; ParsedCredential(const Signature& signature_algorithm_in, - std::string key_id_in, + std::optional 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, @@ -223,6 +223,11 @@ struct UserInfoVC::ParsedCredential signature = jws_to_der_sig(signature); } + auto kid = std::optional{}; + if (header.contains("kid")) { + kid = header.at("kid").get(); + } + // Verify the VC parts const auto& vc = payload.at("vc"); @@ -254,7 +259,7 @@ struct UserInfoVC::ParsedCredential // Extract the salient parts return std::make_shared( sig, - header.at("kid"), + kid, payload.at("iss"), epoch_time(payload.at("nbf").get()), @@ -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 UserInfoVC::key_id() const { return parsed_cred->key_id; diff --git a/src/credential.cpp b/src/credential.cpp index 730562b9..370b985d 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -115,24 +115,53 @@ 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)) -{ - 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 }; -} + , _vc(std::make_shared(userinfo_vc_jwt)) +{} 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); +} + + + + /// /// CredentialBinding and MultiCredential /// @@ -236,7 +265,7 @@ Credential::multi(const std::vector& 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 } }; } diff --git a/src/crypto.cpp b/src/crypto.cpp index 315ededd..fa400b6e 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -385,6 +385,15 @@ SignaturePublicKey::verify(const CipherSuite& suite, return suite.sig().verify(content, signature, *pub); } +PublicJWK +SignaturePublicKey::parse_jwk(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 } }; +} + SignaturePublicKey SignaturePublicKey::from_jwk(CipherSuite suite, const std::string& json_str) {