simple_oauth 0.5.1 → 0.5.2

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: 680161f3f3fc8cf54f78c5111c1f0c4bcd6380b0d29022fb9ac5fd407805d8b7
4
- data.tar.gz: 6951095c0b1b04b7755e8857a977946825053a141e3fd4c1c3b7e10cade061fe
3
+ metadata.gz: 88c89c6dff93b31b6279da0d85c0b719cc94bac966356ae1783702ed3cbfe8e5
4
+ data.tar.gz: 1febe046539f65e64a5b3d6b787282e5ce91178bf1617f97ce6a08a5a159318e
5
5
  SHA512:
6
- metadata.gz: f852606e3aa48e70a5856f0cfccbfafe4c9235a6a701faca692446186b5bf8f30781b316577b7ac15a164b07293f9b550ad5cdfe0db58df6e57be396de380cf6
7
- data.tar.gz: d938005d0ad30817b294e0e8143b272be0dafb794dfa4a57ee012fc8f162c7d895d85a5a2ae0a57f4bc3b9e50c372fde93ac22b31bc01579ead3deec968f2d2a
6
+ metadata.gz: 1f2b608cbf969b4983f29c746a5e892b0d5b1d321a6e044b8c01f645be8733aade948f5410a0654a6ece51778050cdb5a846e7ca02d70c1bd53cdf4d943aab8d
7
+ data.tar.gz: 5c76e78b8b537367e00833e063f9a23bacd5e8ea00563e5e41536e0ee223f4c257032001411059380a6f3b39ab883e5253304a12e59eba2da4d88d6ebe130af7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [0.5.2] - 2026-11-14
2
+
3
+ ### Security
4
+
5
+ * `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))
6
+ * Given a `signature_method`, the header must claim that method
7
+ * Without one, the header must claim an RSA method for an RSA key and an HMAC method for any other secret
8
+ * PLAINTEXT signatures verify only when the caller names `PLAINTEXT`
9
+
10
+ ### Deprecated
11
+
12
+ * Calling `Header#valid?` without a `signature_method`, which a later major version will require
13
+
1
14
  ## [0.5.1] - 2026-09-12
2
15
 
3
16
  ### Fixed
data/README.md CHANGED
@@ -155,16 +155,18 @@ parsed = SimpleOAuth::Header.parse_query("oauth_consumer_key=key&status=hello")
155
155
  header = SimpleOAuth::Header.new(:get, request_url, params, authorization_header)
156
156
 
157
157
  # Verify the signature
158
- header.valid?(consumer_secret: "secret", token_secret: "token_secret")
158
+ header.valid?(consumer_secret: "secret", token_secret: "token_secret", signature_method: "HMAC-SHA1")
159
159
  # => true
160
160
  ```
161
161
 
162
+ Name the signature method you expect, and a header claiming any other method is not valid. The method a request claims is chosen by whoever sent it, so verifying with it unchecked would let anyone holding an RSA public key pass it off as an HMAC or PLAINTEXT secret. Leaving `signature_method` out is deprecated: the header must then claim an RSA method for an RSA key and an HMAC method for any other secret. PLAINTEXT signatures are accepted only when you name `PLAINTEXT`, and they send the secrets themselves, so accept them only over TLS.
163
+
162
164
  Verifying compares signatures in constant time and leaves the header's own options untouched, so the secrets stay with the caller.
163
165
 
164
166
  RSA signatures verify with the client's public key, which is all a server has:
165
167
 
166
168
  ```ruby
