webauthn 2.4.1 → 3.0.0.alpha1

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: e1ffc928d4b54cc4c19c946a30e3e0d1e6a56b317d9d82376bb7f7f9693ee88a
4
- data.tar.gz: fe42ab966c5ec4ef20089f147276ba485496b8bcf284c1a16a05606c217a434a
3
+ metadata.gz: 1f418eb52a085d8e7c03bd1c3d11b88ed8fe467f4ec01d3178836689d470f436
4
+ data.tar.gz: 4ab67e8804cbd7d785e29b94760af6db9d6b1e52de1a58bafc74aed19b5b7e21
5
5
  SHA512:
6
- metadata.gz: bd77e2c99e1a08f63dc1986edef737e64872f48108e5e664c8517c7bea11e22a9b4c2bf6e07f7d370d09c3ba3ba3264dff309fd792a47c239d359b27bdd070db
7
- data.tar.gz: 36a50f38e8c7dac6e33e0494d9ebeac33587ace21d4cad022be1984bd7f915112ba99c6fb975186e63d2757156c09014538a8cfda55db8edeac5ef0327c42bc0
6
+ metadata.gz: ccdcd22e494079eb67c122c03e3061166f91de50dd0c2bf8662748c976483a9d472fa9f8d6b67db9abf484e8eac182195b5f1e1cca8aaaf88146d831919f1c93
7
+ data.tar.gz: b2506b530c796ee57e5d037e7dd3692b1e359408fd9757141b6c4f042c43d57344baa09dc63d17ace549927eeea50abaa8c0771a0ab6da0ece5d880362d890db
data/.rubocop.yml CHANGED
@@ -24,9 +24,6 @@ Layout:
24
24
  Layout/ClassStructure:
25
25
  Enabled: true
26
26
 
27
- Layout/EmptyLinesAroundAttributeAccessor:
28
- Enabled: true
29
-
30
27
  Layout/FirstMethodArgumentLineBreak:
31
28
  Enabled: true
32
29
 
@@ -41,54 +38,9 @@ Layout/MultilineAssignmentLayout:
41
38
  Layout/MultilineMethodArgumentLineBreaks:
42
39
  Enabled: true
43
40
 
44
- Layout/SpaceAroundMethodCallOperator:
45
- Enabled: true
46
-
47
41
  Lint:
48
42
  Enabled: true
49
43
 
50
- Lint/DeprecatedOpenSSLConstant:
51
- Enabled: true
52
-
53
- Lint/MixedRegexpCaptureTypes:
54
- Enabled: true
55
-
56
- Lint/RaiseException:
57
- Enabled: true
58
-
59
- Lint/StructNewOverride:
60
- Enabled: true
61
-
62
- Lint/BinaryOperatorWithIdenticalOperands:
63
- Enabled: true
64
-
65
- Lint/DuplicateElsifCondition:
66
- Enabled: true
67
-
68
- Lint/DuplicateRescueException:
69
- Enabled: true
70
-
71
- Lint/EmptyConditionalBody:
72
- Enabled: true
73
-
74
- Lint/FloatComparison:
75
- Enabled: true
76
-
77
- Lint/MissingSuper:
78
- Enabled: true
79
-
80
- Lint/OutOfRangeRegexpRef:
81
- Enabled: true
82
-
83
- Lint/SelfAssignment:
84
- Enabled: true
85
-
86
- Lint/TopLevelReturnWithArgument:
87
- Enabled: true
88
-
89
- Lint/UnreachableLoop:
90
- Enabled: true
91
-
92
44
  Naming:
93
45
  Enabled: true
94
46
 
data/CHANGELOG.md CHANGED
@@ -1,17 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## [v2.4.1] - 2021-02-15
4
-
5
- ### Fixed
6
-
7
- - Fix verification of new credential if no attestation provided and 'None' type is not among configured `acceptable_attestation_types`. I.e. reject it instead of letting it go through.
8
-
9
- ## [v2.4.0] - 2020-09-03
3
+ ## [v3.0.0.alpha1] - 2020-06-27
10
4
 
11
5
  ### Added
12
6
 
13
- - Support for ES256K credentials
14
- - `FakeClient#get` accepts `user_handle:` keyword argument ([@lgarron])
7
+ - Ability to define multiple relying parties with the introduction of the `WebAuthn::RelyingParty` class ([@padulafacundo], [@brauliomartinezlm])
15
8
 
16
9
  ## [v2.3.0] - 2020-06-27
