webauthn 1.12.0 → 1.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rspec +1 -1
- data/CHANGELOG.md +15 -1
- data/README.md +12 -2
- data/lib/cose/algorithm.rb +27 -0
- data/lib/webauthn.rb +11 -8
- data/lib/webauthn/attestation_statement/android_key.rb +16 -5
- data/lib/webauthn/attestation_statement/fido_u2f.rb +9 -3
- data/lib/webauthn/attestation_statement/fido_u2f/public_key.rb +2 -2
- data/lib/webauthn/attestation_statement/none.rb +5 -1
- data/lib/webauthn/attestation_statement/packed.rb +40 -7
- data/lib/webauthn/authenticator_assertion_response.rb +22 -16
- data/lib/webauthn/authenticator_response.rb +6 -0
- data/lib/webauthn/client_data.rb +14 -0
- data/lib/webauthn/fake_authenticator.rb +3 -2
- data/lib/webauthn/fake_authenticator/authenticator_data.rb +4 -2
- data/lib/webauthn/fake_client.rb +11 -4
- data/lib/webauthn/version.rb +1 -1
- data/webauthn.gemspec +1 -1
- metadata +5 -5
- data/lib/cose/ecdsa.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9b0ee13d67d762afc44c92d76d99a7a7f3500c31658a0248a6dfb4d986f3f18
|
4
|
+
data.tar.gz: 888a0fb232facc8e5d02458bc1c94f926ed4b8541cac4b3937cf7bb4098ed0f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8f7821dbbc3ce730216ade21b37a23d6c202e1e060b90ed1bfb419c2abeed1ff6867d4ba6730de07d7b0f0eef54306358c894cac95faf90cb09af397f60f49e
|
7
|
+
data.tar.gz: ec06219c2f23352fbeec12c6bdf550b5120403c2dbbc249eb577e1eeb90f16ee1425b8ca857cdcdef884d7e2cd385e1d4e5dfb3cc9173aabc511088cf0627760
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
--
|
1
|
+
--order rand
|
2
2
|
--color
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.13.0] - 2019-04-09
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Verify 'none' attestation statement is really empty.
|
8
|
+
- Verify 'packed' attestation statement certificates start/end dates.
|
9
|
+
- Verify 'packed' attestation statement signature algorithm.
|
10
|
+
- Verify 'fiod-u2f attestation statement AAGUID is zeroed out. Thank you @bdewater.
|
11
|
+
- Verify 'android-key' attestation statement signature algorithm.
|
12
|
+
- Verify assertion response signature algorithm.
|
13
|
+
- Verify collectedClientData.tokenBinding format.
|
14
|
+
- `WebAuthn.credential_creation_options` now accept `rp_name`, `user_id`, `user_name` and `display_name` as keyword arguments. Thank you @bdewater.
|
15
|
+
|
3
16
|
## [v1.12.0] - 2019-04-03
|
4
17
|
|
5
18
|
### Added
|
@@ -153,7 +166,8 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
153
166
|
- `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
|
154
167
|
- Works with ruby 2.5
|
155
168
|
|
156
|
-
[v1.
|
169
|
+
[v1.13.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.12.0...v1.13.0/
|
170
|
+
[v1.12.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.11.0...v1.12.0/
|
157
171
|
[v1.11.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.10.0...v1.11.0/
|
158
172
|
[v1.10.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.9.0...v1.10.0/
|
159
173
|
[v1.9.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.8.0...v1.9.0/
|
data/README.md
CHANGED
@@ -95,8 +95,13 @@ attestation_response = WebAuthn::AuthenticatorAttestationResponse.new(
|
|
95
95
|
# the User Agent as part of the verification phase.
|
96
96
|
original_origin = "https://www.example.com"
|
97
97
|
|
98
|
+
# In the case that a Relying Party ID (https://www.w3.org/TR/webauthn/#relying-party-identifier) different from `original_origin` was used on
|
99
|
+
# `navigator.credentials.create`, it needs to specified for verification.
|
100
|
+
# Otherwise, you can ignore passing in this value to the `verify` method below.
|
101
|
+
rp_id = "example.com"
|
102
|
+
|
98
103
|
begin
|
99
|
-
attestation_response.verify(original_challenge, original_origin)
|
104
|
+
attestation_response.verify(original_challenge, original_origin, rp_id: rp_id)
|
100
105
|
|
101
106
|
# 1. Register the new user and
|
102
107
|
# 2. Keep Credential ID and Credential Public Key under storage
|
@@ -157,6 +162,11 @@ assertion_response = WebAuthn::AuthenticatorAssertionResponse.new(
|
|
157
162
|
# the User Agent as part of the verification phase.
|
158
163
|
original_origin = "https://www.example.com"
|
159
164
|
|
165
|
+
# In the case that a Relying Party ID (https://www.w3.org/TR/webauthn/#relying-party-identifier) different from `original_origin` was used on
|
166
|
+
# `navigator.credentials.get`, it needs to be specified for verification.
|
167
|
+
# Otherwise, you can ignore passing in this value to the `verify` method below.`
|
168
|
+
rp_id = "example.com"
|
169
|
+
|
160
170
|
# This hash must have the id and its corresponding public key of the
|
161
171
|
# previously stored credential for the user that is attempting to sign in.
|
162
172
|
allowed_credential = {
|
@@ -165,7 +175,7 @@ allowed_credential = {
|
|
165
175
|
}
|
166
176
|
|
167
177
|
begin
|
168
|
-
assertion_response.verify(original_challenge, original_origin, allowed_credentials: [allowed_credential])
|
178
|
+
assertion_response.verify(original_challenge, original_origin, allowed_credentials: [allowed_credential], rp_id: rp_id)
|
169
179
|
|
170
180
|
# Sign in the user
|
171
181
|
rescue WebAuthn::VerificationError => e
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TODO: Move this to cose gem
|
4
|
+
module COSE
|
5
|
+
# https://tools.ietf.org/html/rfc8152#section-8.1
|
6
|
+
Algorithm = Struct.new(:id, :name, :hash, :key_curve) do
|
7
|
+
@registered = {}
|
8
|
+
|
9
|
+
def self.register(id, name, hash, key_curve)
|
10
|
+
@registered[id] = COSE::Algorithm.new(id, name, hash, key_curve)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.find(id)
|
14
|
+
@registered[id]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.by_name(name)
|
18
|
+
@registered.values.detect { |algorithm| algorithm.name == name }
|
19
|
+
end
|
20
|
+
|
21
|
+
def value
|
22
|
+
id
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
COSE::Algorithm.register(-7, "ES256", "SHA256", "prime256v1")
|
data/lib/webauthn.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "cose/
|
3
|
+
require "cose/algorithm"
|
4
4
|
require "webauthn/authenticator_attestation_response"
|
5
5
|
require "webauthn/authenticator_assertion_response"
|
6
6
|
require "webauthn/security_utils"
|
@@ -11,18 +11,21 @@ require "securerandom"
|
|
11
11
|
require "json"
|
12
12
|
|
13
13
|
module WebAuthn
|
14
|
-
CRED_PARAM_ES256 = { type: "public-key", alg: COSE::
|
15
|
-
RP_NAME = "web-server"
|
16
|
-
USER_ID = "1"
|
17
|
-
USER_NAME = "web-user"
|
14
|
+
CRED_PARAM_ES256 = { type: "public-key", alg: COSE::Algorithm.by_name("ES256").id }.freeze
|
18
15
|
TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
|
19
16
|
|
20
|
-
|
17
|
+
# TODO: make keyword arguments mandatory in next major version
|
18
|
+
def self.credential_creation_options(
|
19
|
+
rp_name: "web-server",
|
20
|
+
user_name: "web-user",
|
21
|
+
display_name: "web-user",
|
22
|
+
user_id: "1"
|
23
|
+
)
|
21
24
|
{
|
22
25
|
challenge: challenge,
|
23
26
|
pubKeyCredParams: [CRED_PARAM_ES256],
|
24
|
-
rp: { name:
|
25
|
-
user: { name:
|
27
|
+
rp: { name: rp_name },
|
28
|
+
user: { name: user_name, displayName: display_name, id: user_id }
|
26
29
|
}
|
27
30
|
end
|
28
31
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "cose/algorithm"
|
3
4
|
require "openssl"
|
4
5
|
require "webauthn/attestation_statement/android_key/key_description"
|
5
6
|
require "webauthn/attestation_statement/base"
|
@@ -26,11 +27,17 @@ module WebAuthn
|
|
26
27
|
private
|
27
28
|
|
28
29
|
def valid_signature?(authenticator_data, client_data_hash)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
cose_algorithm = COSE::Algorithm.find(algorithm)
|
31
|
+
|
32
|
+
if cose_algorithm
|
33
|
+
attestation_certificate.public_key.verify(
|
34
|
+
cose_algorithm.hash,
|
35
|
+
signature,
|
36
|
+
authenticator_data.data + client_data_hash
|
37
|
+
)
|
38
|
+
else
|
39
|
+
raise "Unsupported algorithm #{algorithm}"
|
40
|
+
end
|
34
41
|
end
|
35
42
|
|
36
43
|
def matching_public_key?(authenticator_data)
|
@@ -87,6 +94,10 @@ module WebAuthn
|
|
87
94
|
def signature
|
88
95
|
statement["sig"]
|
89
96
|
end
|
97
|
+
|
98
|
+
def algorithm
|
99
|
+
statement["alg"]
|
100
|
+
end
|
90
101
|
end
|
91
102
|
end
|
92
103
|
end
|
@@ -8,12 +8,14 @@ module WebAuthn
|
|
8
8
|
module AttestationStatement
|
9
9
|
class FidoU2f < Base
|
10
10
|
VALID_ATTESTATION_CERTIFICATE_COUNT = 1
|
11
|
-
|
11
|
+
VALID_ATTESTATION_CERTIFICATE_ALGORITHM = COSE::Algorithm.by_name("ES256")
|
12
|
+
VALID_ATTESTED_AAGUID = 0.chr * WebAuthn::AuthenticatorData::AttestedCredentialData::AAGUID_LENGTH
|
12
13
|
|
13
14
|
def valid?(authenticator_data, client_data_hash)
|
14
15
|
valid_format? &&
|
15
16
|
valid_certificate_public_key? &&
|
16
17
|
valid_credential_public_key?(authenticator_data.credential.public_key) &&
|
18
|
+
valid_aaguid?(authenticator_data.attested_credential_data.aaguid) &&
|
17
19
|
valid_signature?(authenticator_data, client_data_hash) &&
|
18
20
|
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC_OR_ATTCA, [attestation_certificate]]
|
19
21
|
end
|
@@ -31,7 +33,7 @@ module WebAuthn
|
|
31
33
|
|
32
34
|
def valid_certificate_public_key?
|
33
35
|
certificate_public_key.is_a?(OpenSSL::PKey::EC) &&
|
34
|
-
certificate_public_key.group.curve_name ==
|
36
|
+
certificate_public_key.group.curve_name == VALID_ATTESTATION_CERTIFICATE_ALGORITHM.key_curve &&
|
35
37
|
certificate_public_key.check_key
|
36
38
|
end
|
37
39
|
|
@@ -51,9 +53,13 @@ module WebAuthn
|
|
51
53
|
statement["x5c"]
|
52
54
|
end
|
53
55
|
|
56
|
+
def valid_aaguid?(attested_credential_data_aaguid)
|
57
|
+
attested_credential_data_aaguid == VALID_ATTESTED_AAGUID
|
58
|
+
end
|
59
|
+
|
54
60
|
def valid_signature?(authenticator_data, client_data_hash)
|
55
61
|
certificate_public_key.verify(
|
56
|
-
|
62
|
+
VALID_ATTESTATION_CERTIFICATE_ALGORITHM.hash,
|
57
63
|
signature,
|
58
64
|
verification_data(authenticator_data, client_data_hash)
|
59
65
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "cose/
|
3
|
+
require "cose/algorithm"
|
4
4
|
require "cose/key/ec2"
|
5
5
|
require "webauthn/attestation_statement/base"
|
6
6
|
|
@@ -25,7 +25,7 @@ module WebAuthn
|
|
25
25
|
data.size >= COORDINATE_LENGTH * 2 &&
|
26
26
|
cose_key.x.length == COORDINATE_LENGTH &&
|
27
27
|
cose_key.y.length == COORDINATE_LENGTH &&
|
28
|
-
cose_key.alg == COSE::
|
28
|
+
cose_key.alg == COSE::Algorithm.by_name("ES256").id
|
29
29
|
end
|
30
30
|
|
31
31
|
def to_uncompressed_point
|
@@ -6,7 +6,11 @@ module WebAuthn
|
|
6
6
|
module AttestationStatement
|
7
7
|
class None < Base
|
8
8
|
def valid?(*_args)
|
9
|
-
|
9
|
+
if statement == {}
|
10
|
+
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE, nil]
|
11
|
+
else
|
12
|
+
false
|
13
|
+
end
|
10
14
|
end
|
11
15
|
end
|
12
16
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "cose/algorithm"
|
3
4
|
require "openssl"
|
4
5
|
require "webauthn/attestation_statement/base"
|
5
6
|
|
@@ -13,7 +14,9 @@ module WebAuthn
|
|
13
14
|
check_unsupported_feature
|
14
15
|
|
15
16
|
valid_format? &&
|
16
|
-
|
17
|
+
valid_algorithm?(authenticator_data.credential) &&
|
18
|
+
valid_certificate_chain? &&
|
19
|
+
valid_public_keys?(authenticator_data.credential) &&
|
17
20
|
meet_certificate_requirement? &&
|
18
21
|
matching_aaguid?(authenticator_data.attested_credential_data.aaguid) &&
|
19
22
|
valid_signature?(authenticator_data, client_data_hash) &&
|
@@ -22,6 +25,14 @@ module WebAuthn
|
|
22
25
|
|
23
26
|
private
|
24
27
|
|
28
|
+
def valid_algorithm?(credential)
|
29
|
+
!self_attestation? || algorithm == COSE::Key.deserialize(credential.public_key).alg
|
30
|
+
end
|
31
|
+
|
32
|
+
def self_attestation?
|
33
|
+
!raw_attestation_certificates && !raw_ecdaa_key_id
|
34
|
+
end
|
35
|
+
|
25
36
|
def algorithm
|
26
37
|
statement["alg"]
|
27
38
|
end
|
@@ -60,7 +71,16 @@ module WebAuthn
|
|
60
71
|
attestation_certificate_chain&.first
|
61
72
|
end
|
62
73
|
|
63
|
-
def valid_certificate_chain?
|
74
|
+
def valid_certificate_chain?
|
75
|
+
if attestation_certificate_chain
|
76
|
+
attestation_certificate_chain[1..-1].all? { |c| certificate_in_use?(c) }
|
77
|
+
else
|
78
|
+
true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# TODO: Reevaluate this check
|
83
|
+
def valid_public_keys?(credential)
|
64
84
|
public_keys = attestation_certificate_chain&.map(&:public_key) || [credential.public_key_object]
|
65
85
|
public_keys.all? do |public_key|
|
66
86
|
public_key.is_a?(OpenSSL::PKey::EC) && public_key.check_key
|
@@ -73,6 +93,7 @@ module WebAuthn
|
|
73
93
|
subject = attestation_certificate.subject.to_a
|
74
94
|
|
75
95
|
attestation_certificate.version == 2 &&
|
96
|
+
certificate_in_use?(attestation_certificate) &&
|
76
97
|
subject.assoc('OU')&.at(1) == "Authenticator Attestation" &&
|
77
98
|
attestation_certificate.extensions.find { |ext| ext.oid == 'basicConstraints' }&.value == 'CA:FALSE'
|
78
99
|
else
|
@@ -92,12 +113,24 @@ module WebAuthn
|
|
92
113
|
end
|
93
114
|
end
|
94
115
|
|
116
|
+
def certificate_in_use?(certificate)
|
117
|
+
now = Time.now
|
118
|
+
|
119
|
+
certificate.not_before < now && now < certificate.not_after
|
120
|
+
end
|
121
|
+
|
95
122
|
def valid_signature?(authenticator_data, client_data_hash)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
123
|
+
cose_algorithm = COSE::Algorithm.find(algorithm)
|
124
|
+
|
125
|
+
if cose_algorithm
|
126
|
+
(attestation_certificate&.public_key || authenticator_data.credential.public_key_object).verify(
|
127
|
+
cose_algorithm.hash,
|
128
|
+
signature,
|
129
|
+
verification_data(authenticator_data, client_data_hash)
|
130
|
+
)
|
131
|
+
else
|
132
|
+
raise "Unsupported algorithm #{algorithm}"
|
133
|
+
end
|
101
134
|
end
|
102
135
|
|
103
136
|
def verification_data(authenticator_data, client_data_hash)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "cose/algorithm"
|
3
4
|
require "cose/key"
|
4
5
|
require "webauthn/attestation_statement/fido_u2f/public_key"
|
5
6
|
require "webauthn/authenticator_response"
|
@@ -21,7 +22,7 @@ module WebAuthn
|
|
21
22
|
super(original_challenge, original_origin, rp_id: rp_id)
|
22
23
|
|
23
24
|
verify_item(:credential, allowed_credentials)
|
24
|
-
verify_item(:signature,
|
25
|
+
verify_item(:signature, credential_cose_key(allowed_credentials))
|
25
26
|
|
26
27
|
true
|
27
28
|
end
|
@@ -34,12 +35,18 @@ module WebAuthn
|
|
34
35
|
|
35
36
|
attr_reader :credential_id, :authenticator_data_bytes, :signature
|
36
37
|
|
37
|
-
def valid_signature?(
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
def valid_signature?(credential_cose_key)
|
39
|
+
cose_algorithm = COSE::Algorithm.find(credential_cose_key.alg)
|
40
|
+
|
41
|
+
if cose_algorithm
|
42
|
+
credential_cose_key.to_pkey.verify(
|
43
|
+
cose_algorithm.hash,
|
44
|
+
signature,
|
45
|
+
authenticator_data_bytes + client_data.hash
|
46
|
+
)
|
47
|
+
else
|
48
|
+
raise "Unsupported algorithm #{credential_cose_key.alg}"
|
49
|
+
end
|
43
50
|
end
|
44
51
|
|
45
52
|
def valid_credential?(allowed_credentials)
|
@@ -48,7 +55,7 @@ module WebAuthn
|
|
48
55
|
allowed_credential_ids.include?(credential_id)
|
49
56
|
end
|
50
57
|
|
51
|
-
def
|
58
|
+
def credential_cose_key(allowed_credentials)
|
52
59
|
matched_credential = allowed_credentials.find do |credential|
|
53
60
|
credential[:id] == credential_id
|
54
61
|
end
|
@@ -64,15 +71,14 @@ module WebAuthn
|
|
64
71
|
# Given that the credential public key is expected to be stored long-term by the gem
|
65
72
|
# user and later be passed as one of the allowed_credentials arguments in the
|
66
73
|
# AuthenticatorAssertionResponse.verify call, we then need to support the two formats.
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
key
|
74
|
+
COSE::Key::EC2.new(
|
75
|
+
alg: COSE::Algorithm.by_name("ES256").id,
|
76
|
+
crv: 1,
|
77
|
+
x: matched_credential[:public_key][1..32],
|
78
|
+
y: matched_credential[:public_key][33..-1]
|
79
|
+
)
|
74
80
|
else
|
75
|
-
COSE::Key.deserialize(matched_credential[:public_key])
|
81
|
+
COSE::Key.deserialize(matched_credential[:public_key])
|
76
82
|
end
|
77
83
|
end
|
78
84
|
|
@@ -9,6 +9,7 @@ module WebAuthn
|
|
9
9
|
class ChallengeVerificationError < VerificationError; end
|
10
10
|
class OriginVerificationError < VerificationError; end
|
11
11
|
class RpIdVerificationError < VerificationError; end
|
12
|
+
class TokenBindingVerificationError < VerificationError; end
|
12
13
|
class TypeVerificationError < VerificationError; end
|
13
14
|
class UserPresenceVerificationError < VerificationError; end
|
14
15
|
|
@@ -19,6 +20,7 @@ module WebAuthn
|
|
19
20
|
|
20
21
|
def verify(original_challenge, original_origin, rp_id: nil)
|
21
22
|
verify_item(:type)
|
23
|
+
verify_item(:token_binding)
|
22
24
|
verify_item(:challenge, original_challenge)
|
23
25
|
verify_item(:origin, original_origin)
|
24
26
|
verify_item(:authenticator_data)
|
@@ -56,6 +58,10 @@ module WebAuthn
|
|
56
58
|
client_data.type == type
|
57
59
|
end
|
58
60
|
|
61
|
+
def valid_token_binding?
|
62
|
+
client_data.valid_token_binding_format?
|
63
|
+
end
|
64
|
+
|
59
65
|
def valid_challenge?(original_challenge)
|
60
66
|
WebAuthn::SecurityUtils.secure_compare(Base64.urlsafe_decode64(client_data.challenge), original_challenge)
|
61
67
|
end
|
data/lib/webauthn/client_data.rb
CHANGED
@@ -7,6 +7,8 @@ module WebAuthn
|
|
7
7
|
class ClientDataMissingError < Error; end
|
8
8
|
|
9
9
|
class ClientData
|
10
|
+
VALID_TOKEN_BINDING_STATUSES = ["present", "supported", "not-supported"].freeze
|
11
|
+
|
10
12
|
def initialize(client_data_json)
|
11
13
|
@client_data_json = client_data_json
|
12
14
|
end
|
@@ -23,6 +25,18 @@ module WebAuthn
|
|
23
25
|
data["origin"]
|
24
26
|
end
|
25
27
|
|
28
|
+
def token_binding
|
29
|
+
data["tokenBinding"]
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid_token_binding_format?
|
33
|
+
if token_binding
|
34
|
+
token_binding.is_a?(Hash) && VALID_TOKEN_BINDING_STATUSES.include?(token_binding["status"])
|
35
|
+
else
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
26
40
|
def hash
|
27
41
|
OpenSSL::Digest::SHA256.digest(client_data_json)
|
28
42
|
end
|
@@ -32,7 +32,7 @@ module WebAuthn
|
|
32
32
|
attestation_object
|
33
33
|
end
|
34
34
|
|
35
|
-
def get_assertion(rp_id:, client_data_hash:, user_present: true, user_verified: false)
|
35
|
+
def get_assertion(rp_id:, client_data_hash:, user_present: true, user_verified: false, aaguid: AAGUID)
|
36
36
|
credential_options = credentials[rp_id]
|
37
37
|
|
38
38
|
if credential_options
|
@@ -41,7 +41,8 @@ module WebAuthn
|
|
41
41
|
authenticator_data = AuthenticatorData.new(
|
42
42
|
rp_id_hash: hashed(rp_id),
|
43
43
|
user_present: user_present,
|
44
|
-
user_verified: user_verified
|
44
|
+
user_verified: user_verified,
|
45
|
+
aaguid: aaguid,
|
45
46
|
).serialize
|
46
47
|
|
47
48
|
signature = credential_key.sign("SHA256", authenticator_data + client_data_hash)
|
@@ -7,12 +7,14 @@ require "securerandom"
|
|
7
7
|
module WebAuthn
|
8
8
|
class FakeAuthenticator
|
9
9
|
class AuthenticatorData
|
10
|
-
def initialize(rp_id_hash:, credential: nil, sign_count: 0, user_present: true, user_verified: !user_present
|
10
|
+
def initialize(rp_id_hash:, credential: nil, sign_count: 0, user_present: true, user_verified: !user_present,
|
11
|
+
aaguid: WebAuthn::FakeAuthenticator::AAGUID)
|
11
12
|
@rp_id_hash = rp_id_hash
|
12
13
|
@credential = credential
|
13
14
|
@sign_count = sign_count
|
14
15
|
@user_present = user_present
|
15
16
|
@user_verified = user_verified
|
17
|
+
@aaguid = aaguid
|
16
18
|
end
|
17
19
|
|
18
20
|
def serialize
|
@@ -45,7 +47,7 @@ module WebAuthn
|
|
45
47
|
def attested_credential_data
|
46
48
|
@attested_credential_data ||=
|
47
49
|
if credential
|
48
|
-
|
50
|
+
@aaguid +
|
49
51
|
[credential[:id].length].pack("n*") +
|
50
52
|
credential[:id] +
|
51
53
|
cose_credential_public_key
|
data/lib/webauthn/fake_client.rb
CHANGED
@@ -8,10 +8,11 @@ module WebAuthn
|
|
8
8
|
class FakeClient
|
9
9
|
TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
|
10
10
|
|
11
|
-
attr_reader :origin
|
11
|
+
attr_reader :origin, :token_binding
|
12
12
|
|
13
|
-
def initialize(origin = fake_origin, authenticator: WebAuthn::FakeAuthenticator.new)
|
13
|
+
def initialize(origin = fake_origin, token_binding: nil, authenticator: WebAuthn::FakeAuthenticator.new)
|
14
14
|
@origin = origin
|
15
|
+
@token_binding = token_binding
|
15
16
|
@authenticator = authenticator
|
16
17
|
end
|
17
18
|
|
@@ -67,11 +68,17 @@ module WebAuthn
|
|
67
68
|
attr_reader :authenticator
|
68
69
|
|
69
70
|
def data_json_for(method, challenge)
|
70
|
-
{
|
71
|
+
data = {
|
71
72
|
type: type_for(method),
|
72
73
|
challenge: encode(challenge),
|
73
74
|
origin: origin
|
74
|
-
}
|
75
|
+
}
|
76
|
+
|
77
|
+
if token_binding
|
78
|
+
data[:tokenBinding] = token_binding
|
79
|
+
end
|
80
|
+
|
81
|
+
data.to_json
|
75
82
|
end
|
76
83
|
|
77
84
|
def encode(data)
|
data/lib/webauthn/version.rb
CHANGED
data/webauthn.gemspec
CHANGED
@@ -41,5 +41,5 @@ Gem::Specification.new do |spec|
|
|
41
41
|
spec.add_development_dependency "byebug", "~> 11.0"
|
42
42
|
spec.add_development_dependency "rake", "~> 12.3"
|
43
43
|
spec.add_development_dependency "rspec", "~> 3.8"
|
44
|
-
spec.add_development_dependency "rubocop", "0.
|
44
|
+
spec.add_development_dependency "rubocop", "0.67.2"
|
45
45
|
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: 1.
|
4
|
+
version: 1.13.0
|
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: 2019-04-
|
12
|
+
date: 2019-04-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cbor
|
@@ -169,14 +169,14 @@ dependencies:
|
|
169
169
|
requirements:
|
170
170
|
- - '='
|
171
171
|
- !ruby/object:Gem::Version
|
172
|
-
version: 0.
|
172
|
+
version: 0.67.2
|
173
173
|
type: :development
|
174
174
|
prerelease: false
|
175
175
|
version_requirements: !ruby/object:Gem::Requirement
|
176
176
|
requirements:
|
177
177
|
- - '='
|
178
178
|
- !ruby/object:Gem::Version
|
179
|
-
version: 0.
|
179
|
+
version: 0.67.2
|
180
180
|
description: Make your Ruby/Rails web server become a conformant WebAuthn Relying
|
181
181
|
Party
|
182
182
|
email:
|
@@ -200,7 +200,7 @@ files:
|
|
200
200
|
- bin/setup
|
201
201
|
- gemfiles/openssl_2_0.gemfile
|
202
202
|
- gemfiles/openssl_2_1.gemfile
|
203
|
-
- lib/cose/
|
203
|
+
- lib/cose/algorithm.rb
|
204
204
|
- lib/webauthn.rb
|
205
205
|
- lib/webauthn/attestation_statement.rb
|
206
206
|
- lib/webauthn/attestation_statement/android_key.rb
|