webauthn 1.12.0 → 1.13.0
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/.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
|