webauthn 1.10.0 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64d44e8b2917b4301f669f4f736814ab42c27e3541d81404169aedcec18f64c3
4
- data.tar.gz: aa138c35bdcceaf5d6971ae306c2fb3adb9c0be12ffae3ee2c2ebcb051787521
3
+ metadata.gz: 368f1cffa77727f2edab0c872a6e9414af8db2871edfbb3623f06273bb71f5ae
4
+ data.tar.gz: 4b21d7e5cd9ff589bb97f4264d0dbf7f3f04da53aa619893f88a1d831d7c2755
5
5
  SHA512:
6
- metadata.gz: 18ce684f6613dd5d399ef413f3d9d0c8dae77e97ba8eaa7cafae0e785ce14cdaa5c852cb44ea1e41dc03a7a9cb78841f0038ad0e228f4579f0d904fdd90601fc
7
- data.tar.gz: db18d834bbbe2870c8d05db904d4ae2852516b10a786e6c9f7b5b38ea3cbfde9c2cbc7fa9b27b4979e56af892e2fcf019f24821dcc03a13f93e9bf096f1eb198
6
+ metadata.gz: c88fb415b6141de835c2d1536f8e859b1ba1a146b1e9c13971cf8bb07478bda5efa9c6a85f69c06466b91984b7f00e14f0cf29229cf14bc10f70040659411586
7
+ data.tar.gz: f94c85b184e96399f71bc7c070b691160640a5de898e381cd23f4bddc2fce139a2c523172cc8aeb682715b66a038f1ad3ed7798893a645d31eef53268784dc08
data/.travis.yml CHANGED
@@ -3,8 +3,8 @@ language: ruby
3
3
  cache: bundler
4
4
  rvm:
5
5
  - ruby-head
6
- - 2.6.1
7
- - 2.5.3
6
+ - 2.6.2
7
+ - 2.5.4
8
8
  - 2.4.5
9
9
  - 2.3.8
10
10
  matrix:
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 W3C Editor's Draft](https://w3c.github.io/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
@@ -7,6 +7,8 @@ module WebAuthn
7
7
  class Base
8
8
  class NotSupportedError < Error; end
9
9
 
10
+ AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
11
+
10
12
  def initialize(statement)
11
13
  @statement = statement
12
14
  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/public_key_u2f"
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 ||= PublicKeyU2f.new(data_at(public_key_position, public_key_length))
64
+ @public_key ||= PublicKey.new(data_at(public_key_position, public_key_length))
61
65
  end
62
66
 
63
67
  def id_position
@@ -6,7 +6,7 @@ require "cose/key/ec2"
6
6
  module WebAuthn
7
7
  class AuthenticatorData
8
8
  class AttestedCredentialData
9
- class PublicKeyU2f
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.from_cbor(data)
32
+ @cose_key ||= COSE::Key::EC2.deserialize(data)
33
33
  end
34
34
  end
35
35
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebAuthn
4
- VERSION = "1.10.0"
4
+ VERSION = "1.11.0"
5
5
  end
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.2"
33
- spec.add_dependency "cose", "~> 0.1.0"
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.10.0
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-05 00:00:00.000000000 Z
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.2
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.2
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.0
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.0
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/public_key_u2f.rb
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