webauthn 2.0.0 → 2.1.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 -0
- data/.travis.yml +6 -3
- data/CHANGELOG.md +40 -19
- data/CONTRIBUTING.md +2 -2
- data/README.md +7 -3
- data/SECURITY.md +3 -3
- data/lib/android_safetynet/attestation_response.rb +35 -3
- data/lib/cose/rsassa_algorithm.rb +10 -0
- data/lib/tpm/constants.rb +22 -0
- data/lib/webauthn/attestation_statement/android_key.rb +1 -1
- data/lib/webauthn/attestation_statement/android_safetynet.rb +7 -17
- data/lib/webauthn/attestation_statement/base.rb +16 -4
- data/lib/webauthn/attestation_statement/fido_u2f.rb +6 -4
- data/lib/webauthn/attestation_statement/packed.rb +7 -7
- data/lib/webauthn/attestation_statement/tpm.rb +28 -7
- data/lib/webauthn/attestation_statement/tpm/pub_area.rb +2 -2
- data/lib/webauthn/authenticator_assertion_response.rb +5 -30
- data/lib/webauthn/authenticator_attestation_response.rb +45 -2
- data/lib/webauthn/authenticator_response.rb +12 -10
- data/lib/webauthn/configuration.rb +23 -0
- data/lib/webauthn/credential.rb +4 -4
- data/lib/webauthn/credential_creation_options.rb +1 -1
- data/lib/webauthn/public_key.rb +49 -0
- data/lib/webauthn/public_key_credential/creation_options.rb +2 -2
- data/lib/webauthn/signature_verifier.rb +19 -7
- data/lib/webauthn/version.rb +1 -1
- data/webauthn.gemspec +2 -1
- metadata +21 -6
- data/lib/cose/algorithm.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78367d9a528b780d4e53a29667fe47cf95d03698c976714fc7d71d46b253f4fa
|
4
|
+
data.tar.gz: ab4e9b0ef6a0bfcb9bd2c33d8220e0f3241f2ba949c57053d52968b6e34a5a61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9a3514276b45c34c5509e7d55a6238d8a705ab19534a5ca3a802be4a00f42784b81c3dbc02f3c991918bb61da55b6f20c268a0573b8099224d9467d13932a60
|
7
|
+
data.tar.gz: e337a703e30a984b881d589f5c31a653e312a9ae4395cf2c411408e0f162871622c7f3d212412ea01de4db121a1ddf2431000ae55968358062f513b5feef812c
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -4,6 +4,7 @@ cache: bundler
|
|
4
4
|
|
5
5
|
rvm:
|
6
6
|
- ruby-head
|
7
|
+
- 2.7.0
|
7
8
|
- 2.6.5
|
8
9
|
- 2.5.7
|
9
10
|
- 2.4.9
|
@@ -22,12 +23,14 @@ matrix:
|
|
22
23
|
- gemfile: gemfiles/cose_head.gemfile
|
23
24
|
- gemfile: gemfiles/openssl_head.gemfile
|
24
25
|
|
26
|
+
addons:
|
27
|
+
apt:
|
28
|
+
packages:
|
29
|
+
- libfaketime
|
30
|
+
|
25
31
|
before_install:
|
26
|
-
- wget http://archive.ubuntu.com/ubuntu/pool/universe/f/faketime/libfaketime_0.9.7-3_amd64.deb
|
27
|
-
- sudo dpkg -i libfaketime_0.9.7-3_amd64.deb
|
28
32
|
- gem install bundler -v "~> 2.0"
|
29
33
|
|
30
34
|
before_script:
|
31
35
|
- export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
|
32
|
-
- export DONT_FAKE_MONOTONIC=1
|
33
36
|
- export FAKETIME_NO_CACHE=1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v2.1.0] - 2019-12-30
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Ability to convert stored credential public key back to a ruby object with `WebAuthn::PublicKey.deserialize(stored_public_key)`, included the validation during de-serialization ([@ssuttner], [@padulafacundo])
|
8
|
+
- Improved TPM attestation validation by checking "Subject Alternative Name" ([@bdewater])
|
9
|
+
- Improved SafetyNet attestation validation by checking timestamp ([@padulafacundo])
|
10
|
+
- [EXPERIMENTAL] Ability to optionally "Assess the attestation trustworthiness" during registration by setting `acceptable_attestation_types` and `attestation_root_certificates_finders` configuration values ([@padulafacundo])
|
11
|
+
- Ruby 2.7 support without warnings
|
12
|
+
|
13
|
+
Note: Expect possible breaking changes for "EXPERIMENTAL" features.
|
14
|
+
|
3
15
|
## [v2.0.0] - 2019-10-03
|
4
16
|
|
5
17
|
### Added
|
@@ -13,7 +25,7 @@
|
|
13
25
|
- All the above automatically handle encoding/decoding for necessary values. The specific encoding scheme can
|
14
26
|
be set (or even turned off) in `WebAutnn.configuration.encoding=`. Defaults to `:base64url`.
|
15
27
|
- `WebAuthn::FakeClient#get` better fakes a real client by including `userHandle` in the returned hash.
|
16
|
-
- Expose AAGUID and attestationCertificateKey for MDS lookup during attestation (@
|
28
|
+
- Expose AAGUID and attestationCertificateKey for MDS lookup during attestation ([@bdewater])
|
17
29
|
|
18
30
|
### Changed
|
19
31
|
|
@@ -56,23 +68,23 @@ returned base64url-encoded `id` value.
|
|
56
68
|
|
57
69
|
### Added
|
58
70
|
|
59
|
-
- Ability to migrate U2F credentials to WebAuthn ([#211](https://github.com/cedarcode/webauthn-ruby/pull/211)) (@bdewater + @jdongelmans)
|
60
|
-
- Ability to skip attestation statement verification ([#219](https://github.com/cedarcode/webauthn-ruby/pull/219)) (@MaximeNdutiye)
|
61
|
-
- Ability to configure default credential options timeout ([#243](https://github.com/cedarcode/webauthn-ruby/pull/243)) (@MaximeNdutiye)
|
71
|
+
- Ability to migrate U2F credentials to WebAuthn ([#211](https://github.com/cedarcode/webauthn-ruby/pull/211)) ([@bdewater] + [@jdongelmans])
|
72
|
+
- Ability to skip attestation statement verification ([#219](https://github.com/cedarcode/webauthn-ruby/pull/219)) ([@MaximeNdutiye])
|
73
|
+
- Ability to configure default credential options timeout ([#243](https://github.com/cedarcode/webauthn-ruby/pull/243)) ([@MaximeNdutiye])
|
62
74
|
- AttestedCredentialData presence verification ([#237](https://github.com/cedarcode/webauthn-ruby/pull/237))
|
63
75
|
- FakeClient learns how to increment sign count ([#225](https://github.com/cedarcode/webauthn-ruby/pull/225))
|
64
76
|
|
65
77
|
### Fixed
|
66
78
|
|
67
|
-
- Properly verify SafetyNet certificates from input ([#233](https://github.com/cedarcode/webauthn-ruby/pull/233)) (@bdewater)
|
68
|
-
- FakeClient default origin URL ([#242](https://github.com/cedarcode/webauthn-ruby/pull/242)) (@kalebtesfay)
|
79
|
+
- Properly verify SafetyNet certificates from input ([#233](https://github.com/cedarcode/webauthn-ruby/pull/233)) ([@bdewater])
|
80
|
+
- FakeClient default origin URL ([#242](https://github.com/cedarcode/webauthn-ruby/pull/242)) ([@kalebtesfay])
|
69
81
|
|
70
82
|
## [v1.17.0] - 2019-06-18
|
71
83
|
|
72
84
|
### Added
|
73
85
|
|
74
|
-
- Support ES384, ES512, PS384, PS512, RS384 and RS512 credentials. Off by default. Enable by adding any of them to `WebAuthn.configuration.algorithms` array
|
75
|
-
- Support [Signature Counter](https://www.w3.org/TR/webauthn/#signature-counter) verification
|
86
|
+
- Support ES384, ES512, PS384, PS512, RS384 and RS512 credentials. Off by default. Enable by adding any of them to `WebAuthn.configuration.algorithms` array ([@bdewater])
|
87
|
+
- Support [Signature Counter](https://www.w3.org/TR/webauthn/#signature-counter) verification ([@bdewater])
|
76
88
|
|
77
89
|
## [v1.16.0] - 2019-06-13
|
78
90
|
|
@@ -80,7 +92,7 @@ returned base64url-encoded `id` value.
|
|
80
92
|
|
81
93
|
- Ability to enforce [user verification](https://www.w3.org/TR/webauthn/#user-verification) with extra argument in the `#verify` method.
|
82
94
|
- Support RS1 (RSA w/ SHA-1) credentials. Off by default. Enable by adding `"RS1"` to `WebAuthn.configuration.algorithms` array.
|
83
|
-
- Support PS256 (RSA Probabilistic Signature Scheme w/ SHA-256) credentials. On by default
|
95
|
+
- Support PS256 (RSA Probabilistic Signature Scheme w/ SHA-256) credentials. On by default ([@bdewater])
|
84
96
|
|
85
97
|
## [v1.15.0] - 2019-05-16
|
86
98
|
|
@@ -102,11 +114,11 @@ returned base64url-encoded `id` value.
|
|
102
114
|
- Verify 'none' attestation statement is really empty.
|
103
115
|
- Verify 'packed' attestation statement certificates start/end dates.
|
104
116
|
- Verify 'packed' attestation statement signature algorithm.
|
105
|
-
- Verify 'fiod-u2f attestation statement AAGUID is zeroed out
|
117
|
+
- Verify 'fiod-u2f attestation statement AAGUID is zeroed out ([@bdewater])
|
106
118
|
- Verify 'android-key' attestation statement signature algorithm.
|
107
119
|
- Verify assertion response signature algorithm.
|
108
120
|
- Verify collectedClientData.tokenBinding format.
|
109
|
-
- `WebAuthn.credential_creation_options` now accept `rp_name`, `user_id`, `user_name` and `display_name` as keyword arguments
|
121
|
+
- `WebAuthn.credential_creation_options` now accept `rp_name`, `user_id`, `user_name` and `display_name` as keyword arguments ([@bdewater])
|
110
122
|
|
111
123
|
## [v1.12.0] - 2019-04-03
|
112
124
|
|
@@ -128,11 +140,11 @@ Note #2: You don't need to do any convesion before passing the public key in `Au
|
|
128
140
|
|
129
141
|
### Added
|
130
142
|
|
131
|
-
- `WebAuthn::AuthenticatorAttestationResponse#verify` supports `android-key` attestation statements
|
143
|
+
- `WebAuthn::AuthenticatorAttestationResponse#verify` supports `android-key` attestation statements ([@bdewater])
|
132
144
|
|
133
145
|
### Fixed
|
134
146
|
|
135
|
-
- Verify matching AAGUID if needed when verifying `packed` attestation statements
|
147
|
+
- Verify matching AAGUID if needed when verifying `packed` attestation statements ([@bdewater])
|
136
148
|
|
137
149
|
## [v1.10.0] - 2019-03-05
|
138
150
|
|
@@ -150,7 +162,7 @@ Note #2: You don't need to do any convesion before passing the public key in `Au
|
|
150
162
|
|
151
163
|
### Added
|
152
164
|
|
153
|
-
- Make challenge validation inside `#valid?` method resistant to timing attacks
|
165
|
+
- Make challenge validation inside `#valid?` method resistant to timing attacks (@tomek-bt)
|
154
166
|
- Support for ruby 2.6
|
155
167
|
|
156
168
|
### Changed
|
@@ -162,7 +174,7 @@ Note #2: You don't need to do any convesion before passing the public key in `Au
|
|
162
174
|
### Added
|
163
175
|
|
164
176
|
- _Registration_ ceremony
|
165
|
-
- `WebAuthn::AuthenticatorAttestationResponse` exposes attestation type and trust path via `#attestation_type` and `#attestation_trust_path` methods
|
177
|
+
- `WebAuthn::AuthenticatorAttestationResponse` exposes attestation type and trust path via `#attestation_type` and `#attestation_trust_path` methods ([@bdewater])
|
166
178
|
|
167
179
|
## [v1.6.0] - 2018-11-01
|
168
180
|
|
@@ -174,21 +186,21 @@ Note #2: You don't need to do any convesion before passing the public key in `Au
|
|
174
186
|
|
175
187
|
### Added
|
176
188
|
|
177
|
-
- Works with ruby 2.3
|
189
|
+
- Works with ruby 2.3 ([@bdewater])
|
178
190
|
|
179
191
|
## [v1.4.0] - 2018-10-11
|
180
192
|
|
181
193
|
### Added
|
182
194
|
|
183
195
|
- _Registration_ ceremony
|
184
|
-
- `WebAuthn::AuthenticatorAttestationResponse.valid?` supports `android-safetynet` attestation statements
|
196
|
+
- `WebAuthn::AuthenticatorAttestationResponse.valid?` supports `android-safetynet` attestation statements ([@bdewater])
|
185
197
|
|
186
198
|
## [v1.3.0] - 2018-10-11
|
187
199
|
|
188
200
|
### Added
|
189
201
|
|
190
202
|
- _Registration_ ceremony
|
191
|
-
- `WebAuthn::AuthenticatorAttestationResponse.valid?` supports `packed` attestation statements
|
203
|
+
- `WebAuthn::AuthenticatorAttestationResponse.valid?` supports `packed` attestation statements ([@sorah])
|
192
204
|
|
193
205
|
## [v1.2.0] - 2018-10-08
|
194
206
|
|
@@ -206,7 +218,7 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
206
218
|
### Added
|
207
219
|
|
208
220
|
- _Registration_ ceremony
|
209
|
-
- `WebAuthn::AuthenticatorAttestationResponse.valid?` optionally accepts rp_id
|
221
|
+
- `WebAuthn::AuthenticatorAttestationResponse.valid?` optionally accepts rp_id ([@sorah])
|
210
222
|
- _Authentication_ ceremony
|
211
223
|
- `WebAuthn::AuthenticatorAssertionResponse.valid?` optionally accepts rp_id.
|
212
224
|
|
@@ -261,6 +273,7 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
261
273
|
- `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
|
262
274
|
- Works with ruby 2.5
|
263
275
|
|
276
|
+
[v2.1.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.0.0...v2.1.0/
|
264
277
|
[v2.0.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.18.0...v2.0.0/
|
265
278
|
[v1.18.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.17.0...v1.18.0/
|
266
279
|
[v1.17.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.16.0...v1.17.0/
|
@@ -283,3 +296,11 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
283
296
|
[v1.0.0]: https://github.com/cedarcode/webauthn-ruby/compare/v0.2.0...v1.0.0/
|
284
297
|
[v0.2.0]: https://github.com/cedarcode/webauthn-ruby/compare/v0.1.0...v0.2.0/
|
285
298
|
[v0.1.0]: https://github.com/cedarcode/webauthn-ruby/compare/v0.0.0...v0.1.0/
|
299
|
+
|
300
|
+
[@bdewater]: https://github.com/bdewater
|
301
|
+
[@jdongelmans]: https://github.com/jdongelmans
|
302
|
+
[@kalebtesfay]: https://github.com/kalebtesfay
|
303
|
+
[@MaximeNdutiye]: https://github.com/MaximeNdutiye
|
304
|
+
[@sorah]: https://github.com/sorah
|
305
|
+
[@ssuttner]: https://github.com/ssuttner
|
306
|
+
[@padulafacundo]: https://github.com/padulafacundo
|
data/CONTRIBUTING.md
CHANGED
@@ -14,9 +14,9 @@
|
|
14
14
|
|
15
15
|
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.
|
16
16
|
|
17
|
-
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 `
|
17
|
+
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 `FAKETIME_NO_CACHE=1` option. E.g. when installed via homebrew on macOS:
|
18
18
|
```shell
|
19
|
-
DYLD_INSERT_LIBRARIES=/usr/local/Cellar/libfaketime/2.9.7_1/lib/faketime/libfaketime.1.dylib DYLD_FORCE_FLAT_NAMESPACE=1
|
19
|
+
DYLD_INSERT_LIBRARIES=/usr/local/Cellar/libfaketime/2.9.7_1/lib/faketime/libfaketime.1.dylib DYLD_FORCE_FLAT_NAMESPACE=1 FAKETIME_NO_CACHE=1 bundle exec rspec
|
20
20
|
```
|
21
21
|
|
22
22
|
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).
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
__Note__: You are viewing the README for the development version of webauthn-ruby.
|
2
|
-
For the current release version see https://github.com/cedarcode/webauthn-ruby/blob/
|
2
|
+
For the current release version see https://github.com/cedarcode/webauthn-ruby/blob/2-stable/README.md.
|
3
3
|
|
4
4
|
# webauthn-ruby
|
5
5
|
|
@@ -342,7 +342,9 @@ credential_with_assertion.verify(
|
|
342
342
|
)
|
343
343
|
```
|
344
344
|
|
345
|
-
## Attestation
|
345
|
+
## Attestation
|
346
|
+
|
347
|
+
### Attestation Statement Format
|
346
348
|
|
347
349
|
| Attestation Statement Format | Supported? |
|
348
350
|
| -------- | :--------: |
|
@@ -356,7 +358,9 @@ credential_with_assertion.verify(
|
|
356
358
|
| fido-u2f | Yes |
|
357
359
|
| none | Yes |
|
358
360
|
|
359
|
-
|
361
|
+
### Attestation Types
|
362
|
+
|
363
|
+
You can define what trust policy to enforce by setting `acceptable_attestation_types` config to a subset of `['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA']` and `attestation_root_certificates_finders` to an object that responds to `#find` and returns the corresponding root certificate for each registration. The `#find` method will be called passing keyword arguments `attesation_format`, `aaguid` and `attestation_certificate_key_id`.
|
360
364
|
|
361
365
|
## Testing Your Integration
|
362
366
|
|
data/SECURITY.md
CHANGED
@@ -4,11 +4,11 @@
|
|
4
4
|
|
5
5
|
| Version | Supported |
|
6
6
|
| ------- | ------------------ |
|
7
|
+
| 2.1.z | :white_check_mark: |
|
8
|
+
| 2.0.z | :white_check_mark: |
|
7
9
|
| 1.18.z | :white_check_mark: |
|
8
10
|
| 1.17.z | :white_check_mark: |
|
9
|
-
| 1.
|
10
|
-
| 1.15.z | :white_check_mark: |
|
11
|
-
| < 1.15 | :x: |
|
11
|
+
| < 1.17 | :x: |
|
12
12
|
|
13
13
|
## Reporting a Vulnerability
|
14
14
|
|
@@ -13,11 +13,22 @@ module AndroidSafetynet
|
|
13
13
|
class NonceMismatchError < VerificationError; end
|
14
14
|
class SignatureError < VerificationError; end
|
15
15
|
class ResponseMissingError < VerificationError; end
|
16
|
+
class TimestampError < VerificationError; end
|
17
|
+
class TrustworthinessError < VerificationError; end
|
16
18
|
|
17
19
|
CERTIRICATE_CHAIN_HEADER = "x5c"
|
18
20
|
VALID_SUBJECT_HOSTNAME = "attest.android.com"
|
19
21
|
HEADERS_POSITION = 1
|
20
22
|
PAYLOAD_POSITION = 0
|
23
|
+
LEEWAY = 60
|
24
|
+
|
25
|
+
# FIXME: Should probably be limited to only roots published by Google
|
26
|
+
# See https://github.com/cedarcode/webauthn-ruby/issues/160#issuecomment-487941201
|
27
|
+
@trust_store = OpenSSL::X509::Store.new.tap { |trust_store| trust_store.set_default_paths }
|
28
|
+
|
29
|
+
class << self
|
30
|
+
attr_accessor :trust_store
|
31
|
+
end
|
21
32
|
|
22
33
|
attr_reader :response
|
23
34
|
|
@@ -25,11 +36,15 @@ module AndroidSafetynet
|
|
25
36
|
@response = response
|
26
37
|
end
|
27
38
|
|
28
|
-
def verify(nonce)
|
39
|
+
def verify(nonce, trustworthiness: true)
|
29
40
|
if response
|
30
41
|
valid_nonce?(nonce) || raise(NonceMismatchError)
|
31
42
|
valid_attestation_domain? || raise(LeafCertificateSubjectError)
|
32
43
|
valid_signature? || raise(SignatureError)
|
44
|
+
valid_timestamp? || raise(TimestampError)
|
45
|
+
trustworthy? || raise(TrustworthinessError) if trustworthiness
|
46
|
+
|
47
|
+
true
|
33
48
|
else
|
34
49
|
raise(ResponseMissingError)
|
35
50
|
end
|
@@ -39,12 +54,21 @@ module AndroidSafetynet
|
|
39
54
|
payload["ctsProfileMatch"]
|
40
55
|
end
|
41
56
|
|
57
|
+
def leaf_certificate
|
58
|
+
certificate_chain[0]
|
59
|
+
end
|
60
|
+
|
42
61
|
def certificate_chain
|
43
62
|
@certificate_chain ||= headers[CERTIRICATE_CHAIN_HEADER].map do |cert|
|
44
63
|
OpenSSL::X509::Certificate.new(Base64.strict_decode64(cert))
|
45
64
|
end
|
46
65
|
end
|
47
66
|
|
67
|
+
def valid_timestamp?
|
68
|
+
now = Time.now
|
69
|
+
Time.at((payload["timestampMs"] / 1000.0).round).between?(now - LEEWAY, now)
|
70
|
+
end
|
71
|
+
|
48
72
|
private
|
49
73
|
|
50
74
|
def valid_nonce?(nonce)
|
@@ -65,8 +89,16 @@ module AndroidSafetynet
|
|
65
89
|
false
|
66
90
|
end
|
67
91
|
|
68
|
-
def
|
69
|
-
|
92
|
+
def trustworthy?
|
93
|
+
!trust_store || trust_store.verify(leaf_certificate, signing_certificates)
|
94
|
+
end
|
95
|
+
|
96
|
+
def trust_store
|
97
|
+
self.class.trust_store
|
98
|
+
end
|
99
|
+
|
100
|
+
def signing_certificates
|
101
|
+
certificate_chain[1..-1]
|
70
102
|
end
|
71
103
|
|
72
104
|
def headers
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cose"
|
4
|
+
|
5
|
+
RSASSAAlgorithm = Struct.new(:id, :name, :hash_function, :kty)
|
6
|
+
|
7
|
+
COSE::Algorithm.register(RSASSAAlgorithm.new(-257, "RS256", "SHA256", COSE::Key::RSA::KTY_RSA))
|
8
|
+
COSE::Algorithm.register(RSASSAAlgorithm.new(-258, "RS384", "SHA384", COSE::Key::RSA::KTY_RSA))
|
9
|
+
COSE::Algorithm.register(RSASSAAlgorithm.new(-259, "RS512", "SHA512", COSE::Key::RSA::KTY_RSA))
|
10
|
+
COSE::Algorithm.register(RSASSAAlgorithm.new(-65535, "RS1", "SHA1", COSE::Key::RSA::KTY_RSA))
|
data/lib/tpm/constants.rb
CHANGED
@@ -19,4 +19,26 @@ module TPM
|
|
19
19
|
|
20
20
|
# ECC curves
|
21
21
|
ECC_NIST_P256 = 0x0003
|
22
|
+
|
23
|
+
# https://trustedcomputinggroup.org/resource/vendor-id-registry/ section 2 "TPM Capabilities Vendor ID (CAP_VID)"
|
24
|
+
VENDOR_IDS = {
|
25
|
+
"id:414D4400" => "AMD",
|
26
|
+
"id:41544D4C" => "Atmel",
|
27
|
+
"id:4252434D" => "Broadcom",
|
28
|
+
"id:49424D00" => "IBM",
|
29
|
+
"id:49465800" => "Infineon",
|
30
|
+
"id:494E5443" => "Intel",
|
31
|
+
"id:4C454E00" => "Lenovo",
|
32
|
+
"id:4E534D20" => "National Semiconductor",
|
33
|
+
"id:4E545A00" => "Nationz",
|
34
|
+
"id:4E544300" => "Nuvoton Technology",
|
35
|
+
"id:51434F4D" => "Qualcomm",
|
36
|
+
"id:534D5343" => "SMSC",
|
37
|
+
"id:53544D20" => "ST Microelectronics",
|
38
|
+
"id:534D534E" => "Samsung",
|
39
|
+
"id:534E5300" => "Sinosun",
|
40
|
+
"id:54584E00" => "Texas Instruments",
|
41
|
+
"id:57454300" => "Winbond",
|
42
|
+
"id:524F4343" => "Fuzhou Rockchip",
|
43
|
+
}.freeze
|
22
44
|
end
|
@@ -22,7 +22,7 @@ module WebAuthn
|
|
22
22
|
all_applications_field_not_present? &&
|
23
23
|
valid_authorization_list_origin? &&
|
24
24
|
valid_authorization_list_purpose? &&
|
25
|
-
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC,
|
25
|
+
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC, attestation_trust_path]
|
26
26
|
end
|
27
27
|
|
28
28
|
private
|
@@ -8,34 +8,24 @@ module WebAuthn
|
|
8
8
|
module AttestationStatement
|
9
9
|
# Implements https://www.w3.org/TR/webauthn-1/#sctn-android-safetynet-attestation
|
10
10
|
class AndroidSafetynet < Base
|
11
|
-
def
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
def valid?(authenticator_data, client_data_hash, trust_store: self.class.default_trust_store)
|
16
|
-
trusted_attestation_certificate?(trust_store) &&
|
17
|
-
valid_response?(authenticator_data, client_data_hash) &&
|
11
|
+
def valid?(authenticator_data, client_data_hash)
|
12
|
+
valid_response?(authenticator_data, client_data_hash) &&
|
18
13
|
valid_version? &&
|
19
14
|
cts_profile_match? &&
|
20
|
-
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC,
|
15
|
+
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC, attestation_trust_path]
|
21
16
|
end
|
22
17
|
|
23
18
|
def attestation_certificate
|
24
|
-
attestation_response.
|
19
|
+
attestation_response.leaf_certificate
|
25
20
|
end
|
26
21
|
|
27
22
|
private
|
28
23
|
|
29
|
-
# FIXME: This should be a responsibility of AndroidSafetynet::AttestationResponse#verify
|
30
|
-
def trusted_attestation_certificate?(trust_store)
|
31
|
-
trust_store.verify(attestation_certificate, signing_certificates)
|
32
|
-
end
|
33
|
-
|
34
24
|
def valid_response?(authenticator_data, client_data_hash)
|
35
25
|
nonce = Digest::SHA256.base64digest(authenticator_data.data + client_data_hash)
|
36
26
|
|
37
27
|
begin
|
38
|
-
attestation_response.verify(nonce)
|
28
|
+
attestation_response.verify(nonce, trustworthiness: false)
|
39
29
|
rescue ::AndroidSafetynet::AttestationResponse::VerificationError
|
40
30
|
false
|
41
31
|
end
|
@@ -50,8 +40,8 @@ module WebAuthn
|
|
50
40
|
attestation_response.cts_profile_match?
|
51
41
|
end
|
52
42
|
|
53
|
-
def
|
54
|
-
attestation_response.certificate_chain
|
43
|
+
def attestation_trust_path
|
44
|
+
attestation_response.certificate_chain
|
55
45
|
end
|
56
46
|
|
57
47
|
def attestation_response
|
@@ -27,7 +27,13 @@ module WebAuthn
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def attestation_certificate
|
30
|
-
|
30
|
+
certificates&.first
|
31
|
+
end
|
32
|
+
|
33
|
+
def certificate_chain
|
34
|
+
if certificates
|
35
|
+
certificates[1..-1]
|
36
|
+
end
|
31
37
|
end
|
32
38
|
|
33
39
|
private
|
@@ -46,8 +52,8 @@ module WebAuthn
|
|
46
52
|
end
|
47
53
|
end
|
48
54
|
|
49
|
-
def
|
50
|
-
@
|
55
|
+
def certificates
|
56
|
+
@certificates ||= raw_certificates&.map do |raw_certificate|
|
51
57
|
OpenSSL::X509::Certificate.new(raw_certificate)
|
52
58
|
end
|
53
59
|
end
|
@@ -56,7 +62,7 @@ module WebAuthn
|
|
56
62
|
statement["alg"]
|
57
63
|
end
|
58
64
|
|
59
|
-
def
|
65
|
+
def raw_certificates
|
60
66
|
statement["x5c"]
|
61
67
|
end
|
62
68
|
|
@@ -67,6 +73,12 @@ module WebAuthn
|
|
67
73
|
def signature
|
68
74
|
statement["sig"]
|
69
75
|
end
|
76
|
+
|
77
|
+
def attestation_trust_path
|
78
|
+
if certificates&.any?
|
79
|
+
certificates
|
80
|
+
end
|
81
|
+
end
|
70
82
|
end
|
71
83
|
end
|
72
84
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "cose"
|
3
4
|
require "openssl"
|
4
5
|
require "webauthn/attestation_statement/base"
|
5
6
|
require "webauthn/attestation_statement/fido_u2f/public_key"
|
@@ -10,6 +11,7 @@ module WebAuthn
|
|
10
11
|
class FidoU2f < Base
|
11
12
|
VALID_ATTESTATION_CERTIFICATE_COUNT = 1
|
12
13
|
VALID_ATTESTATION_CERTIFICATE_ALGORITHM = COSE::Algorithm.by_name("ES256")
|
14
|
+
VALID_ATTESTATION_CERTIFICATE_KEY_CURVE = COSE::Key::Curve.by_name("P-256")
|
13
15
|
|
14
16
|
def valid?(authenticator_data, client_data_hash)
|
15
17
|
valid_format? &&
|
@@ -17,19 +19,19 @@ module WebAuthn
|
|
17
19
|
valid_credential_public_key?(authenticator_data.credential.public_key) &&
|
18
20
|
valid_aaguid?(authenticator_data.attested_credential_data.raw_aaguid) &&
|
19
21
|
valid_signature?(authenticator_data, client_data_hash) &&
|
20
|
-
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC_OR_ATTCA,
|
22
|
+
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC_OR_ATTCA, attestation_trust_path]
|
21
23
|
end
|
22
24
|
|
23
25
|
private
|
24
26
|
|
25
27
|
def valid_format?
|
26
|
-
!!(
|
27
|
-
|
28
|
+
!!(raw_certificates && signature) &&
|
29
|
+
raw_certificates.length == VALID_ATTESTATION_CERTIFICATE_COUNT
|
28
30
|
end
|
29
31
|
|
30
32
|
def valid_certificate_public_key?
|
31
33
|
certificate_public_key.is_a?(OpenSSL::PKey::EC) &&
|
32
|
-
certificate_public_key.group.curve_name ==
|
34
|
+
certificate_public_key.group.curve_name == VALID_ATTESTATION_CERTIFICATE_KEY_CURVE.pkey_name &&
|
33
35
|
certificate_public_key.check_key
|
34
36
|
end
|
35
37
|
|
@@ -30,12 +30,12 @@ module WebAuthn
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def self_attestation?
|
33
|
-
!
|
33
|
+
!raw_certificates && !raw_ecdaa_key_id
|
34
34
|
end
|
35
35
|
|
36
36
|
def valid_format?
|
37
37
|
algorithm && signature && (
|
38
|
-
[
|
38
|
+
[raw_certificates, raw_ecdaa_key_id].compact.size < 2
|
39
39
|
)
|
40
40
|
end
|
41
41
|
|
@@ -46,15 +46,15 @@ module WebAuthn
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def valid_certificate_chain?
|
49
|
-
if
|
50
|
-
|
49
|
+
if certificate_chain
|
50
|
+
certificate_chain.all? { |c| certificate_in_use?(c) }
|
51
51
|
else
|
52
52
|
true
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
56
|
def valid_ec_public_keys?(credential)
|
57
|
-
(
|
57
|
+
(certificates&.map(&:public_key) || [credential.public_key_object])
|
58
58
|
.select { |pkey| pkey.is_a?(OpenSSL::PKey::EC) }
|
59
59
|
.all? { |pkey| pkey.check_key }
|
60
60
|
end
|
@@ -89,8 +89,8 @@ module WebAuthn
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def attestation_type_and_trust_path
|
92
|
-
if
|
93
|
-
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC_OR_ATTCA,
|
92
|
+
if attestation_trust_path
|
93
|
+
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC_OR_ATTCA, attestation_trust_path]
|
94
94
|
else
|
95
95
|
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_SELF, nil]
|
96
96
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "cose/algorithm"
|
4
4
|
require "openssl"
|
5
|
+
require "tpm/constants"
|
5
6
|
require "webauthn/attestation_statement/base"
|
6
7
|
require "webauthn/attestation_statement/tpm/cert_info"
|
7
8
|
require "webauthn/attestation_statement/tpm/pub_area"
|
@@ -12,6 +13,10 @@ module WebAuthn
|
|
12
13
|
class TPM < Base
|
13
14
|
CERTIFICATE_V3 = 2
|
14
15
|
CERTIFICATE_EMPTY_NAME = OpenSSL::X509::Name.new([]).freeze
|
16
|
+
CERTIFICATE_SAN_DIRECTORY_NAME = 4
|
17
|
+
OID_TCG_AT_TPM_MANUFACTURER = "2.23.133.2.1"
|
18
|
+
OID_TCG_AT_TPM_MODEL = "2.23.133.2.2"
|
19
|
+
OID_TCG_AT_TPM_VERSION = "2.23.133.2.3"
|
15
20
|
OID_TCG_KP_AIK_CERTIFICATE = "2.23.133.8.3"
|
16
21
|
TPM_V2 = "2.0"
|
17
22
|
|
@@ -24,7 +29,8 @@ module WebAuthn
|
|
24
29
|
valid_signature? &&
|
25
30
|
valid_attestation_certificate? &&
|
26
31
|
pub_area.valid?(authenticator_data.credential.public_key) &&
|
27
|
-
cert_info.valid?(statement["pubArea"],
|
32
|
+
cert_info.valid?(statement["pubArea"],
|
33
|
+
OpenSSL::Digest.digest(cose_algorithm.hash_function, att_to_be_signed)) &&
|
28
34
|
matching_aaguid?(authenticator_data.attested_credential_data.raw_aaguid) &&
|
29
35
|
[attestation_type, attestation_trust_path]
|
30
36
|
when ATTESTATION_TYPE_ECDAA
|
@@ -48,11 +54,30 @@ module WebAuthn
|
|
48
54
|
|
49
55
|
attestation_certificate.version == CERTIFICATE_V3 &&
|
50
56
|
attestation_certificate.subject.eql?(CERTIFICATE_EMPTY_NAME) &&
|
57
|
+
valid_subject_alternative_name? &&
|
51
58
|
certificate_in_use?(attestation_certificate) &&
|
52
59
|
extensions.find { |ext| ext.oid == 'basicConstraints' }&.value == "CA:FALSE" &&
|
53
60
|
extensions.find { |ext| ext.oid == "extendedKeyUsage" }&.value == OID_TCG_KP_AIK_CERTIFICATE
|
54
61
|
end
|
55
62
|
|
63
|
+
def valid_subject_alternative_name?
|
64
|
+
extension = attestation_certificate.extensions.detect { |ext| ext.oid == "subjectAltName" }
|
65
|
+
return unless extension&.critical?
|
66
|
+
|
67
|
+
san_asn1 = OpenSSL::ASN1.decode(extension).find do |val|
|
68
|
+
val.tag_class == :UNIVERSAL && val.tag == OpenSSL::ASN1::OCTET_STRING
|
69
|
+
end
|
70
|
+
directory_name = OpenSSL::ASN1.decode(san_asn1.value).find do |val|
|
71
|
+
val.tag_class == :CONTEXT_SPECIFIC && val.tag == CERTIFICATE_SAN_DIRECTORY_NAME
|
72
|
+
end
|
73
|
+
name = OpenSSL::X509::Name.new(directory_name.value.first).to_a
|
74
|
+
manufacturer = name.assoc(OID_TCG_AT_TPM_MANUFACTURER).at(1)
|
75
|
+
model = name.assoc(OID_TCG_AT_TPM_MODEL).at(1)
|
76
|
+
version = name.assoc(OID_TCG_AT_TPM_VERSION).at(1)
|
77
|
+
|
78
|
+
::TPM::VENDOR_IDS[manufacturer] && !model.empty? && !version.empty?
|
79
|
+
end
|
80
|
+
|
56
81
|
def certificate_in_use?(certificate)
|
57
82
|
now = Time.now
|
58
83
|
|
@@ -80,18 +105,14 @@ module WebAuthn
|
|
80
105
|
end
|
81
106
|
|
82
107
|
def attestation_type
|
83
|
-
if
|
108
|
+
if raw_certificates && !raw_ecdaa_key_id
|
84
109
|
ATTESTATION_TYPE_ATTCA
|
85
|
-
elsif raw_ecdaa_key_id && !
|
110
|
+
elsif raw_ecdaa_key_id && !raw_certificates
|
86
111
|
ATTESTATION_TYPE_ECDAA
|
87
112
|
else
|
88
113
|
raise "Attestation type invalid"
|
89
114
|
end
|
90
115
|
end
|
91
|
-
|
92
|
-
def attestation_trust_path
|
93
|
-
attestation_certificate_chain
|
94
|
-
end
|
95
116
|
end
|
96
117
|
end
|
97
118
|
end
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "cose/algorithm"
|
4
|
-
require "cose/key"
|
5
|
-
require "webauthn/attestation_statement/fido_u2f/public_key"
|
6
3
|
require "webauthn/authenticator_data"
|
7
4
|
require "webauthn/authenticator_response"
|
8
5
|
require "webauthn/encoder"
|
9
6
|
require "webauthn/signature_verifier"
|
7
|
+
require "webauthn/public_key"
|
10
8
|
|
11
9
|
module WebAuthn
|
12
10
|
class SignatureVerificationError < VerificationError; end
|
@@ -32,7 +30,7 @@ module WebAuthn
|
|
32
30
|
attr_reader :user_handle
|
33
31
|
|
34
32
|
def initialize(authenticator_data:, signature:, user_handle: nil, **options)
|
35
|
-
super(options)
|
33
|
+
super(**options)
|
36
34
|
|
37
35
|
@authenticator_data_bytes = authenticator_data
|
38
36
|
@signature = signature
|
@@ -42,7 +40,7 @@ module WebAuthn
|
|
42
40
|
def verify(expected_challenge, expected_origin = nil, public_key:, sign_count:, user_verification: nil,
|
43
41
|
rp_id: nil)
|
44
42
|
super(expected_challenge, expected_origin, user_verification: user_verification, rp_id: rp_id)
|
45
|
-
verify_item(:signature,
|
43
|
+
verify_item(:signature, WebAuthn::PublicKey.deserialize(public_key))
|
46
44
|
verify_item(:sign_count, sign_count)
|
47
45
|
|
48
46
|
true
|
@@ -56,9 +54,9 @@ module WebAuthn
|
|
56
54
|
|
57
55
|
attr_reader :authenticator_data_bytes, :signature
|
58
56
|
|
59
|
-
def valid_signature?(
|
57
|
+
def valid_signature?(webauthn_public_key)
|
60
58
|
WebAuthn::SignatureVerifier
|
61
|
-
.new(
|
59
|
+
.new(webauthn_public_key.alg, webauthn_public_key.pkey)
|
62
60
|
.verify(signature, authenticator_data_bytes + client_data.hash)
|
63
61
|
end
|
64
62
|
|
@@ -71,29 +69,6 @@ module WebAuthn
|
|
71
69
|
end
|
72
70
|
end
|
73
71
|
|
74
|
-
def credential_cose_key(public_key)
|
75
|
-
if WebAuthn::AttestationStatement::FidoU2f::PublicKey.uncompressed_point?(public_key)
|
76
|
-
# Gem version v1.11.0 and lower, used to behave so that Credential#public_key
|
77
|
-
# returned an EC P-256 uncompressed point.
|
78
|
-
#
|
79
|
-
# Because of https://github.com/cedarcode/webauthn-ruby/issues/137 this was changed
|
80
|
-
# and Credential#public_key started returning the unchanged COSE_Key formatted
|
81
|
-
# credentialPublicKey (as in https://www.w3.org/TR/webauthn/#credentialpublickey).
|
82
|
-
#
|
83
|
-
# Given that the credential public key is expected to be stored long-term by the gem
|
84
|
-
# user and later be passed as the public_key argument in the
|
85
|
-
# AuthenticatorAssertionResponse.verify call, we then need to support the two formats.
|
86
|
-
COSE::Key::EC2.new(
|
87
|
-
alg: COSE::Algorithm.by_name("ES256").id,
|
88
|
-
crv: 1,
|
89
|
-
x: public_key[1..32],
|
90
|
-
y: public_key[33..-1]
|
91
|
-
)
|
92
|
-
else
|
93
|
-
COSE::Key.deserialize(public_key)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
72
|
def type
|
98
73
|
WebAuthn::TYPES[:get]
|
99
74
|
end
|
@@ -12,6 +12,7 @@ require "webauthn/encoder"
|
|
12
12
|
|
13
13
|
module WebAuthn
|
14
14
|
class AttestationStatementVerificationError < VerificationError; end
|
15
|
+
class AttestationTrustworthinessVerificationError < VerificationError; end
|
15
16
|
class AttestedCredentialVerificationError < VerificationError; end
|
16
17
|
|
17
18
|
class AuthenticatorAttestationResponse < AuthenticatorResponse
|
@@ -27,7 +28,7 @@ module WebAuthn
|
|
27
28
|
attr_reader :attestation_type, :attestation_trust_path
|
28
29
|
|
29
30
|
def initialize(attestation_object:, **options)
|
30
|
-
super(options)
|
31
|
+
super(**options)
|
31
32
|
|
32
33
|
@attestation_object = attestation_object
|
33
34
|
end
|
@@ -36,7 +37,10 @@ module WebAuthn
|
|
36
37
|
super
|
37
38
|
|
38
39
|
verify_item(:attested_credential)
|
39
|
-
|
40
|
+
if WebAuthn.configuration.verify_attestation_statement
|
41
|
+
verify_item(:attestation_statement)
|
42
|
+
verify_item(:attestation_trustworthiness) if WebAuthn.configuration.attestation_root_certificates_finders.any?
|
43
|
+
end
|
40
44
|
|
41
45
|
true
|
42
46
|
end
|
@@ -90,6 +94,18 @@ module WebAuthn
|
|
90
94
|
@attestation_type, @attestation_trust_path = attestation_statement.valid?(authenticator_data, client_data.hash)
|
91
95
|
end
|
92
96
|
|
97
|
+
def valid_attestation_trustworthiness?
|
98
|
+
case @attestation_type
|
99
|
+
when WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE
|
100
|
+
WebAuthn.configuration.acceptable_attestation_types.include?('None')
|
101
|
+
when WebAuthn::AttestationStatement::ATTESTATION_TYPE_SELF
|
102
|
+
WebAuthn.configuration.acceptable_attestation_types.include?('Self')
|
103
|
+
else
|
104
|
+
WebAuthn.configuration.acceptable_attestation_types.include?(@attestation_type) &&
|
105
|
+
attestation_root_certificates_store.verify(leaf_certificate, signing_certificates)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
93
109
|
def raw_subject_key_identifier(certificate)
|
94
110
|
extension = certificate.extensions.detect { |ext| ext.oid == "subjectKeyIdentifier" }
|
95
111
|
return unless extension
|
@@ -98,5 +114,32 @@ module WebAuthn
|
|
98
114
|
ext_value = ext_asn1.value.last
|
99
115
|
OpenSSL::ASN1.decode(ext_value.value).value
|
100
116
|
end
|
117
|
+
|
118
|
+
def attestation_root_certificates_store
|
119
|
+
certificates =
|
120
|
+
WebAuthn.configuration.attestation_root_certificates_finders.reduce([]) do |certs, finder|
|
121
|
+
if certs.empty?
|
122
|
+
finder.find(attestation_format: attestation_format,
|
123
|
+
aaguid: aaguid,
|
124
|
+
attestation_certificate_key_id: attestation_certificate_key) || []
|
125
|
+
else
|
126
|
+
certs
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
OpenSSL::X509::Store.new.tap do |store|
|
131
|
+
certificates.each do |cert|
|
132
|
+
store.add_cert(cert)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def signing_certificates
|
138
|
+
@attestation_trust_path[1..-1]
|
139
|
+
end
|
140
|
+
|
141
|
+
def leaf_certificate
|
142
|
+
@attestation_trust_path.first
|
143
|
+
end
|
101
144
|
end
|
102
145
|
end
|
@@ -33,14 +33,20 @@ module WebAuthn
|
|
33
33
|
verify_item(:origin, expected_origin)
|
34
34
|
verify_item(:authenticator_data)
|
35
35
|
verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
|
36
|
-
|
37
|
-
|
36
|
+
|
37
|
+
if !WebAuthn.configuration.silent_authentication
|
38
|
+
verify_item(:user_presence)
|
39
|
+
end
|
40
|
+
|
41
|
+
if user_verification
|
42
|
+
verify_item(:user_verified)
|
43
|
+
end
|
38
44
|
|
39
45
|
true
|
40
46
|
end
|
41
47
|
|
42
|
-
def valid?(*args)
|
43
|
-
verify(*args)
|
48
|
+
def valid?(*args, **keyword_arguments)
|
49
|
+
verify(*args, **keyword_arguments)
|
44
50
|
rescue WebAuthn::VerificationError
|
45
51
|
false
|
46
52
|
end
|
@@ -91,12 +97,8 @@ module WebAuthn
|
|
91
97
|
authenticator_data.user_flagged?
|
92
98
|
end
|
93
99
|
|
94
|
-
def valid_user_verified?
|
95
|
-
|
96
|
-
authenticator_data.user_verified?
|
97
|
-
else
|
98
|
-
true
|
99
|
-
end
|
100
|
+
def valid_user_verified?
|
101
|
+
authenticator_data.user_verified?
|
100
102
|
end
|
101
103
|
|
102
104
|
def rp_id_from_origin(expected_origin)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "openssl"
|
4
4
|
require "webauthn/encoder"
|
5
|
+
require "webauthn/error"
|
5
6
|
|
6
7
|
module WebAuthn
|
7
8
|
def self.configuration
|
@@ -12,6 +13,8 @@ module WebAuthn
|
|
12
13
|
yield(configuration)
|
13
14
|
end
|
14
15
|
|
16
|
+
class RootCertificateFinderNotSupportedError < Error; end
|
17
|
+
|
15
18
|
class Configuration
|
16
19
|
def self.if_pss_supported(algorithm)
|
17
20
|
OpenSSL::PKey::RSA.instance_methods.include?(:verify_pss) ? algorithm : nil
|
@@ -26,12 +29,18 @@ module WebAuthn
|
|
26
29
|
attr_accessor :rp_name
|
27
30
|
attr_accessor :verify_attestation_statement
|
28
31
|
attr_accessor :credential_options_timeout
|
32
|
+
attr_accessor :silent_authentication
|
33
|
+
attr_accessor :acceptable_attestation_types
|
34
|
+
attr_reader :attestation_root_certificates_finders
|
29
35
|
|
30
36
|
def initialize
|
31
37
|
@algorithms = DEFAULT_ALGORITHMS.dup
|
32
38
|
@encoding = WebAuthn::Encoder::STANDARD_ENCODING
|
33
39
|
@verify_attestation_statement = true
|
34
40
|
@credential_options_timeout = 120000
|
41
|
+
@silent_authentication = false
|
42
|
+
@acceptable_attestation_types = ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA']
|
43
|
+
@attestation_root_certificates_finders = []
|
35
44
|
end
|
36
45
|
|
37
46
|
# This is the user-data encoder.
|
@@ -39,5 +48,19 @@ module WebAuthn
|
|
39
48
|
def encoder
|
40
49
|
@encoder ||= WebAuthn::Encoder.new(encoding)
|
41
50
|
end
|
51
|
+
|
52
|
+
def attestation_root_certificates_finders=(finders)
|
53
|
+
if !finders.respond_to?(:each)
|
54
|
+
finders = [finders]
|
55
|
+
end
|
56
|
+
|
57
|
+
finders.each do |finder|
|
58
|
+
unless finder.respond_to?(:find)
|
59
|
+
raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@attestation_root_certificates_finders = finders
|
64
|
+
end
|
42
65
|
end
|
43
66
|
end
|
data/lib/webauthn/credential.rb
CHANGED
@@ -7,12 +7,12 @@ require "webauthn/public_key_credential_with_attestation"
|
|
7
7
|
|
8
8
|
module WebAuthn
|
9
9
|
module Credential
|
10
|
-
def self.options_for_create(
|
11
|
-
WebAuthn::PublicKeyCredential::CreationOptions.new(
|
10
|
+
def self.options_for_create(**keyword_arguments)
|
11
|
+
WebAuthn::PublicKeyCredential::CreationOptions.new(**keyword_arguments)
|
12
12
|
end
|
13
13
|
|
14
|
-
def self.options_for_get(
|
15
|
-
WebAuthn::PublicKeyCredential::RequestOptions.new(
|
14
|
+
def self.options_for_get(**keyword_arguments)
|
15
|
+
WebAuthn::PublicKeyCredential::RequestOptions.new(**keyword_arguments)
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.from_create(credential)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webauthn/attestation_statement/fido_u2f/public_key"
|
4
|
+
require "cose/key"
|
5
|
+
require "cose/algorithm"
|
6
|
+
|
7
|
+
module WebAuthn
|
8
|
+
class PublicKey
|
9
|
+
def self.deserialize(public_key)
|
10
|
+
cose_key =
|
11
|
+
if WebAuthn::AttestationStatement::FidoU2f::PublicKey.uncompressed_point?(public_key)
|
12
|
+
# Gem version v1.11.0 and lower, used to behave so that Credential#public_key
|
13
|
+
# returned an EC P-256 uncompressed point.
|
14
|
+
#
|
15
|
+
# Because of https://github.com/cedarcode/webauthn-ruby/issues/137 this was changed
|
16
|
+
# and Credential#public_key started returning the unchanged COSE_Key formatted
|
17
|
+
# credentialPublicKey (as in https://www.w3.org/TR/webauthn/#credentialpublickey).
|
18
|
+
#
|
19
|
+
# Given that the credential public key is expected to be stored long-term by the gem
|
20
|
+
# user and later be passed as the public_key argument in the
|
21
|
+
# AuthenticatorAssertionResponse.verify call, we then need to support the two formats.
|
22
|
+
COSE::Key::EC2.new(
|
23
|
+
alg: COSE::Algorithm.by_name("ES256").id,
|
24
|
+
crv: 1,
|
25
|
+
x: public_key[1..32],
|
26
|
+
y: public_key[33..-1]
|
27
|
+
)
|
28
|
+
else
|
29
|
+
COSE::Key.deserialize(public_key)
|
30
|
+
end
|
31
|
+
|
32
|
+
new(cose_key: cose_key)
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :cose_key
|
36
|
+
|
37
|
+
def initialize(cose_key:)
|
38
|
+
@cose_key = cose_key
|
39
|
+
end
|
40
|
+
|
41
|
+
def pkey
|
42
|
+
@cose_key.to_pkey
|
43
|
+
end
|
44
|
+
|
45
|
+
def alg
|
46
|
+
@cose_key.alg
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -42,14 +42,14 @@ module WebAuthn
|
|
42
42
|
rp[:name] ||= configuration.rp_name
|
43
43
|
rp[:id] ||= configuration.rp_id
|
44
44
|
|
45
|
-
RPEntity.new(rp)
|
45
|
+
RPEntity.new(**rp)
|
46
46
|
else
|
47
47
|
rp
|
48
48
|
end
|
49
49
|
|
50
50
|
@user =
|
51
51
|
if user.is_a?(Hash)
|
52
|
-
UserEntity.new(user)
|
52
|
+
UserEntity.new(**user)
|
53
53
|
else
|
54
54
|
user
|
55
55
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "cose
|
4
|
-
require "cose/
|
3
|
+
require "cose"
|
4
|
+
require "cose/rsassa_algorithm"
|
5
5
|
require "openssl"
|
6
6
|
require "webauthn/error"
|
7
7
|
|
@@ -24,10 +24,10 @@ module WebAuthn
|
|
24
24
|
|
25
25
|
def verify(signature, verification_data, rsa_pss_salt_length: :digest)
|
26
26
|
if rsa_pss?
|
27
|
-
public_key.verify_pss(cose_algorithm.
|
28
|
-
salt_length: rsa_pss_salt_length, mgf1_hash: cose_algorithm.
|
27
|
+
public_key.verify_pss(cose_algorithm.hash_function, signature, verification_data,
|
28
|
+
salt_length: rsa_pss_salt_length, mgf1_hash: cose_algorithm.hash_function)
|
29
29
|
else
|
30
|
-
public_key.verify(cose_algorithm.
|
30
|
+
public_key.verify(cose_algorithm.hash_function, signature, verification_data)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -37,13 +37,25 @@ module WebAuthn
|
|
37
37
|
|
38
38
|
def cose_algorithm
|
39
39
|
case algorithm
|
40
|
-
when COSE::Algorithm
|
40
|
+
when COSE::Algorithm::Base
|
41
41
|
algorithm
|
42
42
|
else
|
43
43
|
COSE::Algorithm.find(algorithm)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
+
# This logic is a candidate to be moved to cose gem domain
|
48
|
+
def cose_key_type
|
49
|
+
case cose_algorithm
|
50
|
+
when COSE::Algorithm::ECDSA
|
51
|
+
COSE::Key::EC2::KTY_EC2
|
52
|
+
when COSE::Algorithm::RSAPSS, RSASSAAlgorithm
|
53
|
+
COSE::Key::RSA::KTY_RSA
|
54
|
+
else
|
55
|
+
raise UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
47
59
|
def rsa_pss?
|
48
60
|
cose_algorithm.name.start_with?("PS")
|
49
61
|
end
|
@@ -53,7 +65,7 @@ module WebAuthn
|
|
53
65
|
raise UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}"
|
54
66
|
elsif !supported_algorithms.include?(cose_algorithm.name)
|
55
67
|
raise UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}"
|
56
|
-
elsif !KTY_MAP[
|
68
|
+
elsif !KTY_MAP[cose_key_type].include?(public_key.class)
|
57
69
|
raise("Incompatible algorithm and key")
|
58
70
|
end
|
59
71
|
end
|
data/lib/webauthn/version.rb
CHANGED
data/webauthn.gemspec
CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency "awrence", "~> 1.1"
|
35
35
|
spec.add_dependency "bindata", "~> 2.4"
|
36
36
|
spec.add_dependency "cbor", "~> 0.5.9"
|
37
|
-
spec.add_dependency "cose", "~> 0.
|
37
|
+
spec.add_dependency "cose", "~> 0.10.0"
|
38
38
|
spec.add_dependency "jwt", [">= 1.5", "< 3.0"]
|
39
39
|
spec.add_dependency "openssl", "~> 2.0"
|
40
40
|
spec.add_dependency "securecompare", "~> 1.0"
|
@@ -45,4 +45,5 @@ Gem::Specification.new do |spec|
|
|
45
45
|
spec.add_development_dependency "rake", "~> 13.0"
|
46
46
|
spec.add_development_dependency "rspec", "~> 3.8"
|
47
47
|
spec.add_development_dependency "rubocop", "0.75.0"
|
48
|
+
spec.add_development_dependency "timecop", "~> 0.9.1"
|
48
49
|
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: 2.
|
4
|
+
version: 2.1.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-12-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: awrence
|
@@ -59,14 +59,14 @@ dependencies:
|
|
59
59
|
requirements:
|
60
60
|
- - "~>"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0.
|
62
|
+
version: 0.10.0
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - "~>"
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: 0.
|
69
|
+
version: 0.10.0
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: jwt
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -205,6 +205,20 @@ dependencies:
|
|
205
205
|
- - '='
|
206
206
|
- !ruby/object:Gem::Version
|
207
207
|
version: 0.75.0
|
208
|
+
- !ruby/object:Gem::Dependency
|
209
|
+
name: timecop
|
210
|
+
requirement: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - "~>"
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: 0.9.1
|
215
|
+
type: :development
|
216
|
+
prerelease: false
|
217
|
+
version_requirements: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - "~>"
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: 0.9.1
|
208
222
|
description: |-
|
209
223
|
WebAuthn ruby server library ― Make your application a W3C Web Authentication conformant
|
210
224
|
Relying Party and allow your users to authenticate with U2F and FIDO2 authenticators.
|
@@ -235,7 +249,7 @@ files:
|
|
235
249
|
- gemfiles/openssl_2_1.gemfile
|
236
250
|
- gemfiles/openssl_head.gemfile
|
237
251
|
- lib/android_safetynet/attestation_response.rb
|
238
|
-
- lib/cose/
|
252
|
+
- lib/cose/rsassa_algorithm.rb
|
239
253
|
- lib/tpm/constants.rb
|
240
254
|
- lib/tpm/s_attest.rb
|
241
255
|
- lib/tpm/s_attest/s_certify_info.rb
|
@@ -277,6 +291,7 @@ files:
|
|
277
291
|
- lib/webauthn/fake_authenticator/attestation_object.rb
|
278
292
|
- lib/webauthn/fake_authenticator/authenticator_data.rb
|
279
293
|
- lib/webauthn/fake_client.rb
|
294
|
+
- lib/webauthn/public_key.rb
|
280
295
|
- lib/webauthn/public_key_credential.rb
|
281
296
|
- lib/webauthn/public_key_credential/creation_options.rb
|
282
297
|
- lib/webauthn/public_key_credential/entity.rb
|
@@ -313,7 +328,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
313
328
|
- !ruby/object:Gem::Version
|
314
329
|
version: '0'
|
315
330
|
requirements: []
|
316
|
-
rubygems_version: 3.
|
331
|
+
rubygems_version: 3.1.2
|
317
332
|
signing_key:
|
318
333
|
specification_version: 4
|
319
334
|
summary: WebAuthn ruby server library
|
data/lib/cose/algorithm.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "cose/key"
|
4
|
-
|
5
|
-
# TODO: Move this to cose gem
|
6
|
-
module COSE
|
7
|
-
# https://tools.ietf.org/html/rfc8152#section-8.1
|
8
|
-
Algorithm = Struct.new(:id, :name, :hash, :kty, :key_curve) do
|
9
|
-
@registered = {}
|
10
|
-
|
11
|
-
def self.register(id, name, hash, kty, key_curve = nil)
|
12
|
-
@registered[id] = COSE::Algorithm.new(id, name, hash, kty, key_curve)
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.find(id)
|
16
|
-
@registered[id]
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.by_name(name)
|
20
|
-
@registered.values.detect { |algorithm| algorithm.name == name }
|
21
|
-
end
|
22
|
-
|
23
|
-
def value
|
24
|
-
id
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
COSE::Algorithm.register(-7, "ES256", "SHA256", COSE::Key::EC2::KTY_EC2, "prime256v1")
|
30
|
-
COSE::Algorithm.register(-35, "ES384", "SHA384", COSE::Key::EC2::KTY_EC2, "prime256v1")
|
31
|
-
COSE::Algorithm.register(-36, "ES512", "SHA512", COSE::Key::EC2::KTY_EC2, "prime256v1")
|
32
|
-
COSE::Algorithm.register(-37, "PS256", "SHA256", COSE::Key::RSA::KTY_RSA)
|
33
|
-
COSE::Algorithm.register(-38, "PS384", "SHA384", COSE::Key::RSA::KTY_RSA)
|
34
|
-
COSE::Algorithm.register(-39, "PS512", "SHA512", COSE::Key::RSA::KTY_RSA)
|
35
|
-
COSE::Algorithm.register(-257, "RS256", "SHA256", COSE::Key::RSA::KTY_RSA)
|
36
|
-
COSE::Algorithm.register(-258, "RS384", "SHA384", COSE::Key::RSA::KTY_RSA)
|
37
|
-
COSE::Algorithm.register(-259, "RS512", "SHA512", COSE::Key::RSA::KTY_RSA)
|
38
|
-
COSE::Algorithm.register(-65535, "RS1", "SHA1", COSE::Key::RSA::KTY_RSA)
|