simple_oauth 1.0.2 → 1.0.3

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: c80dd2b6480173dc8278a8b927f6b25929bddb430a72e152abbd29c8cae4c4da
4
- data.tar.gz: ac11a3c3e75fb0d1eb7964d25e5f53b002b334e27273e9e6428b5a6b9226481f
3
+ metadata.gz: ed41a0a0f5917e46e47d2fc785730f9c194a0541af8199b46a4928405000f110
4
+ data.tar.gz: 34d5043273278eb6571534259ee8628268c851d468462e6a25806402ce2afd83
5
5
  SHA512:
6
- metadata.gz: 7749f45d33c7f55a4186b9a744ea4119fe9d0ce1211ee26c89789887cad66f0a478dfddf4146e585044078adbaae82f548620794b71961a9da832cd8888ee5d3
7
- data.tar.gz: bc81ed8ad865e68843a764e75fc04b06afa4cd441fb4b24a22406c8512ec844dfd01a975dde11ea88bf96f1d4679a96b23fbe63463db5735803193781d72c39c
6
+ metadata.gz: eca0708e81fc8a823e7c5c3e87b48003bce4f6da9ac16d9c8fc0f6a5a45003261ecfaae9bf787e54f5f46b89acdaaf2c1719ad9ba9ac52c4523aaf201cd1aaac
7
+ data.tar.gz: ba7096154201bf4a19cfa3c34e5cf92a5c566e46165be3d1bf9632bbeb28bcd78b891b07a6743d631140a18a1d3bd7170176d65585af3da0f455a01a06236479
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.3] - 2026-11-14
9
+
10
+ ### Fixed
11
+
12
+ - Verify as fast as 1.0.1 when `Header#valid?` is not given a `signature_method`. 1.0.2 parsed the `consumer_secret` as an RSA key on every verification, which takes far longer to fail for a shared secret than verifying does, and parsed an RSA key twice. That made such a verification about 60 to 70 percent slower. Only a secret in PEM, or one that could be in DER, is now parsed, and an RSA key only once
13
+
14
+ ## [0.5.3] - 2026-11-14
15
+
16
+ ### Fixed
17
+
18
+ - Verify as fast as 0.5.1 when `Header#valid?` is not given a `signature_method`. 0.5.2 parsed the `consumer_secret` as an RSA key on every verification, which takes far longer to fail for a shared secret than verifying does, and parsed an RSA key twice. That made such a verification about 60 to 70 percent slower. Only a secret in PEM, or one that could be in DER, is now parsed, and an RSA key only once
19
+
8
20
  ## [1.0.2] - 2026-11-14
9
21
 
10
22
  ### Security
@@ -18,6 +30,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
30
 
19
31
  - Calling `Header#valid?` without a `signature_method`, which a later major version will require
20
32
 
33
+ ## [0.5.2] - 2026-11-14
34
+
35
+ ### Security
36
+
37
+ - `Header#valid?` no longer verifies with whatever signature method the received header claims. A server verifying RSA signatures with a client's public key, as the README showed, accepted forged requests signed with that public key as an HMAC-SHA1 or PLAINTEXT secret. Reported by Rathnadeepan (HotWater, [@HotWater-015](https://github.com/HotWater-015))
38
+ - Given a `signature_method`, the header must claim that method
39
+ - Without one, the header must claim an RSA method for an RSA key and an HMAC method for any other secret
40
+ - PLAINTEXT signatures verify only when the caller names `PLAINTEXT`
41
+
42
+ ### Deprecated
43
+
44
+ - Calling `Header#valid?` without a `signature_method`, which a later major version will require
45
+
21
46
  ## [1.0.1] - 2026-09-30
22
47
 
23
48
  ### Fixed
@@ -118,7 +143,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
118
143
  - Avoid symbolizing untrusted input in parse methods for security
119
144
  - Refactored `Header.parse` for improved robustness using StringScanner
120
145
 
146
+ [1.0.3]: https://github.com/laserlemon/simple_oauth/compare/v1.0.2...v1.0.3
147
+ [0.5.3]: https://github.com/laserlemon/simple_oauth/compare/v0.5.2...v0.5.3
121
148
  [1.0.2]: https://github.com/laserlemon/simple_oauth/compare/v1.0.1...v1.0.2
