webauthn 2.5.2 → 3.0.0.alpha2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f07d44a7778995c6bd2531b26f61e8974ba6ccc8dcf5ff1f21886f353423d598
4
- data.tar.gz: ecb287679447b3074065f2b06cb0f38b20c9a94114ed2b592d93e99874dfda1a
3
+ metadata.gz: 9f9f14328571fd39b5e19c9a6ec5b427cfd4dea9fc2ab4774135f4453e14b676
4
+ data.tar.gz: 1e33b7564ea1b3b8aeb32864782a0dad1e0207f1c70c037eb80140db2272ae01
5
5
  SHA512:
6
- metadata.gz: 98811fdbdb15cd80defb95a75de555a3775864bb1c1f1ac94af4a605af2fa3acf7908d91a120b5c3e214af890469d5f2194e5e037a9f8a40b03935159be6a1f1
7
- data.tar.gz: 8d90a9d37fb83a0ecf5e0c29e8f5154a0ee2170da994238b55fcc4042cb84f6b7fb4e09386e704a2d83f363ef0864fb1fa60c913eea3ae2754e5189435302570
6
+ metadata.gz: b1e22bb07b1ebced7851c592a2a4e8b0e6772e502f402bd841383f8071963e3b0df8c75da66db5623840a32f14799439d09a52c8dabcdd4e5edbef8a0a419a00
7
+ data.tar.gz: 1a1b1c647a06398edad9d1da94ca8de53202e184f8930b2fe001f82e17d8416516cbaf4db4fd012fd44d1ac5da32c6d8d32c971507584bf00ef9d74b15d535b5
@@ -21,7 +21,6 @@ jobs:
21
21
  - '2.7'
22
22
  - '2.6'
23
23
  - '2.5'
24
- - '2.4'
25
24
  - truffleruby
26
25
  steps:
27
26
  - uses: actions/checkout@v2
data/.rubocop.yml CHANGED
@@ -7,7 +7,7 @@ inherit_mode:
7
7
  - AllowedNames
8
8
 
9
9
  AllCops:
10
- TargetRubyVersion: 2.4
10
+ TargetRubyVersion: 2.5
11
11
  DisabledByDefault: true
12
12
  NewCops: disable
13
13
  Exclude:
data/CHANGELOG.md CHANGED
@@ -1,12 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.0.0.alpha2] - 2022-09-12
4
+
5
+ ### Added
6
+
7
+ - 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])
8
+
9
+ ### BREAKING CHANGES
10
+
11
+ - Bumped minimum required Ruby version to 2.5 ([@bdewater])
12
+
3
13
  ## [v3.0.0.alpha1] - 2020-06-27
4
14
 
5
15
  ### Added
6
16
 
7
17
  - Ability to define multiple relying parties with the introduction of the `WebAuthn::RelyingParty` class ([@padulafacundo], [@brauliomartinezlm])
8
18
 
9
- ## [v2.5.1] - 2022-07-13
19
+ ## [v2.5.2] - 2022-07-13
10
20
 
11
21
  ### Added
12
22
 
@@ -340,8 +350,9 @@ Note: Both additions should help making it compatible with Chrome for Android 70
340
350
  - `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
341
351
  - Works with ruby 2.5
342
352
 
343
- [v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha1/
344
- [v2.5.2]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.0...v2.5.2/
353
+ [v3.0.0.alpha2]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha2/
354
+ [v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.3.0...v3.0.0.alpha1
355
+ [v2.5.2]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.1...v2.5.2/
345
356
  [v2.5.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.0...v2.5.1/
346
357
  [v2.5.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.1...v2.5.0/
347
358
  [v2.4.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.0...v2.4.1/
@@ -40,7 +40,7 @@ Intead of the [Global Configuration](../README.md#configuration) you place in `c
40
40
  # In this case the default would be "admin.example.com", but you can set it to
41
41
  # the suffix "example.com"
42
42
  #
