webauthn 1.18.0 → 2.0.0.beta1

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/.travis.yml +7 -3
  4. data/Appraisals +8 -0
  5. data/CHANGELOG.md +52 -0
  6. data/README.md +88 -80
  7. data/SECURITY.md +18 -0
  8. data/gemfiles/cose_head.gemfile +7 -0
  9. data/gemfiles/openssl_head.gemfile +7 -0
  10. data/lib/webauthn.rb +9 -1
  11. data/lib/webauthn/attestation_statement/android_safetynet.rb +4 -4
  12. data/lib/webauthn/attestation_statement/base.rb +4 -4
  13. data/lib/webauthn/attestation_statement/fido_u2f.rb +1 -2
  14. data/lib/webauthn/authenticator_assertion_response.rb +33 -35
  15. data/lib/webauthn/authenticator_attestation_response.rb +30 -0
  16. data/lib/webauthn/authenticator_data.rb +3 -1
  17. data/lib/webauthn/authenticator_data/attested_credential_data.rb +1 -0
  18. data/lib/webauthn/authenticator_response.rb +1 -2
  19. data/lib/webauthn/client_data.rb +2 -1
  20. data/lib/webauthn/configuration.rb +9 -0
  21. data/lib/webauthn/credential.rb +26 -0
  22. data/lib/webauthn/credential_creation_options.rb +5 -1
  23. data/lib/webauthn/credential_request_options.rb +5 -0
  24. data/lib/webauthn/encoder.rb +8 -1
  25. data/lib/webauthn/fake_authenticator.rb +1 -0
  26. data/lib/webauthn/fake_client.rb +26 -22
  27. data/lib/webauthn/public_key_credential.rb +10 -50
  28. data/lib/webauthn/public_key_credential/creation_options.rb +92 -0
  29. data/lib/webauthn/public_key_credential/entity.rb +44 -0
  30. data/lib/webauthn/public_key_credential/options.rb +72 -0
  31. data/lib/webauthn/public_key_credential/request_options.rb +36 -0
  32. data/lib/webauthn/public_key_credential/rp_entity.rb +23 -0
  33. data/lib/webauthn/public_key_credential/user_entity.rb +24 -0
  34. data/lib/webauthn/public_key_credential_with_assertion.rb +35 -0
  35. data/lib/webauthn/public_key_credential_with_attestation.rb +30 -0
  36. data/lib/webauthn/u2f_migrator.rb +1 -1
  37. data/lib/webauthn/version.rb +1 -1
  38. data/webauthn.gemspec +3 -2
  39. metadata +33 -8
  40. data/webauthn-ruby.png +0 -0
@@ -1,50 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "base64"
4
- require "webauthn/authenticator_assertion_response"
5
- require "webauthn/authenticator_attestation_response"
6
3
  require "webauthn/encoder"
7
4
 
8
5
  module WebAuthn
9
6
  class PublicKeyCredential
10
- VALID_TYPE = "public-key"
11
-
12
7
  attr_reader :type, :id, :raw_id, :response
13
8
 
14
- def self.from_create(credential, encoding: :base64)
15
- encoder = WebAuthn::Encoder.new(encoding)
16
-
17
- new(
18
- type: credential["type"],
19
- id: credential["id"],
20
- raw_id: encoder.decode(credential["rawId"]),
21
- response: WebAuthn::AuthenticatorAttestationResponse.new(
22
- attestation_object: encoder.decode(credential["response"]["attestationObject"]),
23
- client_data_json: encoder.decode(credential["response"]["clientDataJSON"])
24
- )
25
- )
26
- end
27
-
28
- def self.from_get(credential, encoding: :base64)
29
- encoder = WebAuthn::Encoder.new(encoding)
30
-
31
- user_handle =
32
- if credential["response"]["userHandle"]
33
- encoder.decode(credential["response"]["userHandle"])
34
- end
35
-
9
+ def self.from_client(credential)
36
10
  new(
37
11
  type: credential["type"],
38
12
  id: credential["id"],
39
- raw_id: encoder.decode(credential["rawId"]),
40
- response: WebAuthn::AuthenticatorAssertionResponse.new(
41
- # FIXME: credential_id doesn't belong inside AuthenticatorAssertionResponse
42
- credential_id: Base64.urlsafe_decode64(credential["id"]),
43
- authenticator_data: encoder.decode(credential["response"]["authenticatorData"]),
44
- client_data_json: encoder.decode(credential["response"]["clientDataJSON"]),
45
- signature: encoder.decode(credential["response"]["signature"]),
46
- user_handle: user_handle
47
- )
13
+ raw_id: WebAuthn.configuration.encoder.decode(credential["rawId"]),
14
+ response: response_class.from_client(credential["response"])
48
15
  )
49
16
  end
50
17
 
@@ -55,24 +22,13 @@ module WebAuthn
55
22
  @response = response
56
23
  end
57
24
 
58
- def verify(*args)
25
+ def verify(*_args)
59
26
  valid_type? || raise("invalid type")
60
27
  valid_id? || raise("invalid id")
