webauthn 3.1.0 → 3.2.1

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: 3b23698bdd722a0cda45be7ee9d5ba528f966eb98304a50069242f0a25dcd259
4
- data.tar.gz: 4d7bf8874268cbbbfa75136006af9dc5a824b25d3f0522c5f2046092261bae5d
3
+ metadata.gz: 4531338c1a9301a58aea6f70458311891a81c06f50e2c223eb812ec24e980e2c
4
+ data.tar.gz: fae69f115e3f93a942446d30115e0268b0a73bd13a9a565b4235ed5103ab7b98
5
5
  SHA512:
6
- metadata.gz: bf2d6697ee0c34cccbb1f5f9765e70efe1a8062f73b35cb900894c59f512d81157a836046e8006c9c6d5b0a0b3040f9c76a83417928d0ff9fbaba2db1893aaef
7
- data.tar.gz: 2952c2573b030ffbba7837f2ba299e73c7a4e09a3a46b4a7a19c7875b4ea5394a8af00059dda69ae4c3dc34b1531b3aad3d1d1445dc4000260b3cfc4e5e23a98
6
+ metadata.gz: '038380bb699e7e9d8ee728b256ae2de062bebed3b9531174c5d428e210bcb6896ba4907ea17343239193900d7d1ffc97bde73e99323e12b8377887f7ba0dcd9a'
7
+ data.tar.gz: eb7062a1dde7a0099154580075bf060a32bdcf000c8156c77048829e38e89e227e0f3d5a15f2d80ba9c408733f94c399bb96f8d19a184bad36943f8309ce3d4a
@@ -7,7 +7,7 @@
7
7
 
8
8
  name: build
9
9
 
10
- on:
10
+ on:
11
11
  push:
12
12
  branches: [master]
13
13
  pull_request:
@@ -20,6 +20,8 @@ jobs:
20
20
  fail-fast: false
21
21
  matrix:
22
22
  ruby:
23
+ - '3.4.0-preview2'
24
+ - '3.3'
23
25
  - '3.2'
24
26
  - '3.1'
25
27
  - '3.0'
@@ -33,4 +35,16 @@ jobs:
33
35
  with:
34
36
  ruby-version: ${{ matrix.ruby }}
35
37
  bundler-cache: true