43
- # rp_id: "example.com"
43
+ # id: "example.com"
44
44
 
45
45
  # Configure preferred binary-to-text encoding scheme. This should match the encoding scheme
46
46
  # used in your client-side (user agent) code before sending the credential to the server.
@@ -171,4 +171,4 @@ end
171
171
 
172
172
  Adding a configuration for a new instance does not mean you need to get rid of your Global configuration. They can co-exist in your application and be both available for the different usages you might have. `WebAuthn.configuration.relying_party` will always return the global one while `WebAuthn::RelyingParty.new`, executed anywhere in your codebase, will allow you to create a different instance as you see the need. They will not collide and instead operate in isolation without any shared state.
173
173
 
174
- The gem API described in the current [Usage](../README.md#usage) section for the [Global Configuration](../README.md#configuration) approach will still valid but the [Instance Based API](#instance-based-api) also works with the global `relying_party` that is maintain globally at `WebAuthn.configuration.relying_party`.
174
+ The gem API described in the current [Usage](../README.md#usage) section for the [Global Configuration](../README.md#configuration) approach will still valid but the [Instance Based API](#instance-based-api) also works with the global `relying_party` that is maintain globally at `WebAuthn.configuration.relying_party`.
@@ -10,18 +10,22 @@ module WebAuthn
10
10
  class AttestationObject
11
11
  extend Forwardable
12
12
 
13
- def self.deserialize(attestation_object)
14
- from_map(CBOR.decode(attestation_object))
13
+ def self.deserialize(attestation_object, relying_party)
14
+ from_map(CBOR.decode(attestation_object), relying_party)
15
15
  end
16
16
 
17
- def self.from_map(map)
17
+ def self.from_map(map, relying_party)
18
18
  new(
19
19
  authenticator_data: WebAuthn::AuthenticatorData.deserialize(map["authData"]),
20
- attestation_statement: WebAuthn::AttestationStatement.from(map["fmt"], map["attStmt"])
20
+ attestation_statement: WebAuthn::AttestationStatement.from(
21
+ map["fmt"],
22
+ map["attStmt"],
23
+ relying_party: relying_party
24
+ )
21
25
  )
22
26
  end
23
27
 
24
- attr_reader :authenticator_data, :attestation_statement
28
+ attr_reader :authenticator_data, :attestation_statement, :relying_party
25
29
 
26
30
  def initialize(authenticator_data:, attestation_statement:)
27
31
  @authenticator_data = authenticator_data
@@ -28,8 +28,9 @@ module WebAuthn
28
28
  class Base
29
29
  AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
30
30
 
31
- def initialize(statement)
31
+ def initialize(statement, relying_party = WebAuthn.configuration.relying_party)
32
32
  @statement = statement
33
+ @relying_party = relying_party
33
34
  end
34
35
 
35
36
  def valid?(_authenticator_data, _client_data_hash)
@@ -50,7 +51,7 @@ module WebAuthn
50
51
 
51
52
  private
52
53
 
53
- attr_reader :statement
54
+ attr_reader :statement, :relying_party
54
55
 
55
56
  def matching_aaguid?(attested_credential_data_aaguid)
56
57
  extension = attestation_certificate&.find_extension(AAGUID_EXTENSION_OID)
@@ -93,10 +94,10 @@ module WebAuthn
93
94
 
94
95
  def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
95
96
  if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
96
- configuration.acceptable_attestation_types.include?(attestation_type) &&
97
+ relying_party.acceptable_attestation_types.include?(attestation_type) &&
97
98
  valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
98
99
  else
99
- configuration.acceptable_attestation_types.include?(attestation_type)
100
+ relying_party.acceptable_attestation_types.include?(attestation_type)
100
101
  end
101
102
  end
102
103
 
@@ -120,7 +121,7 @@ module WebAuthn
120
121
 
121
122
  def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
122
123
  root_certificates =
123
- configuration.attestation_root_certificates_finders.reduce([]) do |certs, finder|
124
+ relying_party.attestation_root_certificates_finders.reduce([]) do |certs, finder|
124
125
  if certs.empty?
125
126
  finder.find(
126
127
  attestation_format: format,
@@ -158,14 +159,10 @@ module WebAuthn
158
159
  def cose_algorithm
159
160
  @cose_algorithm ||=
160
161
  COSE::Algorithm.find(algorithm).tap do |alg|
161
- alg && configuration.algorithms.include?(alg.name) ||
162
+ alg && relying_party.algorithms.include?(alg.name) ||
162
163
  raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
163
164
  end
164
165
  end
165
-
166
- def configuration
167
- WebAuthn.configuration
168
- end
169
166
  end
170
167
  end
171
168
  end
@@ -31,11 +31,11 @@ module WebAuthn
31
31
  ATTESTATION_FORMAT_APPLE => WebAuthn::AttestationStatement::Apple
32
32
  }.freeze
33
33
 
34
- def self.from(format, statement)
34
+ def self.from(format, statement, relying_party: WebAuthn.configuration.relying_party)
35
35
  klass = FORMAT_TO_CLASS[format]
36
36
 
37
37
  if klass
38
- klass.new(statement)
38
+ klass.new(statement, relying_party)
39
39
  else
40
40
  raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
41
41
  end
@@ -10,8 +10,8 @@ module WebAuthn
10
10
  class SignCountVerificationError < VerificationError; end
11
11
 
12
12
  class AuthenticatorAssertionResponse < AuthenticatorResponse
13
- def self.from_client(response)
14
- encoder = WebAuthn.configuration.encoder
13
+ def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
14
+ encoder = relying_party.encoder
15
15
 
16
16
  user_handle =
17
17
  if response["userHandle"]
@@ -22,7 +22,8 @@ module WebAuthn
22
22
  authenticator_data: encoder.decode(response["authenticatorData"]),
23
23
  client_data_json: encoder.decode(response["clientDataJSON"]),
24
24
  signature: encoder.decode(response["signature"]),
25
- user_handle: user_handle
25
+ user_handle: user_handle,
26
+ relying_party: relying_party
26
27
  )
27
28
  end
28
29
 
@@ -18,12 +18,13 @@ module WebAuthn
18
18
  class AuthenticatorAttestationResponse < AuthenticatorResponse
19
19
  extend Forwardable
20
20
 
21
- def self.from_client(response)
22
- encoder = WebAuthn.configuration.encoder
21
+ def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
22
+ encoder = relying_party.encoder
23
23
 
24
24
  new(
25
25
  attestation_object: encoder.decode(response["attestationObject"]),
26
- client_data_json: encoder.decode(response["clientDataJSON"])
26
+ client_data_json: encoder.decode(response["clientDataJSON"]),
27
+ relying_party: relying_party
27
28
  )
28
29
  end
29
30
 
@@ -33,13 +34,14 @@ module WebAuthn
33
34
  super(**options)
34
35
 
35
36
  @attestation_object_bytes = attestation_object
37
+ @relying_party = relying_party
36
38
  end
37
39
 
38
40
  def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
39
41
  super
40
42
 
41
43
  verify_item(:attested_credential)
42
- if WebAuthn.configuration.verify_attestation_statement
44
+ if relying_party.verify_attestation_statement
43
45
  verify_item(:attestation_statement)
44
46
  end
45
47
 
@@ -47,7 +49,7 @@ module WebAuthn
47
49
  end
48
50
 
49
51
  def attestation_object
50
- @attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes)
52
+ @attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes, relying_party)
51
53
  end