61
- response.verify(*args)
62
28
 
63
29
  true
64
30
  end
65
31
 
66
- def public_key
67
- response&.authenticator_data&.credential&.public_key
68
- end
69
-
70
- def user_handle
71
- if response.is_a?(WebAuthn::AuthenticatorAssertionResponse)
72
- response.user_handle
73
- end
74
- end
75
-
76
32
  def sign_count
77
33
  response&.authenticator_data&.sign_count
78
34
  end
@@ -80,11 +36,15 @@ module WebAuthn
80
36
  private
81
37
 
82
38
  def valid_type?
83
- type == VALID_TYPE
39
+ type == TYPE_PUBLIC_KEY
84
40
  end
85
41
 
86
42
  def valid_id?
87
- raw_id && id && raw_id == Base64.urlsafe_decode64(id)
43
+ raw_id && id && raw_id == WebAuthn.standard_encoder.decode(id)
44
+ end
45
+
46
+ def encoder
47
+ WebAuthn.configuration.encoder
88
48
  end
89
49
  end
90
50
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cose/algorithm"
4
+ require "webauthn/public_key_credential/options"
5
+ require "webauthn/public_key_credential/rp_entity"
6
+ require "webauthn/public_key_credential/user_entity"
7
+
8
+ module WebAuthn
9
+ class PublicKeyCredential
10
+ class CreationOptions < Options
11
+ attr_accessor(
12
+ :attestation,
13
+ :authenticator_selection,
14
+ :exclude,
15
+ :algs,
16
+ :rp,
17
+ :user
18
+ )
19
+
20
+ def initialize(
21
+ attestation: nil,
22
+ authenticator_selection: nil,
23
+ exclude_credentials: nil,
24
+ exclude: nil,
25
+ pub_key_cred_params: nil,
26
+ algs: nil,
27
+ rp: {},
28
+ user:,
29
+ **keyword_arguments
30
+ )
31
+ super(**keyword_arguments)
32
+
33
+ @attestation = attestation
34
+ @authenticator_selection = authenticator_selection
35
+ @exclude_credentials = exclude_credentials
36
+ @exclude = exclude
37
+ @pub_key_cred_params = pub_key_cred_params
38
+ @algs = algs
39
+
40
+ @rp =
41
+ if rp.is_a?(Hash)
42
+ rp[:name] ||= configuration.rp_name
43
+ rp[:id] ||= configuration.rp_id
44
+
45
+ RPEntity.new(rp)
46
+ else
47
+ rp
48
+ end
49
+
50
+ @user =
51
+ if user.is_a?(Hash)
52
+ UserEntity.new(user)
53
+ else
54
+ user
55
+ end
56
+ end
57
+
58
+ def exclude_credentials
59
+ @exclude_credentials || exclude_credentials_from_exclude
60
+ end
61
+
62
+ def pub_key_cred_params
63
+ @pub_key_cred_params || pub_key_cred_params_from_algs
64
+ end
65
+
66
+ private
67
+
68
+ def attributes
69
+ super.concat([:rp, :user, :pub_key_cred_params, :attestation, :authenticator_selection, :exclude_credentials])
70
+ end
71
+
72
+ def exclude_credentials_from_exclude
73
+ if exclude
74
+ as_public_key_descriptors(exclude)
75
+ end
76
+ end
77
+
78
+ def pub_key_cred_params_from_algs
79
+ Array(algs || configuration.algorithms).map do |alg|
80
+ alg_id =
81
+ if alg.is_a?(String) || alg.is_a?(Symbol)
82
+ COSE::Algorithm.by_name(alg.to_s).id
83
+ else
84
+ alg
85
+ end
86
+
87
+ { type: TYPE_PUBLIC_KEY, alg: alg_id }
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "awrence"
4
+
5
+ module WebAuthn
6
+ class PublicKeyCredential
7
+ class Entity
8
+ attr_reader :name, :icon
9
+
10
+ def initialize(name:, icon: nil)
11
+ @name = name
12
+ @icon = icon
13
+ end
14
+
15
+ def as_json
16
+ to_hash.to_camelback_keys
17
+ end
18
+
19
+ private
20
+
21
+ def to_hash
22
+ hash = {}
23
+
24
+ attributes.each do |attribute_name|
25
+ value = send(attribute_name)
26
+
27
+ if value.respond_to?(:as_json)
28
+ value = value.as_json
29
+ end
30
+
31
+ if value
32
+ hash[attribute_name] = value
33
+ end
34
+ end
35
+
36
+ hash
37
+ end
38
+
39
+ def attributes
40
+ [:name, :icon]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "awrence"
4
+ require "securerandom"
5
+
6
+ module WebAuthn
7
+ class PublicKeyCredential
8
+ class Options
9
+ CHALLENGE_LENGTH = 32
10
+
11
+ attr_reader :timeout, :extensions
12
+
13
+ def initialize(timeout: default_timeout, extensions: nil)
14
+ @timeout = timeout
15
+ @extensions = extensions
16
+ end
17
+
18
+ def challenge
19
+ encoder.encode(raw_challenge)
20
+ end
21
+
22
+ # Argument wildcard for Ruby on Rails controller automatic object JSON serialization
23
+ def as_json(*)
24
+ to_hash.to_camelback_keys
25
+ end
26
+
27
+ private
28
+
29
+ def to_hash
30
+ hash = {}
31
+
32
+ attributes.each do |attribute_name|
33
+ value = send(attribute_name)
34
+
35
+ if value.respond_to?(:as_json)
36
+ value = value.as_json
37
+ end
38
+
39
+ if value
40
+ hash[attribute_name] = value
41
+ end
42
+ end
43
+
44
+ hash
45
+ end
46
+
47
+ def attributes
48
+ [:challenge, :timeout, :extensions]
49
+ end
50
+
51
+ def encoder
52
+ WebAuthn.configuration.encoder
53
+ end
54
+
55
+ def raw_challenge
56
+ @raw_challenge ||= SecureRandom.random_bytes(CHALLENGE_LENGTH)
57
+ end
58
+
59
+ def default_timeout
60
+ configuration.credential_options_timeout
61
+ end
62
+
63
+ def configuration
64
+ WebAuthn.configuration
65
+ end
66
+
67
+ def as_public_key_descriptors(ids)
68
+ Array(ids).map { |id| { type: TYPE_PUBLIC_KEY, id: id } }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webauthn/public_key_credential/options"
4
+
5
+ module WebAuthn
6
+ class PublicKeyCredential
7
+ class RequestOptions < Options
8
+ attr_accessor :rp_id, :allow, :user_verification
9
+
10
+ def initialize(rp_id: nil, allow_credentials: nil, allow: nil, user_verification: nil, **keyword_arguments)
11
+ super(**keyword_arguments)
12
+
13
+ @rp_id = rp_id || configuration.rp_id
14
+ @allow_credentials = allow_credentials
15
+ @allow = allow
16
+ @user_verification = user_verification
17
+ end
18
+
19
+ def allow_credentials
20
+ @allow_credentials || allow_credentials_from_allow || []
21
+ end
22
+
23
+ private
24
+
25
+ def attributes
26
+ super.concat([:allow_credentials, :rp_id, :user_verification])
27
+ end
28
+
29
+ def allow_credentials_from_allow
30
+ if allow
31
+ as_public_key_descriptors(allow)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webauthn/public_key_credential/entity"
4
+
5
+ module WebAuthn
6
+ class PublicKeyCredential
7
+ class RPEntity < Entity
8
+ attr_reader :id
9
+
10
+ def initialize(id: nil, **keyword_arguments)
11
+ super(**keyword_arguments)
12
+
13
+ @id = id
14
+ end
15
+
16
+ private
17
+
18
+ def attributes
19
+ super.concat([:id])
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webauthn/public_key_credential/entity"
4
+
5
+ module WebAuthn
6
+ class PublicKeyCredential
7
+ class UserEntity < Entity
8
+ attr_reader :id, :display_name
9
+
10
+ def initialize(id:, display_name: nil, **keyword_arguments)
11
+ super(**keyword_arguments)
12
+
13
+ @id = id
14
+ @display_name = display_name || name
15
+ end
16
+
17
+ private
18
+
19
+ def attributes
20
+ super.concat([:id, :display_name])
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webauthn/authenticator_assertion_response"
4
+ require "webauthn/public_key_credential"
5
+
6
+ module WebAuthn
7
+ class PublicKeyCredentialWithAssertion < PublicKeyCredential
8
+ def self.response_class
9
+ WebAuthn::AuthenticatorAssertionResponse
10
+ end
11
+
12
+ def verify(challenge, public_key:, sign_count:, user_verification: nil)
13
+ super
14
+
15
+ response.verify(
16
+ encoder.decode(challenge),
17
+ public_key: encoder.decode(public_key),
18
+ sign_count: sign_count,
19
+ user_verification: user_verification
20
+ )
21
+
22
+ true
23
+ end
24
+
25
+ def user_handle
26
+ if raw_user_handle
27
+ encoder.encode(raw_user_handle)
28
+ end
29
+ end
30
+
31
+ def raw_user_handle
32
+ response.user_handle
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webauthn/authenticator_attestation_response"
4
+ require "webauthn/public_key_credential"
5
+
6
+ module WebAuthn
7
+ class PublicKeyCredentialWithAttestation < PublicKeyCredential
8
+ def self.response_class
9
+ WebAuthn::AuthenticatorAttestationResponse
10
+ end
11
+
12
+ def verify(challenge, user_verification: nil)
13
+ super
14
+
15
+ response.verify(encoder.decode(challenge), user_verification: user_verification)
16
+
17
+ true
18
+ end
19
+
20
+ def public_key
21
+ if raw_public_key
22
+ encoder.encode(raw_public_key)
23
+ end
24
+ end
25
+
26
+ def raw_public_key
27
+ response&.authenticator_data&.credential&.public_key
28
+ end
29
+ end
30
+ end