webauthn 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/CHANGELOG.md +11 -0
- data/README.md +4 -3
- data/lib/webauthn/attestation_statement.rb +4 -0
- data/lib/webauthn/attestation_statement/android_key.rb +102 -0
- data/lib/webauthn/attestation_statement/base.rb +2 -0
- data/lib/webauthn/attestation_statement/packed.rb +13 -0
- data/lib/webauthn/authenticator_data/attested_credential_data.rb +6 -2
- data/lib/webauthn/authenticator_data/attested_credential_data/{public_key_u2f.rb → public_key.rb} +2 -2
- data/lib/webauthn/version.rb +1 -1
- data/webauthn.gemspec +2 -2
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 368f1cffa77727f2edab0c872a6e9414af8db2871edfbb3623f06273bb71f5ae
|
4
|
+
data.tar.gz: 4b21d7e5cd9ff589bb97f4264d0dbf7f3f04da53aa619893f88a1d831d7c2755
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c88fb415b6141de835c2d1536f8e859b1ba1a146b1e9c13971cf8bb07478bda5efa9c6a85f69c06466b91984b7f00e14f0cf29229cf14bc10f70040659411586
|
7
|
+
data.tar.gz: f94c85b184e96399f71bc7c070b691160640a5de898e381cd23f4bddc2fce139a2c523172cc8aeb682715b66a038f1ad3ed7798893a645d31eef53268784dc08
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.11.0] - 2019-03-15
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- `WebAuthn::AuthenticatorAttestationResponse#verify` supports `android-key` attestation statements. Thank you @bdewater!
|
8
|
+
|
9
|
+
### Fixed
|
10
|
+
|
11
|
+
- Verify matching AAGUID if needed when verifying `packed` attestation statements. Thank you @bdewater!
|
12
|
+
|
3
13
|
## [v1.10.0] - 2019-03-05
|
4
14
|
|
5
15
|
### Added
|
@@ -127,6 +137,7 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
127
137
|
- `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
|
128
138
|
- Works with ruby 2.5
|
129
139
|
|
140
|
+
[v1.11.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.10.0...v1.11.0/
|
130
141
|
[v1.10.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.9.0...v1.10.0/
|
131
142
|
[v1.9.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.8.0...v1.9.0/
|
132
143
|
[v1.8.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.7.0...v1.8.0/
|
data/README.md
CHANGED
@@ -7,10 +7,11 @@ Easily implement WebAuthn in your ruby/rails app
|
|
7
7
|
|
8
8
|
## What is WebAuthn?
|
9
9
|
|
10
|
-
- [WebAuthn article with Google IO 2018 talk](https://developers.google.com/web/updates/2018/05/webauthn)
|
11
|
-
- [Web Authentication API draft article by Mozilla](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)
|
12
10
|
- [WebAuthn W3C Recommendation](https://www.w3.org/TR/webauthn/)
|
13
|
-
- [WebAuthn
|
11
|
+
- [WebAuthn intro by Yubico](https://www.yubico.com/webauthn/)
|
12
|
+
- [WebAuthn in Wikipedia](https://en.wikipedia.org/wiki/WebAuthn)
|
13
|
+
- [Web Authentication API in MDN](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)
|
14
|
+
- [WebAuthn article with Google IO 2018 talk](https://developers.google.com/web/updates/2018/05/webauthn)
|
14
15
|
|
15
16
|
## Prerequisites
|
16
17
|
|
@@ -10,6 +10,7 @@ module WebAuthn
|
|
10
10
|
ATTESTATION_FORMAT_FIDO_U2F = "fido-u2f"
|
11
11
|
ATTESTATION_FORMAT_PACKED = 'packed'
|
12
12
|
ATTESTATION_FORMAT_ANDROID_SAFETYNET = "android-safetynet"
|
13
|
+
ATTESTATION_FORMAT_ANDROID_KEY = "android-key"
|
13
14
|
|
14
15
|
ATTESTATION_TYPE_NONE = "None"
|
15
16
|
ATTESTATION_TYPE_BASIC = "Basic"
|
@@ -32,6 +33,9 @@ module WebAuthn
|
|
32
33
|
when ATTESTATION_FORMAT_ANDROID_SAFETYNET
|
33
34
|
require "webauthn/attestation_statement/android_safetynet"
|
34
35
|
WebAuthn::AttestationStatement::AndroidSafetynet.new(statement)
|
36
|
+
when ATTESTATION_FORMAT_ANDROID_KEY
|
37
|
+
require "webauthn/attestation_statement/android_key"
|
38
|
+
WebAuthn::AttestationStatement::AndroidKey.new(statement)
|
35
39
|
else
|
36
40
|
raise FormatNotSupportedError, "Unsupported attestation format '#{format}'"
|
37
41
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
require "webauthn/attestation_statement/base"
|
5
|
+
|
6
|
+
module WebAuthn
|
7
|
+
module AttestationStatement
|
8
|
+
class AndroidKey < Base
|
9
|
+
EXTENSION_DATA_OID = "1.3.6.1.4.1.11129.2.1.17"
|
10
|
+
|
11
|
+
# https://developer.android.com/training/articles/security-key-attestation#certificate_schema
|
12
|
+
ATTESTATION_CHALLENGE_INDEX = 4
|
13
|
+
SOFTWARE_ENFORCED_INDEX = 6
|
14
|
+
TEE_ENFORCED_INDEX = 7
|
15
|
+
PURPOSE_TAG = 1
|
16
|
+
ALL_APPLICATIONS_TAG = 600
|
17
|
+
ORIGIN_TAG = 702
|
18
|
+
|
19
|
+
# https://android.googlesource.com/platform/hardware/libhardware/+/master/include/hardware/keymaster_defs.h
|
20
|
+
KM_ORIGIN_GENERATED = 0
|
21
|
+
KM_PURPOSE_SIGN = 2
|
22
|
+
|
23
|
+
def valid?(authenticator_data, client_data_hash)
|
24
|
+
valid_signature?(authenticator_data, client_data_hash) &&
|
25
|
+
matching_public_key?(authenticator_data) &&
|
26
|
+
valid_attestation_challenge?(client_data_hash) &&
|
27
|
+
all_applications_field_not_present? &&
|
28
|
+
valid_authorization_list_origin? &&
|
29
|
+
valid_authorization_list_purpose? &&
|
30
|
+
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC, attestation_certificate_chain]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def valid_signature?(authenticator_data, client_data_hash)
|
36
|
+
attestation_certificate.public_key.verify(
|
37
|
+
OpenSSL::Digest::SHA256.new,
|
38
|
+
signature,
|
39
|
+
authenticator_data.data + client_data_hash
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def matching_public_key?(authenticator_data)
|
44
|
+
attestation_certificate.public_key.to_der == authenticator_data.credential.public_key_object.to_der
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid_attestation_challenge?(client_data_hash)
|
48
|
+
WebAuthn::SecurityUtils.secure_compare(key_description[ATTESTATION_CHALLENGE_INDEX].value, client_data_hash)
|
49
|
+
end
|
50
|
+
|
51
|
+
def all_applications_field_not_present?
|
52
|
+
tee_enforced.none? { |data| data.tag == ALL_APPLICATIONS_TAG } &&
|
53
|
+
software_enforced.none? { |data| data.tag == ALL_APPLICATIONS_TAG }
|
54
|
+
end
|
55
|
+
|
56
|
+
def valid_authorization_list_origin?
|
57
|
+
tee_enforced.detect { |data| data.tag == ORIGIN_TAG }&.value&.at(0)&.value == KM_ORIGIN_GENERATED ||
|
58
|
+
software_enforced.detect { |data| data.tag == ORIGIN_TAG }&.value&.at(0)&.value == KM_ORIGIN_GENERATED
|
59
|
+
end
|
60
|
+
|
61
|
+
def valid_authorization_list_purpose?
|
62
|
+
tee_enforced.detect { |data| data.tag == PURPOSE_TAG }&.value&.at(0)&.value&.at(0)&.value == KM_PURPOSE_SIGN ||
|
63
|
+
software_enforced.detect { |data| data.tag == PURPOSE_TAG }&.value&.at(0)&.value&.at(0)&.value ==
|
64
|
+
KM_PURPOSE_SIGN
|
65
|
+
end
|
66
|
+
|
67
|
+
def tee_enforced
|
68
|
+
key_description[SOFTWARE_ENFORCED_INDEX].value
|
69
|
+
end
|
70
|
+
|
71
|
+
def software_enforced
|
72
|
+
key_description[TEE_ENFORCED_INDEX].value
|
73
|
+
end
|
74
|
+
|
75
|
+
def key_description
|
76
|
+
@key_description ||= begin
|
77
|
+
extension_data = attestation_certificate.extensions.detect { |ext| ext.oid == EXTENSION_DATA_OID }
|
78
|
+
raw_key_description = OpenSSL::ASN1.decode(extension_data).value.last
|
79
|
+
OpenSSL::ASN1.decode(raw_key_description.value).value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def attestation_certificate
|
84
|
+
attestation_certificate_chain[0]
|
85
|
+
end
|
86
|
+
|
87
|
+
def attestation_certificate_chain
|
88
|
+
@attestation_certificate_chain ||= raw_attestation_certificates.map do |cert|
|
89
|
+
OpenSSL::X509::Certificate.new(cert)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def raw_attestation_certificates
|
94
|
+
statement["x5c"]
|
95
|
+
end
|
96
|
+
|
97
|
+
def signature
|
98
|
+
statement["sig"]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -15,6 +15,7 @@ module WebAuthn
|
|
15
15
|
valid_format? &&
|
16
16
|
valid_certificate_chain?(authenticator_data.credential) &&
|
17
17
|
meet_certificate_requirement? &&
|
18
|
+
matching_aaguid?(authenticator_data.attested_credential_data.aaguid) &&
|
18
19
|
valid_signature?(authenticator_data, client_data_hash) &&
|
19
20
|
attestation_type_and_trust_path
|
20
21
|
end
|
@@ -79,6 +80,18 @@ module WebAuthn
|
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
83
|
+
def matching_aaguid?(attested_credential_data_aaguid)
|
84
|
+
extension = attestation_certificate&.extensions&.detect { |ext| ext.oid == AAGUID_EXTENSION_OID }
|
85
|
+
if extension
|
86
|
+
# `extension.value` mangles data into ASCII, so we must manually compare bytes
|
87
|
+
# see https://github.com/ruby/openssl/pull/234
|
88
|
+
extension.to_der[-WebAuthn::AuthenticatorData::AttestedCredentialData::AAGUID_LENGTH..-1] ==
|
89
|
+
attested_credential_data_aaguid
|
90
|
+
else
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
82
95
|
def valid_signature?(authenticator_data, client_data_hash)
|
83
96
|
(attestation_certificate&.public_key || authenticator_data.credential.public_key_object).verify(
|
84
97
|
"SHA256",
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "webauthn/authenticator_data/attested_credential_data/
|
3
|
+
require "webauthn/authenticator_data/attested_credential_data/public_key"
|
4
4
|
|
5
5
|
module WebAuthn
|
6
6
|
class AuthenticatorData
|
@@ -33,6 +33,10 @@ module WebAuthn
|
|
33
33
|
data.length >= AAGUID_LENGTH + ID_LENGTH_LENGTH && public_key.valid?
|
34
34
|
end
|
35
35
|
|
36
|
+
def aaguid
|
37
|
+
data_at(0, AAGUID_LENGTH)
|
38
|
+
end
|
39
|
+
|
36
40
|
def credential
|
37
41
|
@credential ||=
|
38
42
|
if id
|
@@ -57,7 +61,7 @@ module WebAuthn
|
|
57
61
|
end
|
58
62
|
|
59
63
|
def public_key
|
60
|
-
@public_key ||=
|
64
|
+
@public_key ||= PublicKey.new(data_at(public_key_position, public_key_length))
|
61
65
|
end
|
62
66
|
|
63
67
|
def id_position
|
data/lib/webauthn/authenticator_data/attested_credential_data/{public_key_u2f.rb → public_key.rb}
RENAMED
@@ -6,7 +6,7 @@ require "cose/key/ec2"
|
|
6
6
|
module WebAuthn
|
7
7
|
class AuthenticatorData
|
8
8
|
class AttestedCredentialData
|
9
|
-
class
|
9
|
+
class PublicKey
|
10
10
|
COORDINATE_LENGTH = 32
|
11
11
|
|
12
12
|
def initialize(data)
|
@@ -29,7 +29,7 @@ module WebAuthn
|
|
29
29
|
attr_reader :data
|
30
30
|
|
31
31
|
def cose_key
|
32
|
-
@cose_key ||= COSE::Key::EC2.
|
32
|
+
@cose_key ||= COSE::Key::EC2.deserialize(data)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
data/lib/webauthn/version.rb
CHANGED
data/webauthn.gemspec
CHANGED
@@ -29,8 +29,8 @@ Gem::Specification.new do |spec|
|
|
29
29
|
|
30
30
|
spec.required_ruby_version = ">= 2.3"
|
31
31
|
|
32
|
-
spec.add_dependency "cbor", "~> 0.5.9
|
33
|
-
spec.add_dependency "cose", "~> 0.1
|
32
|
+
spec.add_dependency "cbor", "~> 0.5.9"
|
33
|
+
spec.add_dependency "cose", "~> 0.4.1"
|
34
34
|
spec.add_dependency "jwt", [">= 1.5", "< 3.0"]
|
35
35
|
spec.add_dependency "openssl", "~> 2.0"
|
36
36
|
spec.add_dependency "securecompare", "~> 1.0"
|
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.11.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-03-
|
12
|
+
date: 2019-03-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cbor
|
@@ -17,28 +17,28 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: 0.5.9
|
20
|
+
version: 0.5.9
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: 0.5.9
|
27
|
+
version: 0.5.9
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: cose
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 0.1
|
34
|
+
version: 0.4.1
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: 0.1
|
41
|
+
version: 0.4.1
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: jwt
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -185,6 +185,7 @@ files:
|
|
185
185
|
- lib/cose/ecdsa.rb
|
186
186
|
- lib/webauthn.rb
|
187
187
|
- lib/webauthn/attestation_statement.rb
|
188
|
+
- lib/webauthn/attestation_statement/android_key.rb
|
188
189
|
- lib/webauthn/attestation_statement/android_safetynet.rb
|
189
190
|
- lib/webauthn/attestation_statement/base.rb
|
190
191
|
- lib/webauthn/attestation_statement/fido_u2f.rb
|
@@ -194,7 +195,7 @@ files:
|
|
194
195
|
- lib/webauthn/authenticator_attestation_response.rb
|
195
196
|
- lib/webauthn/authenticator_data.rb
|
196
197
|
- lib/webauthn/authenticator_data/attested_credential_data.rb
|
197
|
-
- lib/webauthn/authenticator_data/attested_credential_data/
|
198
|
+
- lib/webauthn/authenticator_data/attested_credential_data/public_key.rb
|
198
199
|
- lib/webauthn/authenticator_response.rb
|
199
200
|
- lib/webauthn/client_data.rb
|
200
201
|
- lib/webauthn/error.rb
|