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 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