webauthn 2.4.1 → 3.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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