webauthn 3.1.0 → 3.2.1

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