149
+ [0.5.2]: https://github.com/laserlemon/simple_oauth/compare/v0.5.1...v0.5.2
122
150
  [1.0.1]: https://github.com/laserlemon/simple_oauth/compare/v1.0.0...v1.0.1
123
151
  [1.0.0]: https://github.com/laserlemon/simple_oauth/compare/v0.5.1...v1.0.0
124
152
  [0.5.1]: https://github.com/laserlemon/simple_oauth/compare/v0.5.0...v0.5.1
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module SimpleOAuth
6
+ class Header
7
+ # Checks of the signature method a received header claims, before it is verified with it
8
+ #
9
+ # @api private
10
+ module Verification
11
+ # The first byte of a key in DER, which encodes every key as an ASN.1 SEQUENCE
12
+ ASN1_SEQUENCE = 0x30
13
+
14
+ # The start of the line that opens every key in PEM
15
+ PEM_BOUNDARY = "-----BEGIN"
16
+
17
+ private
18
+
19
+ # Checks that the header claims a signature method the verifier accepts
20
+ #
21
+ # @api private
22
+ # @param verifying [Hash] the header's options merged with the verifier's secrets
23
+ # @param expected [String, nil] the signature method the verifier expects, if it names one
24
+ # @return [Boolean] true if the claimed method is the expected one, or fits the key
25
+ # @raise [ArgumentError] if the expected signature method is not registered
26
+ def signature_method_acceptable?(verifying, expected)
27
+ claimed = options[:signature_method].to_s
28
+ return expected_signature_method?(claimed, expected) if expected
29
+
30
+ warn("SimpleOAuth::Header#valid? without a signature_method is deprecated. " \
31
+ "Name the method to verify with, such as signature_method: \"HMAC-SHA1\"", category: :deprecated)
32
+ key = verifying[:consumer_secret].to_s
33
+ Signature.registered?(claimed) && !PLAINTEXT_SIGNATURE_METHOD.casecmp(claimed).eql?(0) &&
34
+ (Signature.rsa?(claimed) ? key_encoded?(key) : !rsa_key?(key))
35
+ end
36
+
37
+ # Checks that the header claims the signature method the verifier names
38
+ #
39
+ # Signature method names are compared without regard to ASCII case, as the registry looks
40
+ # them up, and without allocating. A header that claims the expected method needs no other
41
+ # check here, since Signature.verify raises for a method that is not registered.
42
+ #
43
+ # @api private
44
+ # @param claimed [String] the signature method the header claims
45
+ # @param expected [String] the signature method the verifier expects
46
+ # @return [Boolean] true if the header claims the expected method
47
+ # @raise [ArgumentError] if the expected signature method is not registered
48
+ def expected_signature_method?(claimed, expected)
49
+ return true if claimed.casecmp(expected).eql?(0)
50
+ raise ArgumentError, "Unknown signature method: #{expected}" unless Signature.registered?(expected)
51
+
52
+ false
53
+ end
54
+
55
+ # Checks whether a verifying key is encoded as a key could be
56
+ #
57
+ # Only a key in PEM, which always holds a "-----BEGIN" line, or in DER, which always starts with
58
+ # the byte of an ASN.1 SEQUENCE, can be an RSA key, as those are the encodings OpenSSL decodes.
59
+ # Any other secret need not be parsed, which matters since a parse that fails is far slower
60
+ # than verifying a signature.
61
+ #
62
+ # @api private
63
+ # @param key [String] the consumer_secret the verifier gave
64
+ # @return [Boolean] true if the key is in PEM or could be in DER
65
+ def key_encoded?(key)
66
+ key.getbyte(0).eql?(ASN1_SEQUENCE) || key.include?(PEM_BOUNDARY)
67
+ end
68
+
69
+ # Checks whether a verifying key is an RSA key
70
+ #
71
+ # @api private
72
+ # @param key [String] the consumer_secret the verifier gave
73
+ # @return [Boolean] true if the key parses as an RSA key
74
+ def rsa_key?(key)
75
+ return false unless key_encoded?(key)
76
+
77
+ OpenSSL::PKey::RSA.new(key)
78
+ true
79
+ rescue OpenSSL::PKey::PKeyError
80
+ false
81
+ end
82
+ end
83
+ end
84
+ end
@@ -7,6 +7,7 @@ require_relative "parser"
7
7
  require_relative "signature"
