fido_metadata 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +196 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +31 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +75 -0
- data/LICENSE.txt +21 -0
- data/README.md +86 -0
- data/Rakefile +12 -0
- data/bin/console +24 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/setup +8 -0
- data/fido_metadata.gemspec +41 -0
- data/lib/Root.cer +15 -0
- data/lib/fido_metadata.rb +19 -0
- data/lib/fido_metadata/attributes.rb +37 -0
- data/lib/fido_metadata/biometric_accuracy_descriptor.rb +15 -0
- data/lib/fido_metadata/biometric_status_report.rb +18 -0
- data/lib/fido_metadata/client.rb +110 -0
- data/lib/fido_metadata/code_accuracy_descriptor.rb +14 -0
- data/lib/fido_metadata/coercer/assumed_value.rb +19 -0
- data/lib/fido_metadata/coercer/bit_field.rb +22 -0
- data/lib/fido_metadata/coercer/certificates.rb +16 -0
- data/lib/fido_metadata/coercer/date.rb +15 -0
- data/lib/fido_metadata/coercer/escaped_uri.rb +17 -0
- data/lib/fido_metadata/coercer/magic_number.rb +24 -0
- data/lib/fido_metadata/coercer/objects.rb +18 -0
- data/lib/fido_metadata/coercer/user_verification_details.rb +36 -0
- data/lib/fido_metadata/constants.rb +91 -0
- data/lib/fido_metadata/entry.rb +25 -0
- data/lib/fido_metadata/pattern_accuracy_descriptor.rb +13 -0
- data/lib/fido_metadata/refinement/fixed_length_secure_compare.rb +23 -0
- data/lib/fido_metadata/statement.rb +65 -0
- data/lib/fido_metadata/status_report.rb +20 -0
- data/lib/fido_metadata/store.rb +82 -0
- data/lib/fido_metadata/table_of_contents.rb +17 -0
- data/lib/fido_metadata/test_cache_store.rb +26 -0
- data/lib/fido_metadata/verification_method_descriptor.rb +20 -0
- data/lib/fido_metadata/version.rb +5 -0
- data/lib/fido_metadata/x5c_key_finder.rb +50 -0
- metadata +186 -0
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "fido_metadata"
|
|
6
|
+
|
|
7
|
+
# Configure in-memory cache
|
|
8
|
+
require "fido_metadata/test_cache_store"
|
|
9
|
+
FidoMetadata.configure do |config|
|
|
10
|
+
config.metadata_token = ENV["MDS_TOKEN"]
|
|
11
|
+
config.cache_backend = FidoMetadata::TestCacheStore.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
unless FidoMetadata.configuration.metadata_token
|
|
15
|
+
puts <<~TOKEN_HINT
|
|
16
|
+
No MDS token configured via the MDS_TOKEN environment variable.
|
|
17
|
+
Set one for this session: FidoMetadata.configuration.metadata_token = 'your token'
|
|
18
|
+
TOKEN_HINT
|
|
19
|
+
end
|
|
20
|
+
puts "Reset the cache via: FidoMetadata.configuration.cache_backend.clear"
|
|
21
|
+
|
|
22
|
+
# Start REPL
|
|
23
|
+
require "pry-byebug"
|
|
24
|
+
Pry.start
|
data/bin/rspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
require "pathname"
|
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
|
13
|
+
Pathname.new(__FILE__).realpath)
|
|
14
|
+
|
|
15
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
|
16
|
+
|
|
17
|
+
if File.file?(bundle_binstub)
|
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
|
19
|
+
load(bundle_binstub)
|
|
20
|
+
else
|
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require "rubygems"
|
|
27
|
+
require "bundler/setup"
|
|
28
|
+
|
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/rubocop
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
require "pathname"
|
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
|
13
|
+
Pathname.new(__FILE__).realpath)
|
|
14
|
+
|
|
15
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
|
16
|
+
|
|
17
|
+
if File.file?(bundle_binstub)
|
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
|
19
|
+
load(bundle_binstub)
|
|
20
|
+
else
|
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require "rubygems"
|
|
27
|
+
require "bundler/setup"
|
|
28
|
+
|
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|
data/bin/setup
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require "fido_metadata/version"
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = "fido_metadata"
|
|
9
|
+
spec.version = FidoMetadata::VERSION
|
|
10
|
+
spec.authors = ["Bart de Water"]
|
|
11
|
+
|
|
12
|
+
spec.summary = "FIDO Alliance Metadata Service client"
|
|
13
|
+
spec.description = "Client for looking up metadata about FIDO authenticators, for use by WebAuthn relying parties"
|
|
14
|
+
spec.homepage = "https://github.com/bdewater/fido_metadata"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
|
|
17
|
+
if spec.respond_to?(:metadata)
|
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
20
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
26
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
27
|
+
end
|
|
28
|
+
spec.bindir = "exe"
|
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
30
|
+
spec.require_paths = ["lib"]
|
|
31
|
+
|
|
32
|
+
spec.required_ruby_version = ">= 2.3"
|
|
33
|
+
|
|
34
|
+
spec.add_dependency "jwt", "~> 2.0"
|
|
35
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
|
36
|
+
spec.add_development_dependency "pry-byebug"
|
|
37
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
38
|
+
spec.add_development_dependency "rspec", "~> 3.8"
|
|
39
|
+
spec.add_development_dependency "rubocop", "0.75.0"
|
|
40
|
+
spec.add_development_dependency "webmock", "~> 3.6"
|
|
41
|
+
end
|
data/lib/Root.cer
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIICQzCCAcigAwIBAgIORqmxkzowRM99NQZJurcwCgYIKoZIzj0EAwMwUzELMAkG
|
|
3
|
+
A1UEBhMCVVMxFjAUBgNVBAoTDUZJRE8gQWxsaWFuY2UxHTAbBgNVBAsTFE1ldGFk
|
|
4
|
+
YXRhIFRPQyBTaWduaW5nMQ0wCwYDVQQDEwRSb290MB4XDTE1MDYxNzAwMDAwMFoX
|
|
5
|
+
DTQ1MDYxNzAwMDAwMFowUzELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUZJRE8gQWxs
|
|
6
|
+
aWFuY2UxHTAbBgNVBAsTFE1ldGFkYXRhIFRPQyBTaWduaW5nMQ0wCwYDVQQDEwRS
|
|
7
|
+
b290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEFEoo+6jdxg6oUuOloqPjK/nVGyY+
|
|
8
|
+
AXCFz1i5JR4OPeFJs+my143ai0p34EX4R1Xxm9xGi9n8F+RxLjLNPHtlkB3X4ims
|
|
9
|
+
rfIx7QcEImx1cMTgu5zUiwxLX1ookVhIRSoso2MwYTAOBgNVHQ8BAf8EBAMCAQYw
|
|
10
|
+
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU0qUfC6f2YshA1Ni9udeO0VS7vEYw
|
|
11
|
+
HwYDVR0jBBgwFoAU0qUfC6f2YshA1Ni9udeO0VS7vEYwCgYIKoZIzj0EAwMDaQAw
|
|
12
|
+
ZgIxAKulGbSFkDSZusGjbNkAhAkqTkLWo3GrN5nRBNNk2Q4BlG+AvM5q9wa5WciW
|
|
13
|
+
DcMdeQIxAMOEzOFsxX9Bo0h4LOFE5y5H8bdPFYW+l5gy1tQiJv+5NUyM2IBB55XU
|
|
14
|
+
YjdBz56jSA==
|
|
15
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fido_metadata/store"
|
|
4
|
+
require "fido_metadata/version"
|
|
5
|
+
|
|
6
|
+
module FidoMetadata
|
|
7
|
+
def self.configuration
|
|
8
|
+
@configuration ||= Configuration.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.configure
|
|
12
|
+
yield(configuration)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Configuration
|
|
16
|
+
attr_accessor :metadata_token
|
|
17
|
+
attr_accessor :cache_backend
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FidoMetadata
|
|
4
|
+
module Attributes
|
|
5
|
+
def underscore_name(name)
|
|
6
|
+
name
|
|
7
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
8
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
9
|
+
.downcase
|
|
10
|
+
.to_sym
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private :underscore_name
|
|
14
|
+
|
|
15
|
+
def json_accessor(name, coercer = nil)
|
|
16
|
+
underscored_name = underscore_name(name)
|
|
17
|
+
attr_accessor underscored_name
|
|
18
|
+
|
|
19
|
+
if coercer
|
|
20
|
+
define_method(:"#{underscored_name}=") do |value|
|
|
21
|
+
coerced_value = coercer.coerce(value)
|
|
22
|
+
instance_variable_set(:"@#{underscored_name}", coerced_value)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def from_json(hash = {})
|
|
28
|
+
instance = new
|
|
29
|
+
hash.each do |k, v|
|
|
30
|
+
method_name = :"#{underscore_name(k)}="
|
|
31
|
+
instance.public_send(method_name, v) if instance.respond_to?(method_name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
instance
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fido_metadata/attributes"
|
|
4
|
+
|
|
5
|
+
module FidoMetadata
|
|
6
|
+
class BiometricAccuracyDescriptor
|
|
7
|
+
extend Attributes
|
|
8
|
+
|
|
9
|
+
json_accessor("selfAttestedFRR")
|
|
10
|
+
json_accessor("selfAttestedFAR")
|
|
11
|
+
json_accessor("maxTemplates")
|
|
12
|
+
json_accessor("maxRetries")
|
|
13
|
+
json_accessor("blockSlowdown")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fido_metadata/attributes"
|
|
4
|
+
require "fido_metadata/coercer/date"
|
|
5
|
+
|
|
6
|
+
module FidoMetadata
|
|
7
|
+
class BiometricStatusReport
|
|
8
|
+
extend Attributes
|
|
9
|
+
|
|
10
|
+
json_accessor("certLevel")
|
|
11
|
+
json_accessor("modality")
|
|
12
|
+
json_accessor("effectiveDate", Coercer::Date)
|
|
13
|
+
json_accessor("certificationDescriptor")
|
|
14
|
+
json_accessor("certificateNumber")
|
|
15
|
+
json_accessor("certificationPolicyVersion")
|
|
16
|
+
json_accessor("certificationRequirementsVersion")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "jwt"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "openssl"
|
|
6
|
+
require "fido_metadata/refinement/fixed_length_secure_compare"
|
|
7
|
+
require "fido_metadata/x5c_key_finder"
|
|
8
|
+
require "fido_metadata/version"
|
|
9
|
+
|
|
10
|
+
module FidoMetadata
|
|
11
|
+
class Client
|
|
12
|
+
class DataIntegrityError < StandardError; end
|
|
13
|
+
class InvalidHashError < DataIntegrityError; end
|
|
14
|
+
class UnverifiedSigningKeyError < DataIntegrityError; end
|
|
15
|
+
|
|
16
|
+
using Refinement::FixedLengthSecureCompare
|
|
17
|
+
|
|
18
|
+
DEFAULT_HEADERS = {
|
|
19
|
+
"Content-Type" => "application/json",
|
|
20
|
+
"User-Agent" => "fido_metadata/#{FidoMetadata::VERSION} (Ruby)"
|
|
21
|
+
}.freeze
|
|
22
|
+
FIDO_ROOT_CERTIFICATES = [OpenSSL::X509::Certificate.new(
|
|
23
|
+
File.read(File.join(__dir__, "..", "Root.cer"))
|
|
24
|
+
)].freeze
|
|
25
|
+
|
|
26
|
+
def initialize(token)
|
|
27
|
+
@token = token
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def download_toc(uri, trusted_certs: FIDO_ROOT_CERTIFICATES)
|
|
31
|
+
response = get_with_token(uri)
|
|
32
|
+
payload, _ = JWT.decode(response, nil, true, algorithms: ["ES256"]) do |headers|
|
|
33
|
+
jwt_certificates = headers["x5c"].map do |encoded|
|
|
34
|
+
OpenSSL::X509::Certificate.new(Base64.strict_decode64(encoded))
|
|
35
|
+
end
|
|
36
|
+
crls = download_crls(jwt_certificates)
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
X5cKeyFinder.from(jwt_certificates, trusted_certs, crls)
|
|
40
|
+
rescue JWT::VerificationError => e
|
|
41
|
+
raise(UnverifiedSigningKeyError, e.message)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
payload
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def download_entry(uri, expected_hash:)
|
|
48
|
+
response = get_with_token(uri)
|
|
49
|
+
decoded_hash = Base64.urlsafe_decode64(expected_hash)
|
|
50
|
+
unless OpenSSL.fixed_length_secure_compare(OpenSSL::Digest::SHA256.digest(response), decoded_hash)
|
|
51
|
+
raise(InvalidHashError)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
decoded_body = Base64.urlsafe_decode64(response)
|
|
55
|
+
JSON.parse(decoded_body)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def get_with_token(uri)
|
|
61
|
+
if @token && !@token.empty?
|
|
62
|
+
uri.path += "/" unless uri.path.end_with?("/")
|
|
63
|
+
uri.query = "token=#{@token}"
|
|
64
|
+
end
|
|
65
|
+
get(uri)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def get(uri)
|
|
69
|
+
get = Net::HTTP::Get.new(uri, DEFAULT_HEADERS)
|
|
70
|
+
response = http(uri).request(get)
|
|
71
|
+
response.value
|
|
72
|
+
response.body
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def http(uri)
|
|
76
|
+
@http ||= begin
|
|
77
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
78
|
+
http.use_ssl = uri.port == 443
|
|
79
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
80
|
+
http.open_timeout = 5
|
|
81
|
+
http.read_timeout = 5
|
|
82
|
+
http
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def download_crls(certificates)
|
|
87
|
+
uris = extract_crl_distribution_points(certificates)
|
|
88
|
+
|
|
89
|
+
crls = uris.compact.uniq.map do |uri|
|
|
90
|
+
begin
|
|
91
|
+
get(uri)
|
|
92
|
+
rescue Net::ProtoServerError
|
|
93
|
+
# TODO: figure out why test endpoint specifies a missing and unused CRL in the cert chain, and see if this
|
|
94
|
+
# rescue can be removed. If the CRL is used, OpenSSL error 3 (unable to get certificate CRL) will raise.
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
crls.compact.map { |crl| OpenSSL::X509::CRL.new(crl) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def extract_crl_distribution_points(certificates)
|
|
102
|
+
certificates.map do |certificate|
|
|
103
|
+
extension = certificate.extensions.detect { |ext| ext.oid == "crlDistributionPoints" }
|
|
104
|
+
# TODO: replace this with proper parsing of deeply nested ASN1 structures
|
|
105
|
+
match = extension&.value&.match(/URI:(?<uri>\S*)/)
|
|
106
|
+
URI(match[:uri]) if match
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fido_metadata/attributes"
|
|
4
|
+
|
|
5
|
+
module FidoMetadata
|
|
6
|
+
class CodeAccuracyDescriptor
|
|
7
|
+
extend Attributes
|
|
8
|
+
|
|
9
|
+
json_accessor("base")
|
|
10
|
+
json_accessor("minLength")
|
|
11
|
+
json_accessor("maxRetries")
|
|
12
|
+
json_accessor("blockSlowdown")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FidoMetadata
|
|
4
|
+
module Coercer
|
|
5
|
+
class AssumedValue
|
|
6
|
+
def initialize(assume)
|
|
7
|
+
@assume = assume
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def coerce(value)
|
|
11
|
+
if value.nil?
|
|
12
|
+
@assume
|
|
13
|
+
else
|
|
14
|
+
value
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FidoMetadata
|
|
4
|
+
module Coercer
|
|
5
|
+
class BitField
|
|
6
|
+
def initialize(mapping, single_value: false)
|
|
7
|
+
@mapping = mapping
|
|
8
|
+
@single_value = single_value
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def coerce(value)
|
|
12
|
+
results = @mapping.reject { |flag, _constant| flag & value == 0 }.values
|
|
13
|
+
|
|
14
|
+
if @single_value
|
|
15
|
+
results.first
|
|
16
|
+
else
|
|
17
|
+
results
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl/x509"
|
|
4
|
+
|
|
5
|
+
module FidoMetadata
|
|
6
|
+
module Coercer
|
|
7
|
+
module Certificates
|
|
8
|
+
def self.coerce(values)
|
|
9
|
+
return unless values.is_a?(Array)
|
|
10
|
+
return values if values.all? { |value| value.is_a?(OpenSSL::X509::Certificate) }
|
|
11
|
+
|
|
12
|
+
values.map { |value| OpenSSL::X509::Certificate.new(Base64.decode64(value)) }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|