webauthn 2.5.1 → 3.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -56
- data/.travis.yml +39 -0
- data/Appraisals +21 -0
- data/CHANGELOG.md +0 -43
- data/README.md +2 -5
- data/SECURITY.md +3 -6
- data/docs/u2f_migration.md +2 -3
- data/gemfiles/cose_head.gemfile +7 -0
- data/gemfiles/openssl_2_0.gemfile +7 -0
- data/gemfiles/openssl_2_1.gemfile +7 -0
- data/gemfiles/openssl_2_2.gemfile +7 -0
- data/gemfiles/openssl_head.gemfile +7 -0
- data/lib/cose/rsapkcs1_algorithm.rb +0 -7
- data/lib/webauthn/attestation_object.rb +9 -5
- data/lib/webauthn/attestation_statement/android_key.rb +4 -0
- data/lib/webauthn/attestation_statement/android_safetynet.rb +5 -1
- data/lib/webauthn/attestation_statement/base.rb +29 -21
- data/lib/webauthn/attestation_statement/none.rb +1 -7
- data/lib/webauthn/attestation_statement/packed.rb +1 -1
- data/lib/webauthn/attestation_statement.rb +3 -6
- data/lib/webauthn/authenticator_assertion_response.rb +4 -3
- data/lib/webauthn/authenticator_attestation_response.rb +10 -7
- data/lib/webauthn/authenticator_data/attested_credential_data.rb +10 -4
- data/lib/webauthn/authenticator_response.rb +8 -6
- data/lib/webauthn/configuration.rb +36 -38
- data/lib/webauthn/credential.rb +5 -4
- data/lib/webauthn/credential_creation_options.rb +0 -2
- data/lib/webauthn/credential_request_options.rb +0 -2
- data/lib/webauthn/fake_authenticator.rb +2 -10
- data/lib/webauthn/fake_client.rb +5 -12
- data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
- data/lib/webauthn/public_key_credential/entity.rb +4 -3
- data/lib/webauthn/public_key_credential/options.rb +6 -9
- data/lib/webauthn/public_key_credential/request_options.rb +1 -1
- data/lib/webauthn/public_key_credential.rb +15 -8
- data/lib/webauthn/relying_party.rb +117 -0
- data/lib/webauthn/security_utils.rb +20 -0
- data/lib/webauthn/version.rb +1 -1
- data/script/ci/install-openssl +7 -0
- data/script/ci/install-ruby +13 -0
- data/webauthn.gemspec +7 -6
- metadata +56 -35
- data/.github/workflows/build.yml +0 -32
- data/.github/workflows/git.yml +0 -21
- data/docs/advanced_configuration.md +0 -174
- data/lib/webauthn/attestation_statement/apple.rb +0 -65
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f418eb52a085d8e7c03bd1c3d11b88ed8fe467f4ec01d3178836689d470f436
|
4
|
+
data.tar.gz: 4ab67e8804cbd7d785e29b94760af6db9d6b1e52de1a58bafc74aed19b5b7e21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccdcd22e494079eb67c122c03e3061166f91de50dd0c2bf8662748c976483a9d472fa9f8d6b67db9abf484e8eac182195b5f1e1cca8aaaf88146d831919f1c93
|
7
|
+
data.tar.gz: b2506b530c796ee57e5d037e7dd3692b1e359408fd9757141b6c4f042c43d57344baa09dc63d17ace549927eeea50abaa8c0771a0ab6da0ece5d880362d890db
|
data/.rubocop.yml
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require:
|
2
2
|
- rubocop-rspec
|
3
|
-
- rubocop-rake
|
4
3
|
|
5
4
|
inherit_mode:
|
6
5
|
merge:
|
@@ -9,7 +8,6 @@ inherit_mode:
|
|
9
8
|
AllCops:
|
10
9
|
TargetRubyVersion: 2.4
|
11
10
|
DisabledByDefault: true
|
12
|
-
NewCops: disable
|
13
11
|
Exclude:
|
14
12
|
- "gemfiles/**/*"
|
15
13
|
- "vendor/**/*"
|
@@ -26,12 +24,6 @@ Layout:
|
|
26
24
|
Layout/ClassStructure:
|
27
25
|
Enabled: true
|
28
26
|
|
29
|
-
Layout/EmptyLineBetweenDefs:
|
30
|
-
AllowAdjacentOneLineDefs: true
|
31
|
-
|
32
|
-
Layout/EmptyLinesAroundAttributeAccessor:
|
33
|
-
Enabled: true
|
34
|
-
|
35
27
|
Layout/FirstMethodArgumentLineBreak:
|
36
28
|
Enabled: true
|
37
29
|
|
@@ -46,60 +38,12 @@ Layout/MultilineAssignmentLayout:
|
|
46
38
|
Layout/MultilineMethodArgumentLineBreaks:
|
47
39
|
Enabled: true
|
48
40
|
|
49
|
-
Layout/SpaceAroundMethodCallOperator:
|
50
|
-
Enabled: true
|
51
|
-
|
52
41
|
Lint:
|
53
42
|
Enabled: true
|
54
43
|
|
55
|
-
Lint/DeprecatedOpenSSLConstant:
|
56
|
-
Enabled: true
|
57
|
-
|
58
|
-
Lint/MixedRegexpCaptureTypes:
|
59
|
-
Enabled: true
|
60
|
-
|
61
|
-
Lint/RaiseException:
|
62
|
-
Enabled: true
|
63
|
-
|
64
|
-
Lint/StructNewOverride:
|
65
|
-
Enabled: true
|
66
|
-
|
67
|
-
Lint/BinaryOperatorWithIdenticalOperands:
|
68
|
-
Enabled: true
|
69
|
-
|
70
|
-
Lint/DuplicateElsifCondition:
|
71
|
-
Enabled: true
|
72
|
-
|
73
|
-
Lint/DuplicateRescueException:
|
74
|
-
Enabled: true
|
75
|
-
|
76
|
-
Lint/EmptyConditionalBody:
|
77
|
-
Enabled: true
|
78
|
-
|
79
|
-
Lint/FloatComparison:
|
80
|
-
Enabled: true
|
81
|
-
|
82
|
-
Lint/MissingSuper:
|
83
|
-
Enabled: true
|
84
|
-
|
85
|
-
Lint/OutOfRangeRegexpRef:
|
86
|
-
Enabled: true
|
87
|
-
|
88
|
-
Lint/SelfAssignment:
|
89
|
-
Enabled: true
|
90
|
-
|
91
|
-
Lint/TopLevelReturnWithArgument:
|
92
|
-
Enabled: true
|
93
|
-
|
94
|
-
Lint/UnreachableLoop:
|
95
|
-
Enabled: true
|
96
|
-
|
97
44
|
Naming:
|
98
45
|
Enabled: true
|
99
46
|
|
100
|
-
Naming/VariableNumber:
|
101
|
-
Enabled: false
|
102
|
-
|
103
47
|
RSpec/Be:
|
104
48
|
Enabled: true
|
105
49
|
|
data/.travis.yml
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
dist: bionic
|
2
|
+
language: ruby
|
3
|
+
|
4
|
+
cache:
|
5
|
+
bundler: true
|
6
|
+
directories:
|
7
|
+
- /home/travis/.rvm/
|
8
|
+
|
9
|
+
env:
|
10
|
+
- LIBSSL=1.1 RB=2.7.1
|
11
|
+
- LIBSSL=1.1 RB=2.6.6
|
12
|
+
- LIBSSL=1.1 RB=2.5.8
|
13
|
+
- LIBSSL=1.1 RB=2.4.10
|
14
|
+
- LIBSSL=1.1 RB=ruby-head
|
15
|
+
- LIBSSL=1.0 RB=2.7.1
|
16
|
+
- LIBSSL=1.0 RB=2.6.6
|
17
|
+
- LIBSSL=1.0 RB=2.5.8
|
18
|
+
- LIBSSL=1.0 RB=2.4.10
|
19
|
+
- LIBSSL=1.0 RB=ruby-head
|
20
|
+
|
21
|
+
gemfile:
|
22
|
+
- gemfiles/cose_head.gemfile
|
23
|
+
- gemfiles/openssl_head.gemfile
|
24
|
+
- gemfiles/openssl_2_2.gemfile
|
25
|
+
- gemfiles/openssl_2_1.gemfile
|
26
|
+
- gemfiles/openssl_2_0.gemfile
|
27
|
+
|
28
|
+
matrix:
|
29
|
+
fast_finish: true
|
30
|
+
allow_failures:
|
31
|
+
- env: LIBSSL=1.1 RB=ruby-head
|
32
|
+
- env: LIBSSL=1.0 RB=ruby-head
|
33
|
+
- gemfile: gemfiles/cose_head.gemfile
|
34
|
+
- gemfile: gemfiles/openssl_head.gemfile
|
35
|
+
|
36
|
+
before_install:
|
37
|
+
- ./script/ci/install-openssl
|
38
|
+
- ./script/ci/install-ruby
|
39
|
+
- gem install bundler -v "~> 2.0"
|
data/Appraisals
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
appraise "cose_head" do
|
4
|
+
gem "cose", git: "https://github.com/cedarcode/cose-ruby"
|
5
|
+
end
|
6
|
+
|
7
|
+
appraise "openssl_head" do
|
8
|
+
gem "openssl", git: "https://github.com/ruby/openssl"
|
9
|
+
end
|
10
|
+
|
11
|
+
appraise "openssl_2_2" do
|
12
|
+
gem "openssl", "~> 2.2.0"
|
13
|
+
end
|
14
|
+
|
15
|
+
appraise "openssl_2_1" do
|
16
|
+
gem "openssl", "~> 2.1.0"
|
17
|
+
end
|
18
|
+
|
19
|
+
appraise "openssl_2_0" do
|
20
|
+
gem "openssl", "~> 2.0.0"
|
21
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -6,40 +6,6 @@
|
|
6
6
|
|
7
7
|
- Ability to define multiple relying parties with the introduction of the `WebAuthn::RelyingParty` class ([@padulafacundo], [@brauliomartinezlm])
|
8
8
|
|
9
|
-
## [v2.5.1] - 2022-03-20
|
10
|
-
|
11
|
-
### Added
|
12
|
-
|
13
|
-
- Updated openssl support to be ~>2.2 [@bdewater]
|
14
|
-
|
15
|
-
### Removed
|
16
|
-
|
17
|
-
- Removed dependency [secure_compare dependency] (https://rubygems.org/gems/secure_compare/versions/0.0.1) and use OpenSSL#secure_compare instead [@bdewater]
|
18
|
-
|
19
|
-
## [v2.5.0] - 2021-03-14
|
20
|
-
|
21
|
-
### Added
|
22
|
-
|
23
|
-
- Support 'apple' attestation statement format ([#343](https://github.com/cedarcode/webauthn-ruby/pull/343) / [@juanarias93], [@santiagorodriguez96])
|
24
|
-
- Allow specifying an array of ids as `allow_credentials:` for `FakeClient#get` method ([#335](https://github.com/cedarcode/webauthn-ruby/pull/335) / [@kingjan1999])
|
25
|
-
|
26
|
-
### Removed
|
27
|
-
|
28
|
-
- No longer accept "removed from the WebAuthn spec" options `rp: { icon: }` and `user: { icon: }` for `WebAuthn::Credential.options_for_create` method ([#326](https://github.com/cedarcode/webauthn-ruby/pull/326) / [@santiagorodriguez96])
|
29
|
-
|
30
|
-
## [v2.4.1] - 2021-02-15
|
31
|
-
|
32
|
-
### Fixed
|
33
|
-
|
34
|
-
- Fix verification of new credential if no attestation provided and 'None' type is not among configured `acceptable_attestation_types`. I.e. reject it instead of letting it go through.
|
35
|
-
|
36
|
-
## [v2.4.0] - 2020-09-03
|
37
|
-
|
38
|
-
### Added
|
39
|
-
|
40
|
-
- Support for ES256K credentials
|
41
|
-
- `FakeClient#get` accepts `user_handle:` keyword argument ([@lgarron])
|
42
|
-
|
43
9
|
## [v2.3.0] - 2020-06-27
|
44
10
|
|
45
11
|
### Added
|
@@ -335,10 +301,6 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
335
301
|
- Works with ruby 2.5
|
336
302
|
|
337
303
|
[v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha1/
|
338
|
-
[v2.5.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.0...v2.5.1/
|
339
|
-
[v2.5.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.1...v2.5.0/
|
340
|
-
[v2.4.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.0...v2.4.1/
|
341
|
-
[v2.4.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.3.0...v2.4.0/
|
342
304
|
[v2.3.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.2.1...v2.3.0/
|
343
305
|
[v2.2.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.2.0...v2.2.1/
|
344
306
|
[v2.2.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.1.0...v2.2.0/
|
@@ -375,8 +337,3 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
375
337
|
[@ssuttner]: https://github.com/ssuttner
|
376
338
|
[@padulafacundo]: https://github.com/padulafacundo
|
377
339
|
[@santiagorodriguez96]: https://github.com/santiagorodriguez96
|
378
|
-
[@lgarron]: https://github.com/lgarron
|
379
|
-
[@juanarias93]: https://github.com/juanarias93
|
380
|
-
[@kingjan1999]: https://github.com/@kingjan1999
|
381
|
-
[@jdongelmans]: https://github.com/jdongelmans
|
382
|
-
[@petergoldstein]: https://github.com/petergoldstein
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ For the current release version see https://github.com/cedarcode/webauthn-ruby/b
|
|
6
6
|
![banner](assets/webauthn-ruby.png)
|
7
7
|
|
8
8
|
[![Gem](https://img.shields.io/gem/v/webauthn.svg?style=flat-square)](https://rubygems.org/gems/webauthn)
|
9
|
-
[![Travis](https://img.shields.io/travis/cedarcode/webauthn-ruby/master.svg?style=flat-square)](https://travis-ci.
|
9
|
+
[![Travis](https://img.shields.io/travis/cedarcode/webauthn-ruby/master.svg?style=flat-square)](https://travis-ci.org/cedarcode/webauthn-ruby)
|
10
10
|
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-informational.svg?style=flat-square)](https://conventionalcommits.org)
|
11
11
|
[![Join the chat at https://gitter.im/cedarcode/webauthn-ruby](https://badges.gitter.im/cedarcode/webauthn-ruby.svg)](https://gitter.im/cedarcode/webauthn-ruby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
12
12
|
|
@@ -96,8 +96,6 @@ If you are migrating an existing application from the legacy FIDO U2F JavaScript
|
|
96
96
|
|
97
97
|
### Configuration
|
98
98
|
|
99
|
-
If you have a multi-tenant application or just need to configure WebAuthn differently for separate parts of your application (e.g. if your users authenticate to different subdomains in the same application), we strongly recommend you look at this [Advanced Configuration](docs/advanced_configuration.md) section instead of this.
|
100
|
-
|
101
99
|
For a Rails application this would go in `config/initializers/webauthn.rb`.
|
102
100
|
|
103
101
|
```ruby
|
@@ -410,7 +408,7 @@ credential.authenticator_extension_outputs
|
|
410
408
|
|
411
409
|
## Attestation
|
412
410
|
|
413
|
-
### Attestation Statement
|
411
|
+
### Attestation Statement Format
|
414
412
|
|
415
413
|
| Attestation Statement Format | Supported? |
|
416
414
|
| -------- | :--------: |
|
@@ -419,7 +417,6 @@ credential.authenticator_extension_outputs
|
|
419
417
|
| tpm (x5c attestation) | Yes |
|
420
418
|
| android-key | Yes |
|
421
419
|
| android-safetynet | Yes |
|
422
|
-
| apple | Yes |
|
423
420
|
| fido-u2f | Yes |
|
424
421
|
| none | Yes |
|
425
422
|
|
data/SECURITY.md
CHANGED
@@ -4,12 +4,9 @@
|
|
4
4
|
|
5
5
|
| Version | Supported |
|
6
6
|
| ------- | ------------------ |
|
7
|
-
| 2.
|
8
|
-
| 2.
|
9
|
-
| 2.
|
10
|
-
| 2.2.z | :x: |
|
11
|
-
| 2.1.z | :x: |
|
12
|
-
| 2.0.z | :x: |
|
7
|
+
| 2.2.z | :white_check_mark: |
|
8
|
+
| 2.1.z | :white_check_mark: |
|
9
|
+
| 2.0.z | :white_check_mark: |
|
13
10
|
| 1.18.z | :white_check_mark: |
|
14
11
|
| < 1.18 | :x: |
|
15
12
|
|
data/docs/u2f_migration.md
CHANGED
@@ -82,7 +82,7 @@ During authentication verification phase, you must pass either the original AppI
|
|
82
82
|
|
83
83
|
```ruby
|
84
84
|
assertion_response = WebAuthn::AuthenticatorAssertionResponse.new(
|
85
|
-
|
85
|
+
credential_id: params[:id],
|
86
86
|
authenticator_data: params[:response][:authenticatorData],
|
87
87
|
client_data_json: params[:response][:clientDataJSON],
|
88
88
|
signature: params[:response][:signature],
|
@@ -90,8 +90,7 @@ assertion_response = WebAuthn::AuthenticatorAssertionResponse.new(
|
|
90
90
|
|
91
91
|
assertion_response.verify(
|
92
92
|
expected_challenge,
|
93
|
-
|
94
|
-
sign_count: credential.count,
|
93
|
+
allowed_credentials: [credential],
|
95
94
|
rp_id: params[:clientExtensionResults][:appid] ? domain.to_s : domain.host,
|
96
95
|
)
|
97
96
|
```
|
@@ -40,11 +40,4 @@ end
|
|
40
40
|
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-257, "RS256", hash_function: "SHA256"))
|
41
41
|
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-258, "RS384", hash_function: "SHA384"))
|
42
42
|
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-259, "RS512", hash_function: "SHA512"))
|
43
|
-
|
44
|
-
# Patch openssl-signature_algorithm gem to support discouraged/deprecated RSA-PKCS#1 with SHA-1
|
45
|
-
# (RS1 in JOSE/COSE terminology) algorithm needed for WebAuthn.
|
46
|
-
OpenSSL::SignatureAlgorithm::RSAPKCS1.const_set(
|
47
|
-
:ACCEPTED_HASH_FUNCTIONS,
|
48
|
-
OpenSSL::SignatureAlgorithm::RSAPKCS1::ACCEPTED_HASH_FUNCTIONS + ["SHA1"]
|
49
|
-
)
|
50
43
|
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-65535, "RS1", hash_function: "SHA1"))
|
@@ -10,18 +10,22 @@ module WebAuthn
|
|
10
10
|
class AttestationObject
|
11
11
|
extend Forwardable
|
12
12
|
|
13
|
-
def self.deserialize(attestation_object)
|
14
|
-
from_map(CBOR.decode(attestation_object))
|
13
|
+
def self.deserialize(attestation_object, relying_party)
|
14
|
+
from_map(CBOR.decode(attestation_object), relying_party)
|
15
15
|
end
|
16
16
|
|
17
|
-
def self.from_map(map)
|
17
|
+
def self.from_map(map, relying_party)
|
18
18
|
new(
|
19
19
|
authenticator_data: WebAuthn::AuthenticatorData.deserialize(map["authData"]),
|
20
|
-
attestation_statement: WebAuthn::AttestationStatement.from(
|
20
|
+
attestation_statement: WebAuthn::AttestationStatement.from(
|
21
|
+
map["fmt"],
|
22
|
+
map["attStmt"],
|
23
|
+
relying_party: relying_party
|
24
|
+
)
|
21
25
|
)
|
22
26
|
end
|
23
27
|
|
24
|
-
attr_reader :authenticator_data, :attestation_statement
|
28
|
+
attr_reader :authenticator_data, :attestation_statement, :relying_party
|
25
29
|
|
26
30
|
def initialize(authenticator_data:, attestation_statement:)
|
27
31
|
@authenticator_data = authenticator_data
|
@@ -20,6 +20,10 @@ module WebAuthn
|
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
+
def matching_public_key?(authenticator_data)
|
24
|
+
attestation_certificate.public_key.to_der == authenticator_data.credential.public_key_object.to_der
|
25
|
+
end
|
26
|
+
|
23
27
|
def valid_attestation_challenge?(client_data_hash)
|
24
28
|
android_key_attestation.verify_challenge(client_data_hash)
|
25
29
|
rescue AndroidKeyAttestation::ChallengeMismatchError
|
@@ -16,6 +16,10 @@ module WebAuthn
|
|
16
16
|
[attestation_type, attestation_trust_path]
|
17
17
|
end
|
18
18
|
|
19
|
+
def attestation_certificate
|
20
|
+
attestation_trust_path.first
|
21
|
+
end
|
22
|
+
|
19
23
|
private
|
20
24
|
|
21
25
|
def valid_response?(authenticator_data, client_data_hash)
|
@@ -48,7 +52,7 @@ module WebAuthn
|
|
48
52
|
end
|
49
53
|
|
50
54
|
# SafetyNetAttestation returns full chain including root, WebAuthn expects only the x5c certificates
|
51
|
-
def
|
55
|
+
def attestation_trust_path
|
52
56
|
attestation_response.certificate_chain[0..-2]
|
53
57
|
end
|
54
58
|
|
@@ -16,20 +16,19 @@ module WebAuthn
|
|
16
16
|
ATTESTATION_TYPE_SELF = "Self"
|
17
17
|
ATTESTATION_TYPE_ATTCA = "AttCA"
|
18
18
|
ATTESTATION_TYPE_BASIC_OR_ATTCA = "Basic_or_AttCA"
|
19
|
-
ATTESTATION_TYPE_ANONCA = "AnonCA"
|
20
19
|
|
21
20
|
ATTESTATION_TYPES_WITH_ROOT = [
|
22
21
|
ATTESTATION_TYPE_BASIC,
|
23
22
|
ATTESTATION_TYPE_BASIC_OR_ATTCA,
|
24
|
-
ATTESTATION_TYPE_ATTCA
|
25
|
-
ATTESTATION_TYPE_ANONCA
|
23
|
+
ATTESTATION_TYPE_ATTCA
|
26
24
|
].freeze
|
27
25
|
|
28
26
|
class Base
|
29
27
|
AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
|
30
28
|
|
31
|
-
def initialize(statement)
|
29
|
+
def initialize(statement, relying_party = WebAuthn.configuration.relying_party)
|
32
30
|
@statement = statement
|
31
|
+
@relying_party = relying_party
|
33
32
|
end
|
34
33
|
|
35
34
|
def valid?(_authenticator_data, _client_data_hash)
|
@@ -44,28 +43,32 @@ module WebAuthn
|
|
44
43
|
certificates&.first
|
45
44
|
end
|
46
45
|
|
46
|
+
def certificate_chain
|
47
|
+
if certificates
|
48
|
+
certificates[1..-1]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
47
52
|
def attestation_certificate_key_id
|
48
|
-
|
53
|
+
raw_subject_key_identifier&.unpack("H*")&.[](0)
|
49
54
|
end
|
50
55
|
|
51
56
|
private
|
52
57
|
|
53
|
-
attr_reader :statement
|
58
|
+
attr_reader :statement, :relying_party
|
54
59
|
|
55
60
|
def matching_aaguid?(attested_credential_data_aaguid)
|
56
|
-
extension = attestation_certificate&.
|
61
|
+
extension = attestation_certificate&.extensions&.detect { |ext| ext.oid == AAGUID_EXTENSION_OID }
|
57
62
|
if extension
|
58
|
-
|
59
|
-
|
63
|
+
# `extension.value` mangles data into ASCII, so we must manually compare bytes
|
64
|
+
# see https://github.com/ruby/openssl/pull/234
|
65
|
+
extension.to_der[-WebAuthn::AuthenticatorData::AttestedCredentialData::AAGUID_LENGTH..-1] ==
|
66
|
+
attested_credential_data_aaguid
|
60
67
|
else
|
61
68
|
true
|
62
69
|
end
|
63
70
|
end
|
64
71
|
|
65
|
-
def matching_public_key?(authenticator_data)
|
66
|
-
attestation_certificate.public_key.to_der == authenticator_data.credential.public_key_object.to_der
|
67
|
-
end
|
68
|
-
|
69
72
|
def certificates
|
70
73
|
@certificates ||=
|
71
74
|
raw_certificates&.map do |raw_certificate|
|
@@ -93,10 +96,10 @@ module WebAuthn
|
|
93
96
|
|
94
97
|
def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
|
95
98
|
if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
|
96
|
-
|
99
|
+
relying_party.acceptable_attestation_types.include?(attestation_type) &&
|
97
100
|
valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
|
98
101
|
else
|
99
|
-
|
102
|
+
relying_party.acceptable_attestation_types.include?(attestation_type)
|
100
103
|
end
|
101
104
|
end
|
102
105
|
|
@@ -120,7 +123,7 @@ module WebAuthn
|
|
120
123
|
|
121
124
|
def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
|
122
125
|
root_certificates =
|
123
|
-
|
126
|
+
relying_party.attestation_root_certificates_finders.reduce([]) do |certs, finder|
|
124
127
|
if certs.empty?
|
125
128
|
finder.find(
|
126
129
|
attestation_format: format,
|
@@ -139,6 +142,15 @@ module WebAuthn
|
|
139
142
|
end
|
140
143
|
end
|
141
144
|
|
145
|
+
def raw_subject_key_identifier
|
146
|
+
extension = attestation_certificate.extensions.detect { |ext| ext.oid == "subjectKeyIdentifier" }
|
147
|
+
return unless extension
|
148
|
+
|
149
|
+
ext_asn1 = OpenSSL::ASN1.decode(extension.to_der)
|
150
|
+
ext_value = ext_asn1.value.last
|
151
|
+
OpenSSL::ASN1.decode(ext_value.value).value
|
152
|
+
end
|
153
|
+
|
142
154
|
def valid_signature?(authenticator_data, client_data_hash, public_key = attestation_certificate.public_key)
|
143
155
|
raise("Incompatible algorithm and key") unless cose_algorithm.compatible_key?(public_key)
|
144
156
|
|
@@ -158,14 +170,10 @@ module WebAuthn
|
|
158
170
|
def cose_algorithm
|
159
171
|
@cose_algorithm ||=
|
160
172
|
COSE::Algorithm.find(algorithm).tap do |alg|
|
161
|
-
alg &&
|
173
|
+
alg && relying_party.algorithms.include?(alg.name) ||
|
162
174
|
raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
|
163
175
|
end
|
164
176
|
end
|
165
|
-
|
166
|
-
def configuration
|
167
|
-
WebAuthn.configuration
|
168
|
-
end
|
169
177
|
end
|
170
178
|
end
|
171
179
|
end
|
@@ -6,18 +6,12 @@ module WebAuthn
|
|
6
6
|
module AttestationStatement
|
7
7
|
class None < Base
|
8
8
|
def valid?(*_args)
|
9
|
-
if statement == {}
|
9
|
+
if statement == {}
|
10
10
|
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE, nil]
|
11
11
|
else
|
12
12
|
false
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def attestation_type
|
19
|
-
WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE
|
20
|
-
end
|
21
15
|
end
|
22
16
|
end
|
23
17
|
end
|
@@ -46,7 +46,7 @@ module WebAuthn
|
|
46
46
|
|
47
47
|
attestation_certificate.version == 2 &&
|
48
48
|
subject.assoc('OU')&.at(1) == "Authenticator Attestation" &&
|
49
|
-
attestation_certificate.
|
49
|
+
attestation_certificate.extensions.find { |ext| ext.oid == 'basicConstraints' }&.value == 'CA:FALSE'
|
50
50
|
else
|
51
51
|
true
|
52
52
|
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require "webauthn/attestation_statement/android_key"
|
4
4
|
require "webauthn/attestation_statement/android_safetynet"
|
5
|
-
require "webauthn/attestation_statement/apple"
|
6
5
|
require "webauthn/attestation_statement/fido_u2f"
|
7
6
|
require "webauthn/attestation_statement/none"
|
8
7
|
require "webauthn/attestation_statement/packed"
|
@@ -19,7 +18,6 @@ module WebAuthn
|
|
19
18
|
ATTESTATION_FORMAT_ANDROID_SAFETYNET = "android-safetynet"
|
20
19
|
ATTESTATION_FORMAT_ANDROID_KEY = "android-key"
|
21
20
|
ATTESTATION_FORMAT_TPM = "tpm"
|
22
|
-
ATTESTATION_FORMAT_APPLE = "apple"
|
23
21
|
|
24
22
|
FORMAT_TO_CLASS = {
|
25
23
|
ATTESTATION_FORMAT_NONE => WebAuthn::AttestationStatement::None,
|
@@ -27,15 +25,14 @@ module WebAuthn
|
|
27
25
|
ATTESTATION_FORMAT_PACKED => WebAuthn::AttestationStatement::Packed,
|
28
26
|
ATTESTATION_FORMAT_ANDROID_SAFETYNET => WebAuthn::AttestationStatement::AndroidSafetynet,
|
29
27
|
ATTESTATION_FORMAT_ANDROID_KEY => WebAuthn::AttestationStatement::AndroidKey,
|
30
|
-
ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM
|
31
|
-
ATTESTATION_FORMAT_APPLE => WebAuthn::AttestationStatement::Apple
|
28
|
+
ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM
|
32
29
|
}.freeze
|
33
30
|
|
34
|
-
def self.from(format, statement)
|
31
|
+
def self.from(format, statement, relying_party: WebAuthn.configuration.relying_party)
|
35
32
|
klass = FORMAT_TO_CLASS[format]
|
36
33
|
|
37
34
|
if klass
|
38
|
-
klass.new(statement)
|
35
|
+
klass.new(statement, relying_party)
|
39
36
|
else
|
40
37
|
raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
|
41
38
|
end
|
@@ -10,8 +10,8 @@ module WebAuthn
|
|
10
10
|
class SignCountVerificationError < VerificationError; end
|
11
11
|
|
12
12
|
class AuthenticatorAssertionResponse < AuthenticatorResponse
|
13
|
-
def self.from_client(response)
|
14
|
-
encoder =
|
13
|
+
def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
|
14
|
+
encoder = relying_party.encoder
|
15
15
|
|
16
16
|
user_handle =
|
17
17
|
if response["userHandle"]
|
@@ -22,7 +22,8 @@ module WebAuthn
|
|
22
22
|
authenticator_data: encoder.decode(response["authenticatorData"]),
|
23
23
|
client_data_json: encoder.decode(response["clientDataJSON"]),
|
24
24
|
signature: encoder.decode(response["signature"]),
|
25
|
-
user_handle: user_handle
|
25
|
+
user_handle: user_handle,
|
26
|
+
relying_party: relying_party
|
26
27
|
)
|
27
28
|
end
|
28
29
|
|