17
10
 
@@ -307,8 +300,7 @@ Note: Both additions should help making it compatible with Chrome for Android 70
307
300
  - `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
308
301
  - Works with ruby 2.5
309
302
 
310
- [v2.4.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.0...v2.4.1/
311
- [v2.4.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.3.0...v2.4.0/
303
+ [v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha1/
312
304
  [v2.3.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.2.1...v2.3.0/
313
305
  [v2.2.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.2.0...v2.2.1/
314
306
  [v2.2.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.1.0...v2.2.0/
@@ -336,6 +328,7 @@ Note: Both additions should help making it compatible with Chrome for Android 70
336
328
  [v0.2.0]: https://github.com/cedarcode/webauthn-ruby/compare/v0.1.0...v0.2.0/
337
329
  [v0.1.0]: https://github.com/cedarcode/webauthn-ruby/compare/v0.0.0...v0.1.0/
338
330
 
331
+ [@brauliomartinezlm]: https://github.com/brauliomartinezlm
339
332
  [@bdewater]: https://github.com/bdewater
340
333
  [@jdongelmans]: https://github.com/jdongelmans
341
334
  [@kalebtesfay]: https://github.com/kalebtesfay
@@ -344,4 +337,3 @@ Note: Both additions should help making it compatible with Chrome for Android 70
344
337
  [@ssuttner]: https://github.com/ssuttner
345
338
  [@padulafacundo]: https://github.com/padulafacundo
346
339
  [@santiagorodriguez96]: https://github.com/santiagorodriguez96
347
- [@lgarron]: https://github.com/lgarron
data/SECURITY.md CHANGED
@@ -4,11 +4,9 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | ------- | ------------------ |
7
- | 2.4.z | :white_check_mark: |
8
- | 2.3.z | :white_check_mark: |
9
7
  | 2.2.z | :white_check_mark: |
10
- | 2.1.z | :x: |
11
- | 2.0.z | :x: |
8
+ | 2.1.z | :white_check_mark: |
9
+ | 2.0.z | :white_check_mark: |
12
10
  | 1.18.z | :white_check_mark: |
13
11
  | < 1.18 | :x: |
14
12
 
@@ -40,11 +40,4 @@ end
40
40
  COSE::Algorithm.register(RSAPKCS1Algorithm.new(-257, "RS256", hash_function: "SHA256"))
41
41
  COSE::Algorithm.register(RSAPKCS1Algorithm.new(-258, "RS384", hash_function: "SHA384"))
42
42
  COSE::Algorithm.register(RSAPKCS1Algorithm.new(-259, "RS512", hash_function: "SHA512"))
43
-
44
- # Patch openssl-signature_algorithm gem to support discouraged/deprecated RSA-PKCS#1 with SHA-1
45
- # (RS1 in JOSE/COSE terminology) algorithm needed for WebAuthn.
46
- OpenSSL::SignatureAlgorithm::RSAPKCS1.const_set(
47
- :ACCEPTED_HASH_FUNCTIONS,
48
- OpenSSL::SignatureAlgorithm::RSAPKCS1::ACCEPTED_HASH_FUNCTIONS + ["SHA1"]
49
- )
50
43
  COSE::Algorithm.register(RSAPKCS1Algorithm.new(-65535, "RS1", hash_function: "SHA1"))
@@ -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,11 +28,11 @@ module WebAuthn
28
28
  ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM
29
29
  }.freeze
30
30
 
31
- def self.from(format, statement)
31
+ def self.from(format, statement, relying_party: WebAuthn.configuration.relying_party)
32
32
  klass = FORMAT_TO_CLASS[format]
33
33
 
34
34
  if klass
35
- klass.new(statement)
35
+ klass.new(statement, relying_party)
36
36
  else
37
37
  raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
38
38
  end
@@ -26,8 +26,9 @@ module WebAuthn
26
26
  class Base
27
27
  AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
28
28
 
29
- def initialize(statement)
29
+ def initialize(statement, relying_party = WebAuthn.configuration.relying_party)
30
30
  @statement = statement
31
+ @relying_party = relying_party
31
32
  end
32
33
 
33
34
  def valid?(_authenticator_data, _client_data_hash)
@@ -54,7 +55,7 @@ module WebAuthn
54
55
 
55
56
  private
56
57
 
57
- attr_reader :statement
58
+ attr_reader :statement, :relying_party
58
59
 
59
60
  def matching_aaguid?(attested_credential_data_aaguid)
60
61
  extension = attestation_certificate&.extensions&.detect { |ext| ext.oid == AAGUID_EXTENSION_OID }
@@ -95,10 +96,10 @@ module WebAuthn
95
96
 
96
97
  def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
97
98
  if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
98
- configuration.acceptable_attestation_types.include?(attestation_type) &&
99
+ relying_party.acceptable_attestation_types.include?(attestation_type) &&
99
100
  valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
100
101
  else
101
- configuration.acceptable_attestation_types.include?(attestation_type)
102
+ relying_party.acceptable_attestation_types.include?(attestation_type)
102
103
  end
103
104
  end
104
105
 
@@ -122,7 +123,7 @@ module WebAuthn
122
123
 
123
124
  def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
124
125
  root_certificates =
125
- configuration.attestation_root_certificates_finders.reduce([]) do |certs, finder|
126
+ relying_party.attestation_root_certificates_finders.reduce([]) do |certs, finder|
126
127
  if certs.empty?
127
128
  finder.find(
128
129
  attestation_format: format,
@@ -169,14 +170,10 @@ module WebAuthn
169
170
  def cose_algorithm
170
171
  @cose_algorithm ||=
171
172
  COSE::Algorithm.find(algorithm).tap do |alg|
172
- alg && configuration.algorithms.include?(alg.name) ||
173
+ alg && relying_party.algorithms.include?(alg.name) ||
173
174
  raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
174
175
  end
175
176
  end
176
-
177
- def configuration
178
- WebAuthn.configuration
179
- end
180
177
  end
181
178
  end
182
179
  end
@@ -6,18 +6,12 @@ module WebAuthn
6
6
  module AttestationStatement
7
7
  class None < Base
8
8
  def valid?(*_args)
9
- if statement == {} && trustworthy?
9
+ if statement == {}
10
10
  [WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE, nil]
11
11
  else
12
12
  false
13
13
  end
14
14
  end
15
-
16
- private
17
-
18
- def attestation_type
19
- WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE
20
- end
21
15
  end
22
16
  end
23
17
  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?
@@ -24,7 +24,7 @@ module WebAuthn
24
24
 
25
25
  # TODO: use keyword_init when we dropped Ruby 2.4 support
26
26
  Credential =
27
- Struct.new(:id, :public_key) do
27
+ Struct.new(:id, :public_key, :algorithm) do
28
28
  def public_key_object
29
29
  COSE::Key.deserialize(public_key).to_pkey
30
30
  end
@@ -47,7 +47,7 @@ module WebAuthn
47
47
  def credential
48
48
  @credential ||=
49
49
  if valid?
50
- Credential.new(id, public_key)
50
+ Credential.new(id, public_key, algorithm)
51
51
  end
52
52
  end
53
53
 
@@ -59,10 +59,16 @@ module WebAuthn
59
59
 
60
60
  private
61
61
 
62
+ def algorithm
63
+ COSE::Algorithm.find(cose_key.alg).name
64
+ end
65
+
62
66
  def valid_credential_public_key?
63
- cose_key = COSE::Key.deserialize(public_key)
67
+ !!cose_key.alg
68
+ end
64
69
 
65
- !!cose_key.alg && WebAuthn.configuration.algorithms.include?(COSE::Algorithm.find(cose_key.alg).name)
70
+ def cose_key
71
+ @cose_key ||= COSE::Key.deserialize(public_key)
66
72
  end
67
73
 
68
74
  def public_key
@@ -20,13 +20,14 @@ module WebAuthn
20
20
  class UserVerifiedVerificationError < VerificationError; end
21
21
 
22
22
  class AuthenticatorResponse
23
- def initialize(client_data_json:)
23
+ def initialize(client_data_json:, relying_party: WebAuthn.configuration.relying_party)
24
24
  @client_data_json = client_data_json
25
+ @relying_party = relying_party
25
26
  end
26
27
 
27
28
  def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
28
- expected_origin ||= WebAuthn.configuration.origin || raise("Unspecified expected origin")
29
- rp_id ||= WebAuthn.configuration.rp_id
29
+ expected_origin ||= relying_party.origin || raise("Unspecified expected origin")
30
+ rp_id ||= relying_party.id
30
31
 
31
32
  verify_item(:type)
32
33
  verify_item(:token_binding)
@@ -35,7 +36,7 @@ module WebAuthn
35
36
  verify_item(:authenticator_data)
36
37
  verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
37
38
 
38
- if !WebAuthn.configuration.silent_authentication
39
+ if !relying_party.silent_authentication
39
40
  verify_item(:user_presence)
40
41
  end
41
42
 
@@ -58,7 +59,7 @@ module WebAuthn
58
59
 
59
60
  private
60
61
 
61
- attr_reader :client_data_json
62
+ attr_reader :client_data_json, :relying_party
62
63
 
63
64
  def verify_item(item, *args)
64
65
  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,54 +12,49 @@ module WebAuthn
13
12
  yield(configuration)
14
13
  end
15
14
 
16
- class RootCertificateFinderNotSupportedError < Error; end
17
-
18
15
  class Configuration
19
- def self.if_pss_supported(algorithm)
20
- OpenSSL::PKey::RSA.instance_methods.include?(:verify_pss) ? algorithm : nil
21
- end
22
-
23
- DEFAULT_ALGORITHMS = ["ES256", if_pss_supported("PS256"), "RS256"].compact.freeze
24
-
25
- attr_accessor :algorithms
26
- attr_accessor :encoding
27
- attr_accessor :origin
28
- attr_accessor :rp_id
29
- attr_accessor :rp_name
30
- attr_accessor :verify_attestation_statement
31
- attr_accessor :credential_options_timeout
32
- attr_accessor :silent_authentication
33
- attr_accessor :acceptable_attestation_types
34
- 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
35
39
 
36
40
  def initialize
37
- @algorithms = DEFAULT_ALGORITHMS.dup
38
- @encoding = WebAuthn::Encoder::STANDARD_ENCODING
39
- @verify_attestation_statement = true
40
- @credential_options_timeout = 120000
41
- @silent_authentication = false
42
- @acceptable_attestation_types = ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA']
43
- @attestation_root_certificates_finders = []
41
+ @relying_party = RelyingParty.new
44
42
  end
45
43
 
46
- # This is the user-data encoder.
47
- # Used to decode user input and to encode data provided to the user.
48
- def encoder
49
- @encoder ||= WebAuthn::Encoder.new(encoding)
44
+ def rp_name
45
+ relying_party.name
50
46
  end
51
47
 
52
- def attestation_root_certificates_finders=(finders)
53
- if !finders.respond_to?(:each)
54
- finders = [finders]
55
- end
48
+ def rp_name=(name)
49
+ relying_party.name = name
50
+ end
56
51
 
57
- finders.each do |finder|
58
- unless finder.respond_to?(:find)
59
- raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
60
- end
61
- end
52
+ def rp_id
53
+ relying_party.id
54
+ end
62
55
 
63
- @attestation_root_certificates_finders = finders
56
+ def rp_id=(id)
57
+ relying_party.id = id
64
58
  end
65
59
  end
66
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
@@ -32,8 +32,6 @@ module WebAuthn
32
32
  user_display_name: nil,
33
33
  rp_name: nil
34
34
  )
35
- super()
36
-
37
35
  @attestation = attestation
38
36
  @authenticator_selection = authenticator_selection
39
37
  @exclude_credentials = exclude_credentials
@@ -16,8 +16,6 @@ module WebAuthn
16
16
  attr_accessor :allow_credentials, :extensions, :user_verification
17
17
 
18
18
  def initialize(allow_credentials: [], extensions: nil, user_verification: nil)
19
- super()
20
-
21
19
  @allow_credentials = allow_credentials
22
20
  @extensions = extensions
23
21
  @user_verification = user_verification
@@ -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,
@@ -73,8 +73,7 @@ module WebAuthn
73
73
  user_present: true,
74
74
  user_verified: false,
75
75
  sign_count: nil,
76
- extensions: nil,
77
- user_handle: nil)
76
+ extensions: nil)
78
77
  rp_id ||= URI.parse(origin).host
79
78
 
80
79
  client_data_json = data_json_for(:get, encoder.decode(challenge))
@@ -98,14 +97,14 @@ module WebAuthn
98
97
  "clientDataJSON" => encoder.encode(client_data_json),
99
98
  "authenticatorData" => encoder.encode(assertion[:authenticator_data]),
100
99
  "signature" => encoder.encode(assertion[:signature]),
101
- "userHandle" => user_handle ? encoder.encode(user_handle) : nil
100
+ "userHandle" => nil
102
101
  }
103
102
  }
104
103
  end
105
104
 
106
105
  private
107
106
 
108
- attr_reader :authenticator, :encoding
107
+ attr_reader :authenticator
109
108
 
110
109
  def data_json_for(method, challenge)
111
110
  data = {
@@ -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
@@ -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
@@ -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", if_pss_supported("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'],
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebAuthn
4
- VERSION = "2.4.1"
4
+ VERSION = "3.0.0.alpha1"
5
5
  end
data/webauthn.gemspec CHANGED
@@ -37,17 +37,17 @@ Gem::Specification.new do |spec|
37
37
  spec.add_dependency "awrence", "~> 1.1"
38
38
  spec.add_dependency "bindata", "~> 2.4"
39
39
  spec.add_dependency "cbor", "~> 0.5.9"
40
- spec.add_dependency "cose", "~> 1.1"
40
+ spec.add_dependency "cose", "~> 1.0"
41
41
  spec.add_dependency "openssl", "~> 2.0"
42
42
  spec.add_dependency "safety_net_attestation", "~> 0.4.0"
43
43
  spec.add_dependency "securecompare", "~> 1.0"
44
- spec.add_dependency "tpm-key_attestation", "~> 0.10.0"
44
+ spec.add_dependency "tpm-key_attestation", "~> 0.9.0"
45
45
 
46
46
  spec.add_development_dependency "appraisal", "~> 2.3.0"
47
47
  spec.add_development_dependency "bundler", ">= 1.17", "< 3.0"
48
48
  spec.add_development_dependency "byebug", "~> 11.0"
49
49
  spec.add_development_dependency "rake", "~> 13.0"
50
50
  spec.add_development_dependency "rspec", "~> 3.8"
51
- spec.add_development_dependency "rubocop", "0.89"
51
+ spec.add_development_dependency "rubocop", "0.80.1"
52
52
  spec.add_development_dependency "rubocop-rspec", "~> 1.38.1"
53
53
  end
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.4.1
4
+ version: 3.0.0.alpha1
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: 2021-02-15 00:00:00.000000000 Z
12
+ date: 2020-06-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: android_key_attestation
@@ -73,14 +73,14 @@ dependencies:
73
73
  requirements:
74
74
  - - "~>"
75
75
  - !ruby/object:Gem::Version
76
- version: '1.1'
76
+ version: '1.0'
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
- version: '1.1'
83
+ version: '1.0'
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: openssl
86
86
  requirement: !ruby/object:Gem::Requirement
@@ -129,14 +129,14 @@ dependencies:
129
129
  requirements:
130
130
  - - "~>"
131
131
  - !ruby/object:Gem::Version
132
- version: 0.10.0
132
+ version: 0.9.0
133
133
  type: :runtime
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
137
  - - "~>"
138
138
  - !ruby/object:Gem::Version
139
- version: 0.10.0
139
+ version: 0.9.0
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: appraisal
142
142
  requirement: !ruby/object:Gem::Requirement
@@ -219,14 +219,14 @@ dependencies:
219
219
  requirements:
220
220
  - - '='
221
221
  - !ruby/object:Gem::Version
222
- version: '0.89'
222
+ version: 0.80.1
223
223
  type: :development
224
224
  prerelease: false
225
225
  version_requirements: !ruby/object:Gem::Requirement
226
226
  requirements:
227
227
  - - '='
228
228
  - !ruby/object:Gem::Version
229
- version: '0.89'
229
+ version: 0.80.1
230
230
  - !ruby/object:Gem::Dependency
231
231
  name: rubocop-rspec
232
232
  requirement: !ruby/object:Gem::Requirement
@@ -313,6 +313,7 @@ files:
313
313
  - lib/webauthn/public_key_credential/user_entity.rb
314
314
  - lib/webauthn/public_key_credential_with_assertion.rb
315
315
  - lib/webauthn/public_key_credential_with_attestation.rb
316
+ - lib/webauthn/relying_party.rb
316
317
  - lib/webauthn/security_utils.rb
317
318
  - lib/webauthn/u2f_migrator.rb
318
319
  - lib/webauthn/version.rb
@@ -337,11 +338,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
337
338
  version: '2.4'
338
339
  required_rubygems_version: !ruby/object:Gem::Requirement
339
340
  requirements:
340
- - - ">="
341
+ - - ">"
341
342
  - !ruby/object:Gem::Version
342
- version: '0'
343
+ version: 1.3.1
343
344
  requirements: []
344
- rubygems_version: 3.2.8
345
+ rubygems_version: 3.1.4
345
346
  signing_key:
346
347
  specification_version: 4
347
348
  summary: WebAuthn ruby server library