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)