lib/fido_metadata/client.rb in fido_metadata-0.1.0 vs lib/fido_metadata/client.rb in fido_metadata-0.2.0

- old
+ new

@@ -1,46 +1,54 @@ # frozen_string_literal: true require "jwt" require "net/http" require "openssl" -require "securecompare" +require "fido_metadata/refinement/fixed_length_secure_compare" +require "fido_metadata/x5c_key_finder" module FidoMetadata class Client class DataIntegrityError < StandardError; end class InvalidHashError < DataIntegrityError; end class UnverifiedSigningKeyError < DataIntegrityError; end + using Refinement::FixedLengthSecureCompare + DEFAULT_HEADERS = { "Content-Type" => "application/json", "User-Agent" => "fido_metadata/#{FidoMetadata::VERSION} (Ruby)" }.freeze + FIDO_ROOT_CERTIFICATES = [OpenSSL::X509::Certificate.new( + File.read(File.join(__dir__, "..", "Root.cer")) + )].freeze - def self.fido_trust_store - store = OpenSSL::X509::Store.new - store.purpose = OpenSSL::X509::PURPOSE_ANY - store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL - file = File.read(File.join(__dir__, "..", "Root.cer")) - store.add_cert(OpenSSL::X509::Certificate.new(file)) - end - def initialize(token) @token = token end - def download_toc(uri, trust_store: self.class.fido_trust_store) + def download_toc(uri, trusted_certs: FIDO_ROOT_CERTIFICATES) response = get_with_token(uri) payload, _ = JWT.decode(response, nil, true, algorithms: ["ES256"]) do |headers| - verified_public_key(headers["x5c"], trust_store) + jwt_certificates = headers["x5c"].map do |encoded| + OpenSSL::X509::Certificate.new(Base64.strict_decode64(encoded)) + end + crls = download_crls(jwt_certificates) + + begin + X5cKeyFinder.from(jwt_certificates, trusted_certs, crls) + rescue JWT::VerificationError => e + raise(UnverifiedSigningKeyError, e.message) + end end payload end def download_entry(uri, expected_hash:) response = get_with_token(uri) - unless SecureCompare.compare(OpenSSL::Digest::SHA256.digest(response), Base64.urlsafe_decode64(expected_hash)) + decoded_hash = Base64.urlsafe_decode64(expected_hash) + unless OpenSSL.fixed_length_secure_compare(OpenSSL::Digest::SHA256.digest(response), decoded_hash) raise(InvalidHashError) end decoded_body = Base64.urlsafe_decode64(response) JSON.parse(decoded_body) @@ -69,28 +77,9 @@ http.use_ssl = uri.port == 443 http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.open_timeout = 5 http.read_timeout = 5 http - end - end - - def verified_public_key(x5c, trust_store) - certificates = x5c.map do |encoded| - OpenSSL::X509::Certificate.new(Base64.strict_decode64(encoded)) - end - leaf_certificate = certificates[0] - chain_certificates = certificates[1..-1] - - crls = download_crls(certificates) - crls.each do |crl| - trust_store.add_crl(crl) - end - - if trust_store.verify(leaf_certificate, chain_certificates) - leaf_certificate.public_key - else - raise(UnverifiedSigningKeyError, "OpenSSL error #{trust_store.error} (#{trust_store.error_string})") end end def download_crls(certificates) uris = extract_crl_distribution_points(certificates)