diff --git a/include/mls/credential.h b/include/mls/credential.h index a8bbd77f..be0c213e 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,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 _vc; }; bool @@ -149,7 +159,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..dc467c0d 100644 --- a/include/mls/crypto.h +++ b/include/mls/crypto.h @@ -225,6 +225,15 @@ struct SignaturePublicKey TLS_SERIALIZABLE(data) }; +struct PublicJWK +{ + SignatureScheme signature_scheme; + std::optional key_id; + SignaturePublicKey public_key; + + static PublicJWK parse(const std::string& jwk_json); +}; + 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..36b10629 100644 --- a/src/credential.cpp +++ b/src/credential.cpp @@ -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(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); } /// @@ -236,7 +271,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..1986659c 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -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) { diff --git a/test/crypto.cpp b/test/crypto.cpp index a738f88f..393a0a04 100644 --- a/test/crypto.cpp +++ b/test/crypto.cpp @@ -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")