Skip to content

Commit

Permalink
Merge pull request cedarcode#368 from cedarcode/relying_party-3.0
Browse files Browse the repository at this point in the history
3.0.0.alpha2
  • Loading branch information
bdewater authored Sep 12, 2022
2 parents 255ca22 + d58add5 commit d7022a9
Show file tree
Hide file tree
Showing 26 changed files with 648 additions and 143 deletions.
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

0 comments on commit d7022a9

Please sign in to comment.