8
8
  require_relative "header/class_methods"
9
9
  require_relative "header/params"
10
+ require_relative "header/verification"
10
11
 
11
12
  module SimpleOAuth
12
13
  # Generates OAuth 1.0 Authorization headers for HTTP requests
@@ -68,6 +69,7 @@ module SimpleOAuth
68
69
  attr_reader :options
69
70
 
70
71
  include Params
72
+ include Verification
71
73
 
72
74
  extend ClassMethods
73
75
  extend Encoding
@@ -147,9 +149,8 @@ module SimpleOAuth
147
149
  # # => true
148
150
  def valid?(secrets = {})
149
151
  verifying = options.merge(secrets)
150
- signature_method_acceptable?(verifying, secrets[:signature_method]) && body_hash_valid? &&
151
- Signature.verify(options.fetch(:signature_method), signing_key(verifying), signature_base,
152
- options.fetch(:signature))
152
+ expected = secrets[:signature_method]
153
+ signature_method_acceptable?(verifying, expected) && body_hash_valid? && signature_valid?(verifying, expected)
153
154
  end
154
155
 
155
156
  # Returns the OAuth attributes including the signature
@@ -231,47 +232,25 @@ module SimpleOAuth
231
232
  Signature.rsa?(options.fetch(:signature_method)) ? options[:consumer_secret] : secret(options)
232
233
  end
233
234
 
234
- # Checks that the header claims a signature method the verifier accepts
235
+ # Verifies the signature with the method the header claims
236
+ #
237
+ # A header that claims an RSA method, with no signature_method named, is verified without first
238
+ # parsing the key to check that it is an RSA key, so the key is parsed once rather than twice. The verifier
239
+ # raises PKeyError only for a key that is not an RSA key, which makes the header not valid, as
240
+ # the key does not fit the method. A verifier that names a method keeps the error, since naming
241
+ # an RSA method with another key is a mistake to report.
235
242
  #
236
243
  # @api private
237
244
  # @param verifying [Hash] the header's options merged with the verifier's secrets
238
245
  # @param expected [String, nil] the signature method the verifier expects, if it names one
239
- # @return [Boolean] true if the claimed method is the expected one, or fits the key
240
- # @raise [ArgumentError] if the expected signature method is not registered
241
- def signature_method_acceptable?(verifying, expected)
242
- claimed = options[:signature_method].to_s
243
- return expected_signature_method?(claimed, expected) if expected
244
-
245
- warn("SimpleOAuth::Header#valid? without a signature_method is deprecated. " \
246
- "Name the method to verify with, such as signature_method: \"HMAC-SHA1\"", category: :deprecated)
247
- Signature.registered?(claimed) && !PLAINTEXT_SIGNATURE_METHOD.casecmp?(claimed) &&
248
- Signature.rsa?(claimed).eql?(rsa_key?(verifying[:consumer_secret]))
249
- end
250
-
251
- # Checks that the header claims the signature method the verifier names
252
- #
253
- # Signature method names are compared without regard to case, as the registry looks them up.
254
- #
255
- # @api private
256
- # @param claimed [String] the signature method the header claims
257
- # @param expected [String] the signature method the verifier expects
258
- # @return [Boolean] true if the header claims the expected method
259
- # @raise [ArgumentError] if the expected signature method is not registered
260
- def expected_signature_method?(claimed, expected)
261
- raise ArgumentError, "Unknown signature method: #{expected}" unless Signature.registered?(expected)
262
-
263
- claimed.casecmp?(expected)
264
- end
265
-
266
- # Checks whether a verifying key is an RSA key
267
- #
268
- # @api private
269
- # @param key [String, nil] the consumer_secret the verifier gave
270
- # @return [Boolean] true if the key parses as an RSA key
271
- def rsa_key?(key)
272
- OpenSSL::PKey::RSA.new(key.to_s)
273
- true
246
+ # @return [Boolean] true if the signature is valid
247
+ # @raise [OpenSSL::PKey::PKeyError] if the verifier names an RSA method with a key that is not an RSA key
248
+ def signature_valid?(verifying, expected)
249
+ Signature.verify(options.fetch(:signature_method), signing_key(verifying), signature_base,
250
+ options.fetch(:signature))
274
251
  rescue OpenSSL::PKey::PKeyError
252
+ raise if expected
253
+
275
254
  false
276
255
  end
277
256
 
@@ -3,5 +3,5 @@
3
3
  # OAuth 1.0 header generation library
4
4
  module SimpleOAuth
5
5
  # The current version of the SimpleOAuth gem
6
- VERSION = "1.0.2"
6
+ VERSION = "1.0.3"
7
7
  end
@@ -0,0 +1,28 @@
1
+ module SimpleOAuth
2
+ class Header
3
+ # What the Verification module needs from the header that includes it
4
+ interface _Verifiable
5
+ def options: () -> Header::oauth_options
6
+ end
7
+
8
+ # Checks of the signature method a received header claims, before it is verified with it
9
+ module Verification : _Verifiable
10
+ # The first byte of a key in DER, which encodes every key as an ASN.1 SEQUENCE
11
+ ASN1_SEQUENCE: Integer
12
+
13
+ # The start of the line that opens every key in PEM
14
+ PEM_BOUNDARY: String
15
+
16
+ private
17
+
18
+ # Checks that the header claims a signature method the verifier accepts
19
+ def signature_method_acceptable?: (Header::oauth_options verifying, String? expected) -> bool
20
+ # Checks that the header claims the signature method the verifier names
21
+ def expected_signature_method?: (String claimed, String expected) -> bool
22
+ # Checks whether a verifying key is encoded as a key could be
23
+ def key_encoded?: (String key) -> bool
24
+ # Checks whether a verifying key is an RSA key
25
+ def rsa_key?: (String key) -> bool
26
+ end
27
+ end
28
+ end
data/sig/simple_oauth.rbs CHANGED
@@ -65,6 +65,8 @@ module SimpleOAuth
65
65
  # The signature method that sends the secrets themselves, which verifying accepts only when named
66
66
  PLAINTEXT_SIGNATURE_METHOD: String
67
67
 
68
+
69
+
68
70
  # What from_request needs from a request object, such as a Net::HTTPRequest
69
71
  interface _Request
70
72
  def method: () -> String
@@ -98,6 +100,9 @@ module SimpleOAuth
98
100
  # Parameter and attribute normalization from the Params module
99
101
  include Params
100
102
 
103
+ # Checks of the signature method a received header claims
104
+ include Verification
105
+
101
106
  # Class methods from ClassMethods module
102
107
  extend ClassMethods
103
108
 
@@ -151,14 +156,8 @@ module SimpleOAuth
151
156
  # The key for signing and verifying
152
157
  def signing_key: (oauth_options options) -> String?
153
158
 
154
- # Checks that the header claims a signature method the verifier accepts
155
- def signature_method_acceptable?: (oauth_options verifying, String? expected) -> bool
156
-
157
- # Checks that the header claims the signature method the verifier names
158
- def expected_signature_method?: (String claimed, String expected) -> bool
159
-
160
- # Checks whether a verifying key is an RSA key
161
- def rsa_key?: (String? key) -> bool
159
+ # Verifies the signature with the method the header claims
160
+ def signature_valid?: (oauth_options verifying, String? expected) -> bool
162
161
 
163
162
  # Checks the body against the oauth_body_hash the header carries
164
163
  def body_hash_valid?: () -> bool
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_oauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Richert
@@ -32,6 +32,7 @@ files:
32
32
  - lib/simple_oauth/header.rb
33
33
  - lib/simple_oauth/header/class_methods.rb
34
34
  - lib/simple_oauth/header/params.rb
35
+ - lib/simple_oauth/header/verification.rb
35
36
  - lib/simple_oauth/oauth2.rb
36
37
  - lib/simple_oauth/oauth2/authorization_response.rb
37
38
  - lib/simple_oauth/oauth2/client.rb
@@ -47,6 +48,7 @@ files:
47
48
  - sig/simple_oauth.rbs
48
49
  - sig/simple_oauth/header/class_methods.rbs
49
50
  - sig/simple_oauth/header/params.rbs
51
+ - sig/simple_oauth/header/verification.rbs
50
52
  - sig/simple_oauth/oauth2.rbs
51
53
  - sig/simple_oauth/parser.rbs
52
54
  - sig/simple_oauth/signature.rbs