Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.0.0.alpha2 #368

Merged
merged 3 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ jobs:
- '2.7'
- '2.6'
- '2.5'
- '2.4'
- truffleruby
steps:
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ inherit_mode:
- AllowedNames

AllCops:
TargetRubyVersion: 2.4
TargetRubyVersion: 2.5
DisabledByDefault: true
NewCops: disable
Exclude:
Expand Down
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## [v3.0.0.alpha2] - 2022-09-12

### Added

- Rebased support for multiple relying parties from v3.0.0.alpha1 on top of v2.5.2, the previous alpha version was based on v2.3.0 ([@bdewater])

### BREAKING CHANGES

- Bumped minimum required Ruby version to 2.5 ([@bdewater])

## [v3.0.0.alpha1] - 2020-06-27

### Added
Expand Down Expand Up @@ -340,7 +350,8 @@ Note: Both additions should help making it compatible with Chrome for Android 70
- `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
- Works with ruby 2.5

[v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha1/
[v3.0.0.alpha2]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha2/
[v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.3.0...v3.0.0.alpha1
[v2.5.2]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.1...v2.5.2/
[v2.5.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.0...v2.5.1/
[v2.5.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.1...v2.5.0/
Expand Down
14 changes: 9 additions & 5 deletions lib/webauthn/attestation_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,22 @@ module WebAuthn
class AttestationObject
extend Forwardable

def self.deserialize(attestation_object)
from_map(CBOR.decode(attestation_object))
def self.deserialize(attestation_object, relying_party)
from_map(CBOR.decode(attestation_object), relying_party)
end

def self.from_map(map)
def self.from_map(map, relying_party)
new(
authenticator_data: WebAuthn::AuthenticatorData.deserialize(map["authData"]),
attestation_statement: WebAuthn::AttestationStatement.from(map["fmt"], map["attStmt"])
attestation_statement: WebAuthn::AttestationStatement.from(
map["fmt"],
map["attStmt"],
relying_party: relying_party
)
)
end

attr_reader :authenticator_data, :attestation_statement
attr_reader :authenticator_data, :attestation_statement, :relying_party

def initialize(authenticator_data:, attestation_statement:)
@authenticator_data = authenticator_data
Expand Down
4 changes: 2 additions & 2 deletions lib/webauthn/attestation_statement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ class FormatNotSupportedError < Error; end
ATTESTATION_FORMAT_APPLE => WebAuthn::AttestationStatement::Apple
}.freeze

def self.from(format, statement)
def self.from(format, statement, relying_party: WebAuthn.configuration.relying_party)
klass = FORMAT_TO_CLASS[format]

if klass
klass.new(statement)
klass.new(statement, relying_party)
else
raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
end
Expand Down
17 changes: 7 additions & 10 deletions lib/webauthn/attestation_statement/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ class UnsupportedAlgorithm < Error; end
class Base
AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"

def initialize(statement)
def initialize(statement, relying_party = WebAuthn.configuration.relying_party)
@statement = statement
@relying_party = relying_party
end

def valid?(_authenticator_data, _client_data_hash)
Expand All @@ -50,7 +51,7 @@ def attestation_certificate_key_id

private

attr_reader :statement
attr_reader :statement, :relying_party

def matching_aaguid?(attested_credential_data_aaguid)
extension = attestation_certificate&.find_extension(AAGUID_EXTENSION_OID)
Expand Down Expand Up @@ -93,10 +94,10 @@ def attestation_trust_path

def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
configuration.acceptable_attestation_types.include?(attestation_type) &&
relying_party.acceptable_attestation_types.include?(attestation_type) &&
valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
else
configuration.acceptable_attestation_types.include?(attestation_type)
relying_party.acceptable_attestation_types.include?(attestation_type)
end
end

Expand All @@ -120,7 +121,7 @@ def attestation_root_certificates_store(aaguid: nil, attestation_certificate_key

def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
root_certificates =
configuration.attestation_root_certificates_finders.reduce([]) do |certs, finder|
relying_party.attestation_root_certificates_finders.reduce([]) do |certs, finder|
if certs.empty?
finder.find(
attestation_format: format,
Expand Down Expand Up @@ -158,14 +159,10 @@ def verification_data(authenticator_data, client_data_hash)
def cose_algorithm
@cose_algorithm ||=
COSE::Algorithm.find(algorithm).tap do |alg|
alg && configuration.algorithms.include?(alg.name) ||
alg && relying_party.algorithms.include?(alg.name) ||
raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
end
end

def configuration
WebAuthn.configuration
end
end
end
end
7 changes: 4 additions & 3 deletions lib/webauthn/authenticator_assertion_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class SignatureVerificationError < VerificationError; end
class SignCountVerificationError < VerificationError; end

class AuthenticatorAssertionResponse < AuthenticatorResponse
def self.from_client(response)
encoder = WebAuthn.configuration.encoder
def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
encoder = relying_party.encoder

user_handle =
if response["userHandle"]
Expand All @@ -22,7 +22,8 @@ def self.from_client(response)
authenticator_data: encoder.decode(response["authenticatorData"]),
client_data_json: encoder.decode(response["clientDataJSON"]),
signature: encoder.decode(response["signature"]),
user_handle: user_handle
user_handle: user_handle,
relying_party: relying_party
)
end

Expand Down
17 changes: 10 additions & 7 deletions lib/webauthn/authenticator_attestation_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ class AttestedCredentialVerificationError < VerificationError; end
class AuthenticatorAttestationResponse < AuthenticatorResponse
extend Forwardable

def self.from_client(response)
encoder = WebAuthn.configuration.encoder
def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
encoder = relying_party.encoder

new(
attestation_object: encoder.decode(response["attestationObject"]),
client_data_json: encoder.decode(response["clientDataJSON"])
client_data_json: encoder.decode(response["clientDataJSON"]),
relying_party: relying_party
)
end

Expand All @@ -33,21 +34,22 @@ def initialize(attestation_object:, **options)
super(**options)

@attestation_object_bytes = attestation_object
@relying_party = relying_party
end

def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
super

verify_item(:attested_credential)
if WebAuthn.configuration.verify_attestation_statement
if relying_party.verify_attestation_statement
verify_item(:attestation_statement)
end

true
end

def attestation_object
@attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes)
@attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes, relying_party)
end

def_delegators(
Expand All @@ -63,14 +65,15 @@ def attestation_object

private

attr_reader :attestation_object_bytes
attr_reader :attestation_object_bytes, :relying_party

def type
WebAuthn::TYPES[:create]
end

def valid_attested_credential?
attestation_object.valid_attested_credential?
attestation_object.valid_attested_credential? &&
relying_party.algorithms.include?(authenticator_data.credential.algorithm)
end

def valid_attestation_statement?
Expand Down
15 changes: 10 additions & 5 deletions lib/webauthn/authenticator_data/attested_credential_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ class AttestedCredentialData < BinData::Record
count_bytes_remaining :trailing_bytes_length
string :trailing_bytes, length: :trailing_bytes_length

# TODO: use keyword_init when we dropped Ruby 2.4 support
Credential =
Struct.new(:id, :public_key) do
Struct.new(:id, :public_key, :algorithm, keyword_init: true) do
def public_key_object
COSE::Key.deserialize(public_key).to_pkey
end
Expand All @@ -47,7 +46,7 @@ def aaguid
def credential
@credential ||=
if valid?
Credential.new(id, public_key)
Credential.new(id: id, public_key: public_key, algorithm: algorithm)
end
end

Expand All @@ -59,10 +58,16 @@ def length

private

def algorithm
COSE::Algorithm.find(cose_key.alg).name
end

def valid_credential_public_key?
cose_key = COSE::Key.deserialize(public_key)
!!cose_key.alg
end

!!cose_key.alg && WebAuthn.configuration.algorithms.include?(COSE::Algorithm.find(cose_key.alg).name)
def cose_key
@cose_key ||= COSE::Key.deserialize(public_key)
end

def public_key
Expand Down
11 changes: 6 additions & 5 deletions lib/webauthn/authenticator_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ class UserPresenceVerificationError < VerificationError; end
class UserVerifiedVerificationError < VerificationError; end

class AuthenticatorResponse
def initialize(client_data_json:)
def initialize(client_data_json:, relying_party: WebAuthn.configuration.relying_party)
@client_data_json = client_data_json
@relying_party = relying_party
end

def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
expected_origin ||= WebAuthn.configuration.origin || raise("Unspecified expected origin")
rp_id ||= WebAuthn.configuration.rp_id
expected_origin ||= relying_party.origin || raise("Unspecified expected origin")
rp_id ||= relying_party.id

verify_item(:type)
verify_item(:token_binding)
Expand All @@ -34,7 +35,7 @@ def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp
verify_item(:authenticator_data)
verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))

if !WebAuthn.configuration.silent_authentication
if !relying_party.silent_authentication
verify_item(:user_presence)
end

Expand All @@ -57,7 +58,7 @@ def client_data

private

attr_reader :client_data_json
attr_reader :client_data_json, :relying_party

def verify_item(item, *args)
if send("valid_#{item}?", *args)
Expand Down
74 changes: 36 additions & 38 deletions lib/webauthn/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# frozen_string_literal: true

require "openssl"
require "webauthn/encoder"
require "webauthn/error"
require 'forwardable'
require 'webauthn/relying_party'

module WebAuthn
def self.configuration
Expand All @@ -13,50 +12,49 @@ def self.configure
yield(configuration)
end

class RootCertificateFinderNotSupportedError < Error; end

class Configuration
DEFAULT_ALGORITHMS = ["ES256", "PS256", "RS256"].compact.freeze

attr_accessor :algorithms
attr_accessor :encoding
attr_accessor :origin
attr_accessor :rp_id
attr_accessor :rp_name
attr_accessor :verify_attestation_statement
attr_accessor :credential_options_timeout
attr_accessor :silent_authentication
attr_accessor :acceptable_attestation_types
attr_reader :attestation_root_certificates_finders
extend Forwardable

def_delegators :@relying_party,
:algorithms,
:algorithms=,
:encoding,
:encoding=,
:origin,
:origin=,
:verify_attestation_statement,
:verify_attestation_statement=,
:credential_options_timeout,
:credential_options_timeout=,
:silent_authentication,
:silent_authentication=,
:acceptable_attestation_types,
:acceptable_attestation_types=,
:attestation_root_certificates_finders,
:attestation_root_certificates_finders=,
:encoder,
:encoder=

attr_reader :relying_party

def initialize
@algorithms = DEFAULT_ALGORITHMS.dup
@encoding = WebAuthn::Encoder::STANDARD_ENCODING
@verify_attestation_statement = true
@credential_options_timeout = 120000
@silent_authentication = false
@acceptable_attestation_types = ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA', 'AnonCA']
@attestation_root_certificates_finders = []
@relying_party = RelyingParty.new
end

# This is the user-data encoder.
# Used to decode user input and to encode data provided to the user.
def encoder
@encoder ||= WebAuthn::Encoder.new(encoding)
def rp_name
relying_party.name
end

def attestation_root_certificates_finders=(finders)
if !finders.respond_to?(:each)
finders = [finders]
end
def rp_name=(name)
relying_party.name = name
end

finders.each do |finder|
unless finder.respond_to?(:find)
raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
end
end
def rp_id
relying_party.id
end

@attestation_root_certificates_finders = finders
def rp_id=(id)
relying_party.id = id
end
end
end
Loading