36
- - run: bundle exec rake
38
+ - run: bundle exec rspec
39
+ env:
40
+ RUBYOPT: ${{ startsWith(matrix.ruby, '3.4') && '--enable=frozen-string-literal' || '' }}
41
+
42
+ lint:
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+ - uses: ruby/setup-ruby@v1
47
+ with:
48
+ ruby-version: '3.3'
49
+ bundler-cache: true
50
+ - run: bundle exec rubocop -f github
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.2.1] - 2024-11-14
4
+
5
+ ### Fixed
6
+
7
+ - Fix JSON Serializer generating json with attributes with a null value. [#442](https://github.com/cedarcode/webauthn-ruby/pull/442) @santiagorodriguez96
8
+
9
+ ## [v3.2.0] - 2024-11-13
10
+
11
+ ### Added
12
+
13
+ - Added `AuthenticatorAttestationResponse#transports` for accessing the response's `transports` value. [#421](https://github.com/cedarcode/webauthn-ruby/pull/421) [@santiagorodriguez96]
14
+ - `WebAuthn::AuthenticatorAssertionResponse#verify` and `WebAuthn::AuthenticatorAttestationResponse#verify`,
15
+ as well as `RelyingParty#verify_registration` and `RelyingParty#verify_authentication` now accept a `user_presence`
16
+ keyword arg in order to be able to skip the user presence check for specific attestation and assertion verifications.
17
+ By default, user presence will be checked unless `silent_authentication` is enabled for the Relying Party (as it was before).
18
+ [#432](https://github.com/cedarcode/webauthn-ruby/pull/432), [#434](https://github.com/cedarcode/webauthn-ruby/pull/434), [#435](https://github.com/cedarcode/webauthn-ruby/pull/435) ([@nov](https://github.com/nov), [@santiagorodriguez96])
19
+ - `WebAuthn::FakeClient#create` and `WebAuthn::FakeAuthenticator#make_credential` now support a `credential_algorithm` and
20
+ `algorithm` param (respectively) for choosing the algorithm to use for creating the credential.
21
+ Supported values are: 'ES256', 'RSA256' and 'EdDSA'. [#400](https://github.com/cedarcode/webauthn-ruby/pull/400), [#437](https://github.com/cedarcode/webauthn-ruby/pull/437) [@santiagorodriguez96]
22
+ - Remove `awrence` dependency. [#436](https://github.com/cedarcode/webauthn-ruby/pull/436) [@npezza](https://github.com/npezza93)
23
+ - Run tests with Ruby 3.3. [#416](https://github.com/cedarcode/webauthn-ruby/pull/416) [@santiagorodriguez96]
24
+ - Run tests with Ruby 3.4.0-preview2. [#436](https://github.com/cedarcode/webauthn-ruby/pull/436) [@npezza](https://github.com/npezza93)
25
+
26
+ ### Changed
27
+
28
+ - Remove unused class `AttestationTrustworthinessVerificationError`. [#412](https://github.com/cedarcode/webauthn-ruby/pull/412) [@soartec-lab]
29
+
3
30
  ## [v3.1.0] - 2023-12-26
4
31
 
5
32
  ### Added
@@ -37,8 +37,22 @@ module WebAuthn
37
37
  @user_handle = user_handle
38
38
  end
39
39
 
40
- def verify(expected_challenge, expected_origin = nil, public_key:, sign_count:, user_verification: nil, rp_id: nil)
41
- super(expected_challenge, expected_origin, user_verification: user_verification, rp_id: rp_id)
40
+ def verify(
41
+ expected_challenge,
42
+ expected_origin = nil,
43
+ public_key:,
44
+ sign_count:,
45
+ user_presence: nil,
46
+ user_verification: nil,
47
+ rp_id: nil
48
+ )
49
+ super(
50
+ expected_challenge,
51
+ expected_origin,
52
+ user_presence: user_presence,
53
+ user_verification: user_verification,
54
+ rp_id: rp_id
55
+ )
42
56
  verify_item(:signature, WebAuthn::PublicKey.deserialize(public_key))
43
57
  verify_item(:sign_count, sign_count)
44
58
 
@@ -12,7 +12,6 @@ require "webauthn/encoder"
12
12
 
13
13
  module WebAuthn
14
14
  class AttestationStatementVerificationError < VerificationError; end
15
- class AttestationTrustworthinessVerificationError < VerificationError; end
16
15
  class AttestedCredentialVerificationError < VerificationError; end
17
16
 
18
17
  class AuthenticatorAttestationResponse < AuthenticatorResponse
@@ -23,21 +22,23 @@ module WebAuthn
23
22
 
24
23
  new(
25
24
  attestation_object: encoder.decode(response["attestationObject"]),
25
+ transports: response["transports"],
26
26
  client_data_json: encoder.decode(response["clientDataJSON"]),
27
27
  relying_party: relying_party
28
28
  )
29
29
  end
30
30
 
31
- attr_reader :attestation_type, :attestation_trust_path
31
+ attr_reader :attestation_type, :attestation_trust_path, :transports
32
32
 
33
- def initialize(attestation_object:, **options)
33
+ def initialize(attestation_object:, transports: [], **options)
34
34
  super(**options)
35
35
 
36
36
  @attestation_object_bytes = attestation_object
37
+ @transports = transports
37
38
  @relying_party = relying_party
38
39
  end
39
40
 
40
- def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
41
+ def verify(expected_challenge, expected_origin = nil, user_presence: nil, user_verification: nil, rp_id: nil)
41
42
  super
42
43
 
43
44
  verify_item(:attested_credential)
@@ -24,7 +24,7 @@ module WebAuthn
24
24
  @relying_party = relying_party
25
25
  end
26
26
 
27
- def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
27
+ def verify(expected_challenge, expected_origin = nil, user_presence: nil, user_verification: nil, rp_id: nil)
28
28
  expected_origin ||= relying_party.origin || raise("Unspecified expected origin")
29
29
  rp_id ||= relying_party.id
30
30
 
@@ -35,7 +35,8 @@ module WebAuthn
35
35
  verify_item(:authenticator_data)
36
36
  verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
37
37
 
38
- if !relying_party.silent_authentication
38
+ # Fallback to RP configuration unless user_presence is passed in explicitely
39
+ if user_presence.nil? && !relying_party.silent_authentication || user_presence
39
40
  verify_item(:user_presence)
40
41
  end
41
42
 
@@ -61,7 +61,7 @@ module WebAuthn
61
61
  begin
62
62
  credential_data =
63
63
  if attested_credential_data
64
- { id: credential_id, public_key: credential_key.public_key }
64
+ { id: credential_id, public_key: credential_public_key }
65
65
  end
66
66
 
67
67
  AuthenticatorData.new(
@@ -76,6 +76,15 @@ module WebAuthn
76
76
  )
77
77
  end
78
78
  end
79
+
80
+ def credential_public_key
81
+ case credential_key
82
+ when OpenSSL::PKey::RSA, OpenSSL::PKey::EC
83
+ credential_key.public_key
84
+ when OpenSSL::PKey::PKey
85
+ OpenSSL::PKey.read(credential_key.public_to_der)
86
+ end
87
+ end
79
88
  end
80
89
  end
81
90
  end
@@ -140,7 +140,9 @@ module WebAuthn
140
140
 
141
141
  key = COSE::Key::EC2.from_pkey(credential[:public_key])
142
142
  key.alg = alg[key.crv]
143
-
143
+ when OpenSSL::PKey::PKey
144
+ key = COSE::Key::OKP.from_pkey(credential[:public_key])
145
+ key.alg = -8
144
146
  end
145
147
 
146
148
  key.serialize
@@ -20,10 +20,11 @@ module WebAuthn
20
20
  backup_eligibility: false,
21
21
  backup_state: false,
22
22
  attested_credential_data: true,
23
+ algorithm: nil,
23
24
  sign_count: nil,
24
25
  extensions: nil
25
26
  )
26
- credential_id, credential_key, credential_sign_count = new_credential
27
+ credential_id, credential_key, credential_sign_count = new_credential(algorithm)
27
28
  sign_count ||= credential_sign_count
28
29
 
29
30
  credentials[rp_id] ||= {}
@@ -85,7 +86,14 @@ module WebAuthn
85
86
  extensions: extensions
86
87
  ).serialize
87
88
 
88
- signature = credential_key.sign("SHA256", authenticator_data + client_data_hash)
89
+ signature_digest_algorithm =
90
+ case credential_key
91
+ when OpenSSL::PKey::RSA, OpenSSL::PKey::EC
92
+ 'SHA256'
93
+ when OpenSSL::PKey::PKey
94
+ nil
95
+ end
96
+ signature = credential_key.sign(signature_digest_algorithm, authenticator_data + client_data_hash)
89
97
  credential[:sign_count] += 1
90
98
 
91
99
  {
@@ -102,8 +110,21 @@ module WebAuthn
102
110
 
103
111
  attr_reader :credentials
104
112
 
105
- def new_credential
106
- [SecureRandom.random_bytes(16), OpenSSL::PKey::EC.generate("prime256v1"), 0]
113
+ def new_credential(algorithm)
114
+ algorithm ||= 'ES256'
115
+ credential_key =
116
+ case algorithm
117
+ when 'ES256'
118
+ OpenSSL::PKey::EC.generate('prime256v1')
119
+ when 'RS256'
120
+ OpenSSL::PKey::RSA.new(2048)
121
+ when 'EdDSA'
122
+ OpenSSL::PKey.generate_key("ED25519")
123
+ else
124
+ raise "Unsupported algorithm #{algorithm}"
125
+ end
126
+
127
+ [SecureRandom.random_bytes(16), credential_key, 0]
107
128
  end
108
129
 
109
130
  def hashed(target)
@@ -32,6 +32,7 @@ module WebAuthn
32
32
  backup_eligibility: false,
33
33
  backup_state: false,
34
34
  attested_credential_data: true,
35
+ credential_algorithm: nil,
35
36
  extensions: nil
36
37
  )
37
38
  rp_id ||= URI.parse(origin).host
@@ -47,6 +48,7 @@ module WebAuthn
47
48
  backup_eligibility: backup_eligibility,
48
49
  backup_state: backup_state,
49
50
  attested_credential_data: attested_credential_data,
51
+ algorithm: credential_algorithm,
50
52
  extensions: extensions
51
53
  )
52
54
 
@@ -68,7 +70,8 @@ module WebAuthn
68
70
  "clientExtensionResults" => extensions,
69
71
  "response" => {
70
72
  "attestationObject" => encoder.encode(attestation_object),
71
- "clientDataJSON" => encoder.encode(client_data_json)
73
+ "clientDataJSON" => encoder.encode(client_data_json),
74
+ "transports" => ["internal"],
72
75
  }
73
76
  }
74
77
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebAuthn
4
+ module JSONSerializer
5
+ # Argument wildcard for Ruby on Rails controller automatic object JSON serialization
6
+ def as_json(*)
7
+ to_hash_with_camelized_keys
8
+ end
9
+
10
+ private
11
+
12
+ def to_hash_with_camelized_keys
13
+ attributes.each_with_object({}) do |attribute_name, hash|
14
+ value = send(attribute_name)
15
+
16
+ if value && value.respond_to?(:as_json)
17
+ hash[camelize(attribute_name)] = value.as_json
18
+ elsif value
19
+ hash[camelize(attribute_name)] = deep_camelize_keys(value)
20
+ end
21
+ end
22
+ end
23
+
24
+ def deep_camelize_keys(object)
25
+ case object
26
+ when Hash
27
+ object.each_with_object({}) do |(key, value), result|
28
+ result[camelize(key)] = deep_camelize_keys(value)
29
+ end
30
+ when Array
31
+ object.map { |element| deep_camelize_keys(element) }
32
+ else
33
+ object
34
+ end
35
+ end
36
+
37
+ def camelize(term)
38
+ first_term, *rest = term.to_s.split('_')
39
+
40
+ [first_term, *rest.map(&:capitalize)].join.to_sym
41
+ end
42
+ end
43
+ end
@@ -1,40 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "awrence"
4
-
5
3
  module WebAuthn
6
4
  class PublicKeyCredential
7
5
  class Entity
6
+ include JSONSerializer
7
+
8
8
  attr_reader :name
9
9
 
10
10
  def initialize(name:)
11
11
  @name = name
12
12
  end
13
13
 
14
- def as_json
15
- to_hash.to_camelback_keys
16
- end
17
-
18
14
  private
19
15
 
20
- def to_hash
21
- hash = {}
22
-
23
- attributes.each do |attribute_name|
24
- value = send(attribute_name)
25
-
26
- if value.respond_to?(:as_json)
27
- value = value.as_json
28
- end
29
-
30
- if value
31
- hash[attribute_name] = value
32
- end
33
- end
34
-
35
- hash
36
- end
37
-
38
16
  def attributes
39
17
  [:name]
40
18
  end
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "awrence"
4
3
  require "securerandom"
5
4
 
6
5
  module WebAuthn
7
6
  class PublicKeyCredential
8
7
  class Options
8
+ include JSONSerializer
9
+
9
10
  CHALLENGE_LENGTH = 32
10
11
 
11
12
  attr_reader :timeout, :extensions, :relying_party
@@ -20,31 +21,8 @@ module WebAuthn
20
21
  encoder.encode(raw_challenge)
21
22
  end
22
23
 
23
- # Argument wildcard for Ruby on Rails controller automatic object JSON serialization
24
- def as_json(*)
25
- to_hash.to_camelback_keys
26
- end
27
-
28
24
  private
29
25
 
30
- def to_hash
31
- hash = {}
32
-
33
- attributes.each do |attribute_name|
34
- value = send(attribute_name)
35
-
36
- if value.respond_to?(:as_json)
37
- value = value.as_json
38
- end
39
-
40
- if value
41
- hash[attribute_name] = value
42
- end
43
- end
44
-
45
- hash
46
- end
47
-
48
26
  def attributes
49
27
  [:challenge, :timeout, :extensions]
50
28
  end
@@ -9,13 +9,14 @@ module WebAuthn
9
9
  WebAuthn::AuthenticatorAssertionResponse
10
10
  end
11
11
 
12
- def verify(challenge, public_key:, sign_count:, user_verification: nil)
12
+ def verify(challenge, public_key:, sign_count:, user_presence: nil, user_verification: nil)
13
13
  super
14
14
 
15
15
  response.verify(
16
16
  encoder.decode(challenge),
17
17
  public_key: encoder.decode(public_key),
18
18
  sign_count: sign_count,
19
+ user_presence: user_presence,
19
20
  user_verification: user_verification,
20
21
  rp_id: appid_extension_output ? appid : nil
21
22
  )
@@ -9,10 +9,10 @@ module WebAuthn
9
9
  WebAuthn::AuthenticatorAttestationResponse
10
10
  end
11
11
 
12
- def verify(challenge, user_verification: nil)
12
+ def verify(challenge, user_presence: nil, user_verification: nil)
13
13
  super
14
14
 
15
- response.verify(encoder.decode(challenge), user_verification: user_verification)
15
+ response.verify(encoder.decode(challenge), user_presence: user_presence, user_verification: user_verification)
16
16
 
17
17
  true
18
18
  end
@@ -81,10 +81,10 @@ module WebAuthn
81
81
  )
82
82
  end
83
83
 
84
- def verify_registration(raw_credential, challenge, user_verification: nil)
84
+ def verify_registration(raw_credential, challenge, user_presence: nil, user_verification: nil)
85
85
  webauthn_credential = WebAuthn::Credential.from_create(raw_credential, relying_party: self)
86
86
 
87
- if webauthn_credential.verify(challenge, user_verification: user_verification)
87
+ if webauthn_credential.verify(challenge, user_presence: user_presence, user_verification: user_verification)
88
88
  webauthn_credential
89
89
  end
90
90
  end
@@ -99,6 +99,7 @@ module WebAuthn
99
99
  def verify_authentication(
100
100
  raw_credential,
101
101
  challenge,
102
+ user_presence: nil,
102
103
  user_verification: nil,
103
104
  public_key: nil,
104
105
  sign_count: nil
@@ -111,6 +112,7 @@ module WebAuthn
111
112
  challenge,
112
113
  public_key: public_key || stored_credential.public_key,
113
114
  sign_count: sign_count || stored_credential.sign_count,
115
+ user_presence: user_presence,
114
116
  user_verification: user_verification
115
117
  )
116
118
  block_given? ? [webauthn_credential, stored_credential] : webauthn_credential
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebAuthn
4
- VERSION = "3.1.0"
4
+ VERSION = "3.2.1"
5
5
  end
data/lib/webauthn.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "webauthn/json_serializer"
3
4
  require "webauthn/configuration"
4
5
  require "webauthn/credential"
5
6
  require "webauthn/credential_creation_options"
data/webauthn.gemspec CHANGED
@@ -34,7 +34,6 @@ Gem::Specification.new do |spec|
34
34
  spec.required_ruby_version = ">= 2.5"
35
35
 
36
36
  spec.add_dependency "android_key_attestation", "~> 0.3.0"
37
- spec.add_dependency "awrence", "~> 1.1"
38
37
  spec.add_dependency "bindata", "~> 2.4"
39
38
  spec.add_dependency "cbor", "~> 0.5.9"
40
39
  spec.add_dependency "cose", "~> 1.1"
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: 3.1.0
4
+ version: 3.2.1
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: 2023-12-26 00:00:00.000000000 Z
12
+ date: 2024-11-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: android_key_attestation
@@ -25,20 +25,6 @@ dependencies:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
27
  version: 0.3.0
28
- - !ruby/object:Gem::Dependency
29
- name: awrence
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - "~>"
33
- - !ruby/object:Gem::Version
34
- version: '1.1'
35
- type: :runtime
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: '1.1'
42
28
  - !ruby/object:Gem::Dependency
43
29
  name: bindata
44
30
  requirement: !ruby/object:Gem::Requirement
@@ -301,6 +287,7 @@ files:
301
287
  - lib/webauthn/fake_authenticator/attestation_object.rb
302
288
  - lib/webauthn/fake_authenticator/authenticator_data.rb
303
289
  - lib/webauthn/fake_client.rb
290
+ - lib/webauthn/json_serializer.rb
304
291
  - lib/webauthn/public_key.rb
305
292
  - lib/webauthn/public_key_credential.rb
306
293
  - lib/webauthn/public_key_credential/creation_options.rb
@@ -337,7 +324,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
337
324
  - !ruby/object:Gem::Version
338
325
  version: '0'
339
326
  requirements: []
340
- rubygems_version: 3.4.10
327
+ rubygems_version: 3.5.11
341
328
  signing_key:
342
329
  specification_version: 4
343
330
  summary: WebAuthn ruby server library