52
54
 
53
55
  def_delegators(
@@ -63,14 +65,15 @@ module WebAuthn
63
65
 
64
66
  private
65
67
 
66
- attr_reader :attestation_object_bytes
68
+ attr_reader :attestation_object_bytes, :relying_party
67
69
 
68
70
  def type
69
71
  WebAuthn::TYPES[:create]
70
72
  end
71
73
 
72
74
  def valid_attested_credential?
73
- attestation_object.valid_attested_credential?
75
+ attestation_object.valid_attested_credential? &&
76
+ relying_party.algorithms.include?(authenticator_data.credential.algorithm)
74
77
  end
75
78
 
76
79
  def valid_attestation_statement?
@@ -22,9 +22,8 @@ module WebAuthn
22
22
  count_bytes_remaining :trailing_bytes_length
23
23
  string :trailing_bytes, length: :trailing_bytes_length
24
24
 
25
- # TODO: use keyword_init when we dropped Ruby 2.4 support
26
25
  Credential =
27
- Struct.new(:id, :public_key) do
26
+ Struct.new(:id, :public_key, :algorithm, keyword_init: true) do
28
27
  def public_key_object
29
28
  COSE::Key.deserialize(public_key).to_pkey
30
29
  end
@@ -47,7 +46,7 @@ module WebAuthn
47
46
  def credential
48
47
  @credential ||=
49
48
  if valid?
50
- Credential.new(id, public_key)
49
+ Credential.new(id: id, public_key: public_key, algorithm: algorithm)
51
50
  end
52
51
  end
53
52
 
@@ -59,10 +58,16 @@ module WebAuthn
59
58
 
60
59
  private
61
60
 
61
+ def algorithm
62
+ COSE::Algorithm.find(cose_key.alg).name
63
+ end
64
+
62
65
  def valid_credential_public_key?
63
- cose_key = COSE::Key.deserialize(public_key)
66
+ !!cose_key.alg
67
+ end
64
68
 
65
- !!cose_key.alg && WebAuthn.configuration.algorithms.include?(COSE::Algorithm.find(cose_key.alg).name)
69
+ def cose_key
70
+ @cose_key ||= COSE::Key.deserialize(public_key)
66
71
  end
67
72
 
68
73
  def public_key
@@ -19,13 +19,14 @@ module WebAuthn
19
19
  class UserVerifiedVerificationError < VerificationError; end
20
20
 
21
21
  class AuthenticatorResponse
22
- def initialize(client_data_json:)
22
+ def initialize(client_data_json:, relying_party: WebAuthn.configuration.relying_party)
23
23
  @client_data_json = client_data_json
24
+ @relying_party = relying_party
24
25
  end
25
26
 
26
27
  def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
27
- expected_origin ||= WebAuthn.configuration.origin || raise("Unspecified expected origin")
28
- rp_id ||= WebAuthn.configuration.rp_id
28
+ expected_origin ||= relying_party.origin || raise("Unspecified expected origin")
29
+ rp_id ||= relying_party.id
29
30
 
30
31
  verify_item(:type)
31
32
  verify_item(:token_binding)
@@ -34,7 +35,7 @@ module WebAuthn
34
35
  verify_item(:authenticator_data)
35
36
  verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
36
37
 
37
- if !WebAuthn.configuration.silent_authentication
38
+ if !relying_party.silent_authentication
38
39
  verify_item(:user_presence)
39
40
  end
40
41
 
@@ -57,7 +58,7 @@ module WebAuthn
57
58
 
58
59
  private
59
60
 
60
- attr_reader :client_data_json
61
+ attr_reader :client_data_json, :relying_party
61
62
 
62
63
  def verify_item(item, *args)
63
64
  if send("valid_#{item}?", *args)
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openssl"
4
- require "webauthn/encoder"
5
- require "webauthn/error"
3
+ require 'forwardable'
4
+ require 'webauthn/relying_party'
6
5
 
7
6
  module WebAuthn
8
7
  def self.configuration
@@ -13,50 +12,49 @@ module WebAuthn
13
12
  yield(configuration)
14
13
  end
15
14
 
16
- class RootCertificateFinderNotSupportedError < Error; end
17
-
18
15
  class Configuration
19
- DEFAULT_ALGORITHMS = ["ES256", "PS256", "RS256"].compact.freeze
20
-
21
- attr_accessor :algorithms
22
- attr_accessor :encoding
23
- attr_accessor :origin
24
- attr_accessor :rp_id
25
- attr_accessor :rp_name
26
- attr_accessor :verify_attestation_statement
27
- attr_accessor :credential_options_timeout
28
- attr_accessor :silent_authentication
29
- attr_accessor :acceptable_attestation_types
30
- attr_reader :attestation_root_certificates_finders
16
+ extend Forwardable
17
+
18
+ def_delegators :@relying_party,
19
+ :algorithms,
20
+ :algorithms=,
21
+ :encoding,
22
+ :encoding=,
23
+ :origin,
24
+ :origin=,
25
+ :verify_attestation_statement,
26
+ :verify_attestation_statement=,
27
+ :credential_options_timeout,
28
+ :credential_options_timeout=,
29
+ :silent_authentication,
30
+ :silent_authentication=,
31
+ :acceptable_attestation_types,
32
+ :acceptable_attestation_types=,
33
+ :attestation_root_certificates_finders,
34
+ :attestation_root_certificates_finders=,
35
+ :encoder,
36
+ :encoder=
37
+
38
+ attr_reader :relying_party
31
39
 
32
40
  def initialize
33
- @algorithms = DEFAULT_ALGORITHMS.dup
34
- @encoding = WebAuthn::Encoder::STANDARD_ENCODING
35
- @verify_attestation_statement = true
36
- @credential_options_timeout = 120000
37
- @silent_authentication = false
38
- @acceptable_attestation_types = ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA', 'AnonCA']
39
- @attestation_root_certificates_finders = []
41
+ @relying_party = RelyingParty.new
40
42
  end
41
43
 
42
- # This is the user-data encoder.
43
- # Used to decode user input and to encode data provided to the user.
44
- def encoder
45
- @encoder ||= WebAuthn::Encoder.new(encoding)
44
+ def rp_name
45
+ relying_party.name
46
46
  end
47
47
 
48
- def attestation_root_certificates_finders=(finders)
49
- if !finders.respond_to?(:each)
50
- finders = [finders]
51
- end
48
+ def rp_name=(name)
49
+ relying_party.name = name
50
+ end
52
51
 
53
- finders.each do |finder|
54
- unless finder.respond_to?(:find)
55
- raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
56
- end
57
- end
52
+ def rp_id
53
+ relying_party.id
54
+ end
58
55
 
59
- @attestation_root_certificates_finders = finders
56
+ def rp_id=(id)
57
+ relying_party.id = id
60
58
  end
61
59
  end
62
60
  end
@@ -4,6 +4,7 @@ require "webauthn/public_key_credential/creation_options"
4
4
  require "webauthn/public_key_credential/request_options"
5
5
  require "webauthn/public_key_credential_with_assertion"
6
6
  require "webauthn/public_key_credential_with_attestation"
7
+ require "webauthn/relying_party"
7
8
 
8
9
  module WebAuthn
9
10
  module Credential
@@ -15,12 +16,12 @@ module WebAuthn
15
16
  WebAuthn::PublicKeyCredential::RequestOptions.new(**keyword_arguments)
16
17
  end
17
18
 
18
- def self.from_create(credential)
19
- WebAuthn::PublicKeyCredentialWithAttestation.from_client(credential)
19
+ def self.from_create(credential, relying_party: WebAuthn.configuration.relying_party)
20
+ WebAuthn::PublicKeyCredentialWithAttestation.from_client(credential, relying_party: relying_party)
20
21
  end
21
22
 
22
- def self.from_get(credential)
23
- WebAuthn::PublicKeyCredentialWithAssertion.from_client(credential)
23
+ def self.from_get(credential, relying_party: WebAuthn.configuration.relying_party)
24
+ WebAuthn::PublicKeyCredentialWithAssertion.from_client(credential, relying_party: relying_party)
24
25
  end
25
26
  end
26
27
  end
@@ -10,7 +10,7 @@ module WebAuthn
10
10
  class FakeClient
11
11
  TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
12
12
 
13
- attr_reader :origin, :token_binding
13
+ attr_reader :origin, :token_binding, :encoding
14
14
 
15
15
  def initialize(
16
16
  origin = fake_origin,
@@ -111,7 +111,7 @@ module WebAuthn
111
111
 
112
112
  private
113
113
 
114
- attr_reader :authenticator, :encoding
114
+ attr_reader :authenticator
115
115
 
116
116
  def data_json_for(method, challenge)
117
117
  data = {
@@ -39,8 +39,8 @@ module WebAuthn
39
39
 
40
40
  @rp =
41
41
  if rp.is_a?(Hash)
42
- rp[:name] ||= configuration.rp_name
43
- rp[:id] ||= configuration.rp_id
42
+ rp[:name] ||= relying_party.name
43
+ rp[:id] ||= relying_party.id
44
44
 
45
45
  RPEntity.new(**rp)
46
46
  else
@@ -76,7 +76,7 @@ module WebAuthn
76
76
  end
77
77
 
78
78
  def pub_key_cred_params_from_algs
79
- Array(algs || configuration.algorithms).map do |alg|
79
+ Array(algs || relying_party.algorithms).map do |alg|
80
80
  alg_id =
81
81
  if alg.is_a?(String) || alg.is_a?(Symbol)
82
82
  COSE::Algorithm.by_name(alg.to_s).id
@@ -8,10 +8,11 @@ module WebAuthn
8
8
  class Options
9
9
  CHALLENGE_LENGTH = 32
10
10
 
11
- attr_reader :timeout, :extensions
11
+ attr_reader :timeout, :extensions, :relying_party
12
12
 
13
- def initialize(timeout: default_timeout, extensions: nil)
14
- @timeout = timeout
13
+ def initialize(timeout: nil, extensions: nil, relying_party: WebAuthn.configuration.relying_party)
14
+ @relying_party = relying_party
15
+ @timeout = timeout || default_timeout
15
16
  @extensions = extensions
16
17
  end
17
18
 
@@ -49,7 +50,7 @@ module WebAuthn
49
50
  end
50
51
 
51
52
  def encoder
52
- WebAuthn.configuration.encoder
53
+ relying_party.encoder
53
54
  end
54
55
 
55
56
  def raw_challenge
@@ -57,11 +58,7 @@ module WebAuthn
57
58
  end
58
59
 
59
60
  def default_timeout
60
- configuration.credential_options_timeout
61
- end
62
-
63
- def configuration
64
- WebAuthn.configuration
61
+ relying_party.credential_options_timeout
65
62
  end
66
63
 
67
64
  def as_public_key_descriptors(ids)
@@ -10,7 +10,7 @@ module WebAuthn
10
10
  def initialize(rp_id: nil, allow_credentials: nil, allow: nil, user_verification: nil, **keyword_arguments)
11
11
  super(**keyword_arguments)
12
12
 
13
- @rp_id = rp_id || configuration.rp_id
13
+ @rp_id = rp_id || relying_party.id
14
14
  @allow_credentials = allow_credentials
15
15
  @allow = allow
16
16
  @user_verification = user_verification
@@ -6,22 +6,31 @@ module WebAuthn
6
6
  class PublicKeyCredential
7
7
  attr_reader :type, :id, :raw_id, :client_extension_outputs, :response
8
8
 
9
- def self.from_client(credential)
9
+ def self.from_client(credential, relying_party: WebAuthn.configuration.relying_party)
10
10
  new(
11
11
  type: credential["type"],
12
12
  id: credential["id"],
13
- raw_id: WebAuthn.configuration.encoder.decode(credential["rawId"]),
13
+ raw_id: relying_party.encoder.decode(credential["rawId"]),
14
14
  client_extension_outputs: credential["clientExtensionResults"],
15
- response: response_class.from_client(credential["response"])
15
+ response: response_class.from_client(credential["response"], relying_party: relying_party),
16
+ encoder: relying_party.encoder
16
17
  )
17
18
  end
18
19
 
19
- def initialize(type:, id:, raw_id:, client_extension_outputs: {}, response:)
20
+ def initialize(
21
+ type:,
22
+ id:,
23
+ raw_id:,
24
+ response:,
25
+ client_extension_outputs: {},
26
+ encoder: WebAuthn.configuration.encoder
27
+ )
20
28
  @type = type
21
29
  @id = id
22
30
  @raw_id = raw_id
23
31
  @client_extension_outputs = client_extension_outputs
24
32
  @response = response
33
+ @encoder = encoder
25
34
  end
26
35
 
27
36
  def verify(*_args)
@@ -41,6 +50,8 @@ module WebAuthn
41
50
 
42
51
  private
43
52
 
53
+ attr_reader :encoder
54
+
44
55
  def valid_type?
45
56
  type == TYPE_PUBLIC_KEY
46
57
  end
@@ -52,9 +63,5 @@ module WebAuthn
52
63
  def authenticator_data
53
64
  response&.authenticator_data
54
65
  end
55
-
56
- def encoder
57
- WebAuthn.configuration.encoder
58
- end
59
66
  end
60
67
  end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "webauthn/credential"
5
+ require "webauthn/encoder"
6
+ require "webauthn/error"
7
+
8
+ module WebAuthn
9
+ class RootCertificateFinderNotSupportedError < Error; end
10
+
11
+ class RelyingParty
12
+ def self.if_pss_supported(algorithm)
13
+ OpenSSL::PKey::RSA.instance_methods.include?(:verify_pss) ? algorithm : nil
14
+ end
15
+
16
+ DEFAULT_ALGORITHMS = ["ES256", "PS256", "RS256"].compact.freeze
17
+
18
+ def initialize(
19
+ algorithms: DEFAULT_ALGORITHMS.dup,
20
+ encoding: WebAuthn::Encoder::STANDARD_ENCODING,
21
+ origin: nil,
22
+ id: nil,
23
+ name: nil,
24
+ verify_attestation_statement: true,
25
+ credential_options_timeout: 120000,
26
+ silent_authentication: false,
27
+ acceptable_attestation_types: ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA', 'AnonCA'],
28
+ attestation_root_certificates_finders: []
29
+ )
30
+ @algorithms = algorithms
31
+ @encoding = encoding
32
+ @origin = origin
33
+ @id = id
34
+ @name = name
35
+ @verify_attestation_statement = verify_attestation_statement
36
+ @credential_options_timeout = credential_options_timeout
37
+ @silent_authentication = silent_authentication
38
+ @acceptable_attestation_types = acceptable_attestation_types
39
+ self.attestation_root_certificates_finders = attestation_root_certificates_finders
40
+ end
41
+
42
+ attr_accessor :algorithms,
43
+ :encoding,
44
+ :origin,
45
+ :id,
46
+ :name,
47
+ :verify_attestation_statement,
48
+ :credential_options_timeout,
49
+ :silent_authentication,
50
+ :acceptable_attestation_types
51
+
52
+ attr_reader :attestation_root_certificates_finders
53
+
54
+ # This is the user-data encoder.
55
+ # Used to decode user input and to encode data provided to the user.
56
+ def encoder
57
+ @encoder ||= WebAuthn::Encoder.new(encoding)
58
+ end
59
+
60
+ def attestation_root_certificates_finders=(finders)
61
+ if !finders.respond_to?(:each)
62
+ finders = [finders]
63
+ end
64
+
65
+ finders.each do |finder|
66
+ unless finder.respond_to?(:find)
67
+ raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
68
+ end
69
+ end
70
+
71
+ @attestation_root_certificates_finders = finders
72
+ end
73
+
74
+ def options_for_registration(**keyword_arguments)
75
+ WebAuthn::Credential.options_for_create(
76
+ **keyword_arguments,
77
+ relying_party: self
78
+ )
79
+ end
80
+
81
+ def verify_registration(raw_credential, challenge, user_verification: nil)
82
+ webauthn_credential = WebAuthn::Credential.from_create(raw_credential, relying_party: self)
83
+
84
+ if webauthn_credential.verify(challenge, user_verification: user_verification)
85
+ webauthn_credential
86
+ end
87
+ end
88
+
89
+ def options_for_authentication(**keyword_arguments)
90
+ WebAuthn::Credential.options_for_get(
91
+ **keyword_arguments,
92
+ relying_party: self
93
+ )
94
+ end
95
+
96
+ def verify_authentication(
97
+ raw_credential,
98
+ challenge,
99
+ user_verification: nil,
100
+ public_key: nil,
101
+ sign_count: nil
102
+ )
103
+ webauthn_credential = WebAuthn::Credential.from_get(raw_credential, relying_party: self)
104
+
105
+ stored_credential = yield(webauthn_credential) if block_given?
106
+
107
+ if webauthn_credential.verify(
108
+ challenge,
109
+ public_key: public_key || stored_credential.public_key,
110
+ sign_count: sign_count || stored_credential.sign_count,
111
+ user_verification: user_verification
112
+ )
113
+ block_given? ? [webauthn_credential, stored_credential] : webauthn_credential
114
+ end
115
+ end
116
+ end
117
+ end
@@ -31,7 +31,10 @@ module WebAuthn
31
31
  @credential ||=
32
32
  begin
33
33
  hash = authenticator_data.send(:credential)
34
- WebAuthn::AuthenticatorData::AttestedCredentialData::Credential.new(hash[:id], hash[:public_key].serialize)
34
+ WebAuthn::AuthenticatorData::AttestedCredentialData::Credential.new(
35
+ id: hash[:id],
36
+ public_key: hash[:public_key].serialize
37
+ )
35
38
  end
36
39
  end
37
40
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebAuthn
4
- VERSION = "2.5.2"
4
+ VERSION = "3.0.0.alpha2"
5
5
  end
data/webauthn.gemspec CHANGED
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ["lib"]
33
33
 
34
- spec.required_ruby_version = ">= 2.4"
34
+ spec.required_ruby_version = ">= 2.5"
35
35
 
36
36
  spec.add_dependency "android_key_attestation", "~> 0.3.0"
37
37
  spec.add_dependency "awrence", "~> 1.1"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webauthn
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.2
4
+ version: 3.0.0.alpha2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gonzalo Rodriguez
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-07-14 00:00:00.000000000 Z
12
+ date: 2022-09-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: android_key_attestation
@@ -302,6 +302,7 @@ files:
302
302
  - lib/webauthn/public_key_credential/user_entity.rb
303
303
  - lib/webauthn/public_key_credential_with_assertion.rb
304
304
  - lib/webauthn/public_key_credential_with_attestation.rb
305
+ - lib/webauthn/relying_party.rb
305
306
  - lib/webauthn/u2f_migrator.rb
306
307
  - lib/webauthn/version.rb
307
308
  - webauthn.gemspec
@@ -320,12 +321,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
320
321
  requirements:
321
322
  - - ">="
322
323
  - !ruby/object:Gem::Version
323
- version: '2.4'
324
+ version: '2.5'
324
325
  required_rubygems_version: !ruby/object:Gem::Requirement
325
326
  requirements:
326
- - - ">="
327
+ - - ">"
327
328
  - !ruby/object:Gem::Version
328
- version: '0'
329
+ version: 1.3.1
329
330
  requirements: []
330
331
  rubygems_version: 3.2.32
331
332
  signing_key: