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.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/.travis.yml +7 -3
- data/Appraisals +8 -0
- data/CHANGELOG.md +52 -0
- data/README.md +88 -80
- data/SECURITY.md +18 -0
- data/gemfiles/cose_head.gemfile +7 -0
- data/gemfiles/openssl_head.gemfile +7 -0
- data/lib/webauthn.rb +9 -1
- data/lib/webauthn/attestation_statement/android_safetynet.rb +4 -4
- data/lib/webauthn/attestation_statement/base.rb +4 -4
- data/lib/webauthn/attestation_statement/fido_u2f.rb +1 -2
- data/lib/webauthn/authenticator_assertion_response.rb +33 -35
- data/lib/webauthn/authenticator_attestation_response.rb +30 -0
- data/lib/webauthn/authenticator_data.rb +3 -1
- data/lib/webauthn/authenticator_data/attested_credential_data.rb +1 -0
- data/lib/webauthn/authenticator_response.rb +1 -2
- data/lib/webauthn/client_data.rb +2 -1
- data/lib/webauthn/configuration.rb +9 -0
- data/lib/webauthn/credential.rb +26 -0
- data/lib/webauthn/credential_creation_options.rb +5 -1
- data/lib/webauthn/credential_request_options.rb +5 -0
- data/lib/webauthn/encoder.rb +8 -1
- data/lib/webauthn/fake_authenticator.rb +1 -0
- data/lib/webauthn/fake_client.rb +26 -22
- data/lib/webauthn/public_key_credential.rb +10 -50
- data/lib/webauthn/public_key_credential/creation_options.rb +92 -0
- data/lib/webauthn/public_key_credential/entity.rb +44 -0
- data/lib/webauthn/public_key_credential/options.rb +72 -0
- data/lib/webauthn/public_key_credential/request_options.rb +36 -0
- data/lib/webauthn/public_key_credential/rp_entity.rb +23 -0
- data/lib/webauthn/public_key_credential/user_entity.rb +24 -0
- data/lib/webauthn/public_key_credential_with_assertion.rb +35 -0
- data/lib/webauthn/public_key_credential_with_attestation.rb +30 -0
- data/lib/webauthn/u2f_migrator.rb +1 -1
- data/lib/webauthn/version.rb +1 -1
- data/webauthn.gemspec +3 -2
- metadata +33 -8
- 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.
|
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:
|
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(*
|
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 ==
|
39
|
+
type == TYPE_PUBLIC_KEY
|
84
40
|
end
|
85
41
|
|
86
42
|
def valid_id?
|
87
|
-
raw_id && id && raw_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
|