167
- header.valid?(consumer_secret: File.read("client_public_key.pem"))
169
+ header.valid?(consumer_secret: File.read("client_public_key.pem"), signature_method: "RSA-SHA1")
168
170
  ```
169
171
 
170
172
  Custom signature methods that cannot be verified by recomputing the signature register a `verify` block:
@@ -23,6 +23,9 @@ module SimpleOAuth
23
23
  # Default signature method per RFC 5849
24
24
  DEFAULT_SIGNATURE_METHOD = "HMAC-SHA1".freeze
25
25
 
26
+ # The signature method that sends the secrets themselves, which verifying accepts only when named
27
+ PLAINTEXT_SIGNATURE_METHOD = "PLAINTEXT".freeze
28
+
26
29
  # OAuth version
27
30
  OAUTH_VERSION = "1.0".freeze
28
31
 
@@ -122,18 +125,30 @@ module SimpleOAuth
122
125
 
123
126
  # Validates the signature in the header against the provided secrets
124
127
  #
128
+ # The method a received header claims is chosen by whoever sent it, so it is checked before it
129
+ # is used. Given a signature_method, the header must claim that method. Without one, the header
130
+ # must claim a method that fits the key: an RSA method for an RSA key, and an HMAC method for any
131
+ # other secret. PLAINTEXT is accepted only when named, as it sends the secrets themselves.
132
+ # Without that check, a sender holding only an RSA public key could sign with it as an HMAC or
133
+ # PLAINTEXT secret.
134
+ #
125
135
  # @api public
126
- # @param secrets [Hash] the consumer_secret and token_secret for validation
136
+ # @param secrets [Hash] the consumer_secret and token_secret for validation, and the
137
+ # signature_method the header must use, which a later major version will require
127
138
  # @return [Boolean] true if the signature is valid, false otherwise
139
+ # @raise [ArgumentError] if the expected signature method is not registered
128
140
  # @note When the header was built with a body, the signed oauth_body_hash must match that body,
129
141
  # so a tampered body fails verification even though its signature covers the claimed hash
130
142
  # @example
131
143
  # parsed_header = SimpleOAuth::Header.new(:get, url, {}, authorization_header)
132
- # parsed_header.valid?(consumer_secret: "secret", token_secret: "token_secret")
144
+ # parsed_header.valid?(consumer_secret: "secret", token_secret: "token_secret",
145
+ # signature_method: "HMAC-SHA1")
133
146
  # # => true
134
147
  def valid?(secrets = {})
135
- body_hash_valid? && Signature.verify(options.fetch(:signature_method), signing_key(options.merge(secrets)),
136
- signature_base, options.fetch(:signature))
148
+ verifying = options.merge(secrets)
149
+ signature_method_acceptable?(verifying, secrets[:signature_method]) && body_hash_valid? &&
150
+ Signature.verify(options.fetch(:signature_method), signing_key(verifying), signature_base,
151
+ options.fetch(:signature))
137
152
  end
138
153
 
139
154
  # Returns the OAuth attributes including the signature
@@ -215,6 +230,50 @@ module SimpleOAuth
215
230
  Signature.rsa?(options.fetch(:signature_method)) ? options[:consumer_secret] : secret(options)
216
231
  end
217
232
 
233
+ # Checks that the header claims a signature method the verifier accepts
234
+ #
235
+ # @api private
236
+ # @param verifying [Hash] the header's options merged with the verifier's secrets
237
+ # @param expected [String, nil] the signature method the verifier expects, if it names one
238
+ # @return [Boolean] true if the claimed method is the expected one, or fits the key
239
+ # @raise [ArgumentError] if the expected signature method is not registered
240
+ def signature_method_acceptable?(verifying, expected)
241
+ claimed = options[:signature_method].to_s
242
+ return expected_signature_method?(claimed, expected) if expected
243
+
244
+ warn("SimpleOAuth::Header#valid? without a signature_method is deprecated. " \
245
+ "Name the method to verify with, such as signature_method: \"HMAC-SHA1\"", category: :deprecated)
246
+ Signature.registered?(claimed) && !PLAINTEXT_SIGNATURE_METHOD.casecmp?(claimed) &&
247
+ Signature.rsa?(claimed).eql?(rsa_key?(verifying[:consumer_secret]))
248
+ end
249
+
250
+ # Checks that the header claims the signature method the verifier names
251
+ #
252
+ # Signature method names are compared without regard to case, as the registry looks them up.
253
+ #
254
+ # @api private
255
+ # @param claimed [String] the signature method the header claims
256
+ # @param expected [String] the signature method the verifier expects
257
+ # @return [Boolean] true if the header claims the expected method
258
+ # @raise [ArgumentError] if the expected signature method is not registered
259
+ def expected_signature_method?(claimed, expected)
260
+ raise ArgumentError, "Unknown signature method: #{expected}" unless Signature.registered?(expected)
261
+
262
+ claimed.casecmp?(expected)
263
+ end
264
+
265
+ # Checks whether a verifying key is an RSA key
266
+ #
267
+ # @api private
268
+ # @param key [String, nil] the consumer_secret the verifier gave
269
+ # @return [Boolean] true if the key parses as an RSA key
270
+ def rsa_key?(key)
271
+ OpenSSL::PKey::RSA.new(key.to_s)
272
+ true
273
+ rescue OpenSSL::PKey::PKeyError
274
+ false
275
+ end
276
+
218
277
  # Checks the body against the oauth_body_hash the header carries
219
278
  #
220
279
  # A header parsed from a request claims a body hash that its signature covers, so the claim must be
@@ -1,5 +1,5 @@
1
1
  # OAuth 1.0 header generation library
2
2
  module SimpleOauth
3
3
  # The current version of the SimpleOAuth gem
4
- VERSION = "0.5.1".freeze
4
+ VERSION = "0.5.2".freeze
5
5
  end
data/sig/simple_oauth.rbs CHANGED
@@ -58,6 +58,9 @@ module SimpleOAuth
58
58
  # Valid keys when parsing OAuth parameters (ATTRIBUTE_KEYS + signature)
59
59
  PARSE_KEYS: Array[Symbol]
60
60
 
61
+ # The signature method that sends the secrets themselves, which verifying accepts only when named
62
+ PLAINTEXT_SIGNATURE_METHOD: String
63
+
61
64
  # What from_request needs from a request object, such as a Net::HTTPRequest
62
65
  interface _Request
63
66
  def method: () -> String
@@ -144,6 +147,15 @@ module SimpleOAuth
144
147
  # The key for signing and verifying
145
148
  def signing_key: (oauth_options options) -> String?
146
149
 
150
+ # Checks that the header claims a signature method the verifier accepts
151
+ def signature_method_acceptable?: (oauth_options verifying, String? expected) -> bool
152
+
153
+ # Checks that the header claims the signature method the verifier names
154
+ def expected_signature_method?: (String claimed, String expected) -> bool
155
+
156
+ # Checks whether a verifying key is an RSA key
157
+ def rsa_key?: (String? key) -> bool
158
+
147
159
  # Checks the body against the oauth_body_hash the header carries
148
160
  def body_hash_valid?: () -> bool
149
161
 
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: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Richert
@@ -95,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  requirements: []
98
- rubygems_version: 4.0.20
98
+ rubygems_version: 4.0.24
99
99
  specification_version: 4
100
100
  summary: Simply builds and verifies OAuth headers
101
101
  test_files: []