webauthn 1.8.0 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +8 -2
- data/CHANGELOG.md +7 -0
- data/README.md +27 -14
- data/lib/webauthn/authenticator_assertion_response.rb +10 -4
- data/lib/webauthn/authenticator_attestation_response.rb +9 -6
- data/lib/webauthn/authenticator_response.rb +44 -7
- data/lib/webauthn/security_utils.rb +4 -19
- data/lib/webauthn/version.rb +1 -1
- data/webauthn.gemspec +3 -2
- metadata +20 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 690e8753529b42c4acc5fa15f621c077ee9822c850aa19e8a322ea5d937dbb8d
|
4
|
+
data.tar.gz: c0aa00203643d32d5bcdc673eced0dbc4185b05ab262fef5aa9fcdbf073de5c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66470f20b0365194b77753a664bdd759a204f62cf60c1dcabbf7181c472696c665e8946cf2c1073aad190cbeb3b6b557120aed8386525911cf65e25f5fb82762
|
7
|
+
data.tar.gz: baea59ad89ba252e6da4ec1ddce5394d3ea484d186e7d48740bae1d136e973e4a34114b91c34d64ea65bb4bee4d2a6468300057a5877dfab269296b16ae1a4b0
|
data/.travis.yml
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
dist: xenial
|
2
|
-
sudo: false
|
3
2
|
language: ruby
|
4
3
|
cache: bundler
|
5
4
|
rvm:
|
6
5
|
- ruby-head
|
7
|
-
- 2.6.
|
6
|
+
- 2.6.1
|
8
7
|
- 2.5.3
|
9
8
|
- 2.4.5
|
10
9
|
- 2.3.8
|
@@ -14,4 +13,11 @@ matrix:
|
|
14
13
|
- rvm: ruby-head
|
15
14
|
|
16
15
|
before_install:
|
16
|
+
- wget http://archive.ubuntu.com/ubuntu/pool/universe/f/faketime/libfaketime_0.9.7-3_amd64.deb
|
17
|
+
- sudo dpkg -i libfaketime_0.9.7-3_amd64.deb
|
17
18
|
- gem install bundler -v "~> 2.0"
|
19
|
+
|
20
|
+
before_script:
|
21
|
+
- export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
|
22
|
+
- export DONT_FAKE_MONOTONIC=1
|
23
|
+
- export FAKETIME_NO_CACHE=1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.9.0] - 2019-02-22
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Added `#verify`, which can be used for getting a meaningful error raised in case of a verification error, as opposed to `#valid?` which returns `false`
|
8
|
+
|
3
9
|
## [v1.8.0] - 2019-01-17
|
4
10
|
|
5
11
|
### Added
|
@@ -115,6 +121,7 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
115
121
|
- `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
|
116
122
|
- Works with ruby 2.5
|
117
123
|
|
124
|
+
[v1.9.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.8.0...v1.9.0/
|
118
125
|
[v1.8.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.7.0...v1.8.0/
|
119
126
|
[v1.7.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.6.0...v1.7.0/
|
120
127
|
[v1.6.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.5.0...v1.6.0/
|
data/README.md
CHANGED
@@ -63,7 +63,7 @@ NOTE: You can find a working example on how to use this gem in a __Rails__ app i
|
|
63
63
|
credential_creation_options = WebAuthn.credential_creation_options
|
64
64
|
|
65
65
|
# Store the newly generated challenge somewhere so you can have it
|
66
|
-
# for the
|
66
|
+
# for the verification phase.
|
67
67
|
#
|
68
68
|
# You can read it from the resulting options:
|
69
69
|
credential_creation_options[:challenge]
|
@@ -72,7 +72,7 @@ credential_creation_options[:challenge]
|
|
72
72
|
# to call `navigator.credentials.create({ "publicKey": credentialCreationOptions })`
|
73
73
|
```
|
74
74
|
|
75
|
-
####
|
75
|
+
#### Verification phase
|
76
76
|
|
77
77
|
```ruby
|
78
78
|
# These should be ruby `String`s encoded as binary data, e.g. `Encoding:ASCII-8BIT`.
|
@@ -80,10 +80,10 @@ credential_creation_options[:challenge]
|
|
80
80
|
# If the user-agent is a web browser, you would use some encoding algorithm to send what
|
81
81
|
# `navigator.credentials.create` returned through the wire.
|
82
82
|
#
|
83
|
-
# Then you need to decode that data before passing it to the `#
|
83
|
+
# Then you need to decode that data before passing it to the `#verify` method.
|
84
84
|
#
|
85
85
|
# E.g. in https://github.com/cedarcode/webauthn-rails-demo-app we use `Base64.strict_decode64`
|
86
|
-
# on the user-agent encoded data before calling `#
|
86
|
+
# on the user-agent encoded data before calling `#verify`
|
87
87
|
attestation_object = "..."
|
88
88
|
client_data_json = "..."
|
89
89
|
|
@@ -93,17 +93,19 @@ attestation_response = WebAuthn::AuthenticatorAttestationResponse.new(
|
|
93
93
|
)
|
94
94
|
|
95
95
|
# This value needs to match `window.location.origin` evaluated by
|
96
|
-
# the User Agent as part of the
|
96
|
+
# the User Agent as part of the verification phase.
|
97
97
|
original_origin = "https://www.example.com"
|
98
98
|
|
99
|
-
|
99
|
+
begin
|
100
|
+
attestation_response.verify(original_challenge, original_origin)
|
101
|
+
|
100
102
|
# 1. Register the new user and
|
101
103
|
# 2. Keep Credential ID and Credential Public Key under storage
|
102
104
|
# for future authentications
|
103
105
|
# Access by invoking:
|
104
106
|
# `attestation_response.credential.id`
|
105
107
|
# `attestation_response.credential.public_key`
|
106
|
-
|
108
|
+
rescue WebAuthn::VerificationError => e
|
107
109
|
# Handle error
|
108
110
|
end
|
109
111
|
```
|
@@ -119,7 +121,7 @@ credential_request_options = WebAuthn.credential_request_options
|
|
119
121
|
credential_request_options[:allowCredentials] << { id: credential_id, type: "public-key" }
|
120
122
|
|
121
123
|
# Store the newly generated challenge somewhere so you can have it
|
122
|
-
# for the
|
124
|
+
# for the verification phase.
|
123
125
|
#
|
124
126
|
# You can read it from the resulting options:
|
125
127
|
credential_request_options[:challenge]
|
@@ -128,7 +130,7 @@ credential_request_options[:challenge]
|
|
128
130
|
# to call `navigator.credentials.get({ "publicKey": credentialRequestOptions })`
|
129
131
|
```
|
130
132
|
|
131
|
-
####
|
133
|
+
#### Verification phase
|
132
134
|
|
133
135
|
Assuming you have the previously stored Credential Public Key, now in variable `credential_public_key`
|
134
136
|
|
@@ -138,10 +140,10 @@ Assuming you have the previously stored Credential Public Key, now in variable `
|
|
138
140
|
# If the user-agent is a web browser, you would use some encoding algorithm to send what
|
139
141
|
# `navigator.credentials.get` returned through the wire.
|
140
142
|
#
|
141
|
-
# Then you need to decode that data before passing it to the `#
|
143
|
+
# Then you need to decode that data before passing it to the `#verify` method.
|
142
144
|
#
|
143
145
|
# E.g. in https://github.com/cedarcode/webauthn-rails-demo-app we use `Base64.strict_decode64`
|
144
|
-
# on the user-agent encoded data before calling `#
|
146
|
+
# on the user-agent encoded data before calling `#verify`
|
145
147
|
authenticator_data = "..."
|
146
148
|
client_data_json = "..."
|
147
149
|
signature = "..."
|
@@ -153,7 +155,7 @@ assertion_response = WebAuthn::AuthenticatorAssertionResponse.new(
|
|
153
155
|
)
|
154
156
|
|
155
157
|
# This value needs to match `window.location.origin` evaluated by
|
156
|
-
# the User Agent as part of the
|
158
|
+
# the User Agent as part of the verification phase.
|
157
159
|
original_origin = "https://www.example.com"
|
158
160
|
|
159
161
|
# This hash must have the id and its corresponding public key of the
|
@@ -163,17 +165,28 @@ allowed_credential = {
|
|
163
165
|
public_key: credential_public_key
|
164
166
|
}
|
165
167
|
|
166
|
-
|
168
|
+
begin
|
169
|
+
assertion_response.verify(original_challenge, original_origin, allowed_credentials: [allowed_credential])
|
170
|
+
|
167
171
|
# Sign in the user
|
168
|
-
|
172
|
+
rescue WebAuthn::VerificationError => e
|
169
173
|
# Handle error
|
170
174
|
end
|
171
175
|
```
|
172
176
|
|
177
|
+
## Testing Your Integration
|
178
|
+
|
179
|
+
The Webauthn spec requires for data that is signed and authenticated. As a result, it can be difficult to create valid test authenticator data when testing your integration. Webauthn-ruby exposes [WebAuthn::FakeAuthenticator](https://github.com/cedarcode/webauthn-ruby/blob/master/lib/webauthn/fake_authenticator.rb) for you to use in your tests. Example usage can be found in [webauthn-ruby/spec/webauthn/authenticator_assertion_response_spec.rb](https://github.com/cedarcode/webauthn-ruby/blob/master/spec/webauthn/authenticator_assertion_response_spec.rb).
|
180
|
+
|
173
181
|
## Development
|
174
182
|
|
175
183
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests and code-style checks. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
176
184
|
|
185
|
+
Some tests require stubbing time with [libfaketime](https://github.com/wolfcw/libfaketime) in order to pass, otherwise they're skipped. You can install this library with your package manager. Follow libfaketime's instructions for your OS to preload the library before running the tests, and use the `DONT_FAKE_MONOTONIC=1 FAKETIME_NO_CACHE=1` options. E.g. when installed via homebrew on macOS:
|
186
|
+
```shell
|
187
|
+
DYLD_INSERT_LIBRARIES=/usr/local/Cellar/libfaketime/2.9.7_1/lib/faketime/libfaketime.1.dylib DYLD_FORCE_FLAT_NAMESPACE=1 DONT_FAKE_MONOTONIC=1 FAKETIME_NO_CACHE=1 bundle exec rspec
|
188
|
+
```
|
189
|
+
|
177
190
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
178
191
|
|
179
192
|
### Commit message format
|
@@ -3,6 +3,9 @@
|
|
3
3
|
require "webauthn/authenticator_response"
|
4
4
|
|
5
5
|
module WebAuthn
|
6
|
+
class CredentialVerificationError < VerificationError; end
|
7
|
+
class SignatureVerificationError < VerificationError; end
|
8
|
+
|
6
9
|
class AuthenticatorAssertionResponse < AuthenticatorResponse
|
7
10
|
def initialize(credential_id:, authenticator_data:, signature:, **options)
|
8
11
|
super(options)
|
@@ -12,10 +15,13 @@ module WebAuthn
|
|
12
15
|
@signature = signature
|
13
16
|
end
|
14
17
|
|
15
|
-
def
|
16
|
-
super(original_challenge, original_origin, rp_id: rp_id)
|
17
|
-
|
18
|
-
|
18
|
+
def verify(original_challenge, original_origin, allowed_credentials:, rp_id: nil)
|
19
|
+
super(original_challenge, original_origin, rp_id: rp_id)
|
20
|
+
|
21
|
+
verify_item(:credential, allowed_credentials)
|
22
|
+
verify_item(:signature, credential_public_key(allowed_credentials))
|
23
|
+
|
24
|
+
true
|
19
25
|
end
|
20
26
|
|
21
27
|
def authenticator_data
|
@@ -10,6 +10,8 @@ require "webauthn/attestation_statement"
|
|
10
10
|
require "webauthn/client_data"
|
11
11
|
|
12
12
|
module WebAuthn
|
13
|
+
class AttestationStatementVerificationError < VerificationError; end
|
14
|
+
|
13
15
|
class AuthenticatorAttestationResponse < AuthenticatorResponse
|
14
16
|
attr_reader :attestation_type, :attestation_trust_path
|
15
17
|
|
@@ -19,14 +21,11 @@ module WebAuthn
|
|
19
21
|
@attestation_object = attestation_object
|
20
22
|
end
|
21
23
|
|
22
|
-
def
|
23
|
-
|
24
|
-
return false unless valid_response
|
24
|
+
def verify(original_challenge, original_origin, rp_id: nil)
|
25
|
+
super
|
25
26
|
|
26
|
-
|
27
|
-
return false unless valid_attestation
|
27
|
+
verify_item(:attestation_statement)
|
28
28
|
|
29
|
-
@attestation_type, @attestation_trust_path = valid_attestation
|
30
29
|
true
|
31
30
|
end
|
32
31
|
|
@@ -58,5 +57,9 @@ module WebAuthn
|
|
58
57
|
def type
|
59
58
|
WebAuthn::TYPES[:create]
|
60
59
|
end
|
60
|
+
|
61
|
+
def valid_attestation_statement?
|
62
|
+
@attestation_type, @attestation_trust_path = attestation_statement.valid?(authenticator_data, client_data.hash)
|
63
|
+
end
|
61
64
|
end
|
62
65
|
end
|
@@ -1,18 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "webauthn/error"
|
4
|
+
|
3
5
|
module WebAuthn
|
6
|
+
class VerificationError < Error; end
|
7
|
+
|
8
|
+
class AuthenticatorDataVerificationError < VerificationError; end
|
9
|
+
class ChallengeVerificationError < VerificationError; end
|
10
|
+
class OriginVerificationError < VerificationError; end
|
11
|
+
class RpIdVerificationError < VerificationError; end
|
12
|
+
class TypeVerificationError < VerificationError; end
|
13
|
+
class UserPresenceVerificationError < VerificationError; end
|
14
|
+
|
4
15
|
class AuthenticatorResponse
|
5
16
|
def initialize(client_data_json:)
|
6
17
|
@client_data_json = client_data_json
|
7
18
|
end
|
8
19
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
20
|
+
def verify(original_challenge, original_origin, rp_id: nil)
|
21
|
+
verify_item(:type)
|
22
|
+
verify_item(:challenge, original_challenge)
|
23
|
+
verify_item(:origin, original_origin)
|
24
|
+
verify_item(:authenticator_data)
|
25
|
+
verify_item(:rp_id, rp_id || rp_id_from_origin(original_origin))
|
26
|
+
verify_item(:user_presence)
|
27
|
+
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid?(*args)
|
32
|
+
verify(*args)
|
33
|
+
rescue WebAuthn::VerificationError
|
34
|
+
false
|
16
35
|
end
|
17
36
|
|
18
37
|
def client_data
|
@@ -23,6 +42,16 @@ module WebAuthn
|
|
23
42
|
|
24
43
|
attr_reader :client_data_json
|
25
44
|
|
45
|
+
def verify_item(item, *args)
|
46
|
+
if send("valid_#{item}?", *args)
|
47
|
+
true
|
48
|
+
else
|
49
|
+
camelized_item = item.to_s.split('_').map { |w| w.capitalize }.join
|
50
|
+
error_const_name = "WebAuthn::#{camelized_item}VerificationError"
|
51
|
+
raise Object.const_get(error_const_name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
26
55
|
def valid_type?
|
27
56
|
client_data.type == type
|
28
57
|
end
|
@@ -39,6 +68,14 @@ module WebAuthn
|
|
39
68
|
OpenSSL::Digest::SHA256.digest(rp_id) == authenticator_data.rp_id_hash
|
40
69
|
end
|
41
70
|
|
71
|
+
def valid_authenticator_data?
|
72
|
+
authenticator_data.valid?
|
73
|
+
end
|
74
|
+
|
75
|
+
def valid_user_presence?
|
76
|
+
authenticator_data.user_flagged?
|
77
|
+
end
|
78
|
+
|
42
79
|
def rp_id_from_origin(original_origin)
|
43
80
|
URI.parse(original_origin).host
|
44
81
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "securecompare"
|
4
|
+
|
3
5
|
module WebAuthn
|
4
6
|
module SecurityUtils
|
5
7
|
# Constant time string comparison, for variable length strings.
|
@@ -10,26 +12,9 @@ module WebAuthn
|
|
10
12
|
def secure_compare(first_string, second_string)
|
11
13
|
first_string_sha256 = ::Digest::SHA256.hexdigest(first_string)
|
12
14
|
second_string_sha256 = ::Digest::SHA256.hexdigest(second_string)
|
13
|
-
fixed_length_secure_compare(first_string_sha256, second_string_sha256) && first_string == second_string
|
14
|
-
end
|
15
|
-
module_function :secure_compare
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
# Constant time string comparison, for fixed length strings.
|
20
|
-
# This code was adapted from Rails ActiveSupport::SecurityUtils
|
21
|
-
#
|
22
|
-
# The values compared should be of fixed length, such as strings
|
23
|
-
# that have already been processed by HMAC. Raises in case of length mismatch.
|
24
|
-
def fixed_length_secure_compare(first_string, second_string)
|
25
|
-
raise ArgumentError, "string length mismatch." unless first_string.bytesize == second_string.bytesize
|
26
|
-
|
27
|
-
l = first_string.unpack "C#{first_string.bytesize}"
|
28
|
-
|
29
|
-
res = 0
|
30
|
-
second_string.each_byte { |byte| res |= byte ^ l.shift }
|
31
|
-
res == 0
|
16
|
+
SecureCompare.compare(first_string_sha256, second_string_sha256) && first_string == second_string
|
32
17
|
end
|
33
|
-
module_function :
|
18
|
+
module_function :secure_compare
|
34
19
|
end
|
35
20
|
end
|
data/lib/webauthn/version.rb
CHANGED
data/webauthn.gemspec
CHANGED
@@ -33,10 +33,11 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_dependency "cose", "~> 0.1.0"
|
34
34
|
spec.add_dependency "jwt", [">= 1.5", "< 3.0"]
|
35
35
|
spec.add_dependency "openssl", "~> 2.0"
|
36
|
+
spec.add_dependency "securecompare", "~> 1.0"
|
36
37
|
|
37
38
|
spec.add_development_dependency "bundler", ">= 1.17", "< 3.0"
|
38
|
-
spec.add_development_dependency "byebug", "~>
|
39
|
+
spec.add_development_dependency "byebug", "~> 11.0"
|
39
40
|
spec.add_development_dependency "rake", "~> 12.3"
|
40
41
|
spec.add_development_dependency "rspec", "~> 3.8"
|
41
|
-
spec.add_development_dependency "rubocop", "0.
|
42
|
+
spec.add_development_dependency "rubocop", "0.65.0"
|
42
43
|
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.9.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-
|
12
|
+
date: 2019-02-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cbor
|
@@ -73,6 +73,20 @@ dependencies:
|
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '2.0'
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: securecompare
|
78
|
+
requirement: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.0'
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.0'
|
76
90
|
- !ruby/object:Gem::Dependency
|
77
91
|
name: bundler
|
78
92
|
requirement: !ruby/object:Gem::Requirement
|
@@ -99,14 +113,14 @@ dependencies:
|
|
99
113
|
requirements:
|
100
114
|
- - "~>"
|
101
115
|
- !ruby/object:Gem::Version
|
102
|
-
version: '
|
116
|
+
version: '11.0'
|
103
117
|
type: :development
|
104
118
|
prerelease: false
|
105
119
|
version_requirements: !ruby/object:Gem::Requirement
|
106
120
|
requirements:
|
107
121
|
- - "~>"
|
108
122
|
- !ruby/object:Gem::Version
|
109
|
-
version: '
|
123
|
+
version: '11.0'
|
110
124
|
- !ruby/object:Gem::Dependency
|
111
125
|
name: rake
|
112
126
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,14 +155,14 @@ dependencies:
|
|
141
155
|
requirements:
|
142
156
|
- - '='
|
143
157
|
- !ruby/object:Gem::Version
|
144
|
-
version: 0.
|
158
|
+
version: 0.65.0
|
145
159
|
type: :development
|
146
160
|
prerelease: false
|
147
161
|
version_requirements: !ruby/object:Gem::Requirement
|
148
162
|
requirements:
|
149
163
|
- - '='
|
150
164
|
- !ruby/object:Gem::Version
|
151
|
-
version: 0.
|
165
|
+
version: 0.65.0
|
152
166
|
description:
|
153
167
|
email:
|
154
168
|
- gonzalo@cedarcode.com
|