ibanity 2.3 → 2.3.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: 48dd4b01d72e61f94d0f750657abe36e7598ae92cbc4112b1be54bc5385900a6
4
- data.tar.gz: e2958c684d2b6e9b41474c372407cd78d2f4cc9d54ce9cb0128c68babc5eb9ce
3
+ metadata.gz: 319589bf55ec8cd48ca771266da34bc5374f8ebfa9ec4894c6b9b6ef7c8ca1bf
4
+ data.tar.gz: 5f7c0cc44590541555d51dfc96e1f1e70eabd54b19dbb14b66ffdb40f967a87d
5
5
  SHA512:
6
- metadata.gz: 36edf303e46ef38ccbd09c2f45b5537f534035d39715163599dbe35c9f74e113e920146bb98fe64fb9fac1ee73ee41e448a60a35a608ed54ea7a6844a444eb9f
7
- data.tar.gz: 94fb8e370e15e770fbb2791496ed7fac35ef1e4936ee38f020ce8c0556dedd9e02d572014ac490bb08ff1da0984f439e912bcd4f1a75405b5d74b8c48693ee38
6
+ metadata.gz: 31ec94ee483bc7926d7a66dbb59a34d8bfcdec5e27daedcd7599a53b54c80611ff4d829a1f634b6d6c3c033592e321ab1faf9638c2e52fa00a7664aeac996250
7
+ data.tar.gz: 6f8bd1c3ff75e41c7e32178e9f060896de45245ec6fb8261cd406432eae62653071b4babbf5dd93d976978d63d3dab70e51f4bd3ade1afbcfbd22c753a567179
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.1
4
+
5
+ * Migrate from Jose to JWT gem following incompatibly with OpenSSL 3. Only used in webhooks signature validation.
6
+
3
7
  ## 2.3
4
8
 
5
9
  * [Ponto Connect] Add IntegrationAccount
data/ibanity.gemspec CHANGED
@@ -19,6 +19,6 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "rest-client", ">= 1.8.0"
22
- spec.add_dependency "jose", ">= 1.1.3"
22
+ spec.add_dependency "jwt", ">= 2.0.0"
23
23
  spec.add_development_dependency "rspec", "3.9.0"
24
24
  end
@@ -1,3 +1,3 @@
1
1
  module Ibanity
2
- VERSION = "2.3"
2
+ VERSION = "2.3.1"
3
3
  end
@@ -26,66 +26,56 @@ module Ibanity
26
26
  #
27
27
  # Returns true otherwise
28
28
  def self.verify!(payload, signature_header, tolerance)
29
- begin
30
- key_details = JOSE::JWT.peek_protected(signature_header).to_hash
31
- raise unless key_details["alg"] == SIGNING_ALGORITHM && key_details["kid"]
32
- rescue
33
- raise Ibanity::Error.new("Key details could not be parsed from the header", nil)
34
- end
35
-
36
- key = Ibanity.webhook_keys.find { |key| key.kid == key_details["kid"] }
29
+ jwks_loader = ->(options) do
30
+ raise Ibanity::Error.new("The key id from the header didn't match an available signing key", nil) if options[:kid_not_found]
37
31
 
38
- if key.nil?
39
- raise Ibanity::Error.new("The key id from the header didn't match an available signing key", nil)
32
+ keys = Ibanity.webhook_keys.select { |key| key.use == "sig" }
33
+ .map { |key| JWT::JWK.new(key.to_h {|key, value| [key.to_s, value] }) }
34
+ JWT::JWK::Set.new(keys)
40
35
  end
41
- signer = JOSE::JWK.from(key.to_h {|key, value| [key.to_s, value] })
42
- verified, claims_string, _jws = JOSE::JWT.verify_strict(signer, [SIGNING_ALGORITHM], signature_header)
43
-
44
- raise Ibanity::Error.new("The signature verification failed", nil) unless verified
45
36
 
46
- jwt = JOSE::JWT.from(claims_string)
37
+ options = {
38
+ aud: Ibanity.client.application_id,
39
+ algorithm: SIGNING_ALGORITHM,
40
+ exp_leeway: tolerance,
41
+ iss: Ibanity.client.base_uri,
42
+ jwks: jwks_loader,
43
+ verify_aud: true,
44
+ verify_iss: true
45
+ }
46
+ jwts = JWT.decode(signature_header, nil, true, options)
47
+ jwt = jwts.first
47
48
 
48
49
  validate_digest!(jwt, payload)
49
50
  validate_issued_at!(jwt, tolerance)
50
- validate_expiration!(jwt, tolerance)
51
- validate_issuer!(jwt)
52
- validate_audience!(jwt)
53
51
 
54
52
  true
53
+ rescue JWT::ExpiredSignature
54
+ raise_invalid_signature_error!("exp")
55
+ rescue JWT::IncorrectAlgorithm
56
+ raise Ibanity::Error.new("Incorrect algorithm for signature", nil)
57
+ rescue JWT::InvalidAudError
58
+ raise_invalid_signature_error!("aud")
59
+ rescue JWT::InvalidIssuerError
60
+ raise_invalid_signature_error!("iss")
61
+ rescue JWT::DecodeError
62
+ raise Ibanity::Error.new("The signature verification failed", nil)
55
63
  end
56
64
 
57
65
  private
58
66
 
59
67
  def self.validate_digest!(jwt, payload)
60
- unless Digest::SHA512.base64digest(payload) == jwt.fields["digest"]
68
+ unless Digest::SHA512.base64digest(payload) == jwt["digest"]
61
69
  raise_invalid_signature_error!("digest")
62
70
  end
63
71
  end
64
72
 
65
73
  def self.validate_issued_at!(jwt, tolerance)
66
- unless jwt.fields["iat"] <= Time.now.to_i + tolerance
74
+ unless jwt["iat"] <= Time.now.to_i + tolerance
67
75
  raise_invalid_signature_error!("iat")
68
76
  end
69
77
  end
70
78
 
71
- def self.validate_expiration!(jwt, tolerance)
72
- unless jwt.fields["exp"] >= Time.now.to_i - tolerance
73
- raise_invalid_signature_error!("exp")
74
- end
75
- end
76
-
77
- def self.validate_issuer!(jwt)
78
- unless jwt.fields["iss"] == Ibanity.client.base_uri
79
- raise_invalid_signature_error!("iss")
80
- end
81
- end
82
-
83
- def self.validate_audience!(jwt)
84
- unless jwt.fields["aud"] == Ibanity.client.application_id
85
- raise_invalid_signature_error!("aud")
86
- end
87
- end
88
-
89
79
  def self.raise_invalid_signature_error!(field)
90
80
  raise Ibanity::Error.new("The signature #{field} is invalid", nil)
91
81
  end
data/lib/ibanity.rb CHANGED
@@ -4,7 +4,7 @@ require "uri"
4
4
  require "rest_client"
5
5
  require "json"
6
6
  require "securerandom"
7
- require "jose"
7
+ require "jwt"
8
8
 
9
9
  require_relative "ibanity/util"
10
10
  require_relative "ibanity/error"
@@ -0,0 +1,75 @@
1
+ require 'ibanity'
2
+
3
+ RSpec.describe Ibanity::Webhook::Signature do
4
+ subject(:signature_module) { described_class }
5
+
6
+ let(:client) { instance_double("Ibanity::Client") }
7
+
8
+ before do
9
+ allow(Ibanity).to receive(:client).and_return(client)
10
+ allow(Ibanity).to receive(:webhook_keys).and_return(
11
+ [
12
+ Ibanity::Webhooks::Key.new(
13
+ alg: "RS512",
14
+ e: "AQAB",
15
+ kid: "sandbox_events_signature_1",
16
+ kty: "RSA",
17
+ n: "v9qVZmotpil47Pw2NOmP11bpE5B_GtG6ICfqtm13Uusa4asf4FWedclr-kQTV2Ly5rSItq2f3RGRNyove_4TiTIbx21rwM5HP0iFhlVaqHjkr1iSmKzCFojOnTM4UwKQNROhDVDC6TWIzSafZkBacUrCX5l0PLSh2aEK8aiopu5ajYpOr8Ipjw_mbKXxBfcxtjgskbXPyEcf6xlB_Dygl9-btAvRTKiuie4qAWANTdVAgSnddjZMJxFnndZMCH1h-z4ISwphBYbwG2aZrZ7RfHnoIROxsdmKeostYtHy3gMR4_poufzFRR8lpvODd3m7lzdXKBTCvzlQYBNpmf6gmG9p08laE-h67F1GoqvuqspcvRlVpGZEzEwRIbPMAaS4_omCSj4HFZyo58PLUsAp--AD8GGFfVMyBdFhTEkr2235O5AP4UMdHuvyP-NPFCsqibqKK1GIl_Hy0UXnqg7-MCGqs4jX1k4IZZ3wDwza30f1O6tUtaOT8YXzZ2ZWnVWyMLcNx6gep8t3A7gTzEXcselrJgO6SLFRhYA0QmtIRtTwnl-8OmjEi5AJVzO0e-yiRj7g_JLEmgG3pDwmvbiXzEqkY5mPqJMB9G5qcd0SWgvvZs02_1tRRhvw0D5BTKfcEcLW9PKm8Nts1_BGSXKOhTSeQgxuw4iC63ST3dtpl-0=",
18
+ status: "ACTIVE",
19
+ use: "sig"
20
+ )
21
+ ]
22
+ )
23
+ allow(client).to receive(:application_id).and_return("e643b5be-ccb1-4a38-b7b6-36689853bef5")
24
+ allow(client).to receive(:base_uri).and_return("https://api.ibanity.com")
25
+ end
26
+
27
+ describe ".verify!" do
28
+ let(:payload) { Fixture.load_unparsed_json("webhooks/example_payload.json") }
29
+ let(:signature) { Fixture.load_signature("webhooks/example_signature.sig") }
30
+ let(:tolerance) { Time.now.to_i + 10 - 1691670985 }
31
+
32
+ it "returns true for a correct signature" do
33
+ expect(signature_module.verify!(payload, signature, tolerance)).to be_truthy
34
+ end
35
+
36
+ it "raises an error if the contents are altered" do
37
+ expect do
38
+ signature_module.verify!(payload + "altered", signature, tolerance)
39
+ end.to raise_error(Ibanity::Error, /The signature digest is invalid/)
40
+ end
41
+
42
+ it "raises an error if the signature is expired" do
43
+ expect do
44
+ signature_module.verify!(payload, signature, 0)
45
+ end.to raise_error(Ibanity::Error, /The signature exp is invalid/)
46
+ end
47
+
48
+ it "raises an error if the audience is invalid" do
49
+ allow(client).to receive(:application_id).and_return("invalid")
50
+ expect do
51
+ signature_module.verify!(payload, signature, tolerance)
52
+ end.to raise_error(Ibanity::Error, /The signature aud is invalid/)
53
+ end
54
+
55
+ it "raises an error if the issuer is invalid" do
56
+ allow(client).to receive(:base_uri).and_return("invalid")
57
+ expect do
58
+ signature_module.verify!(payload, signature, tolerance)
59
+ end.to raise_error(Ibanity::Error, /The signature iss is invalid/)
60
+ end
61
+
62
+ it "raises an error if the signing key is unavailable" do
63
+ allow(Ibanity).to receive(:webhook_keys).and_return([])
64
+ expect do
65
+ signature_module.verify!(payload, signature, tolerance)
66
+ end.to raise_error(Ibanity::Error, /The key id from the header didn't match an available signing key/)
67
+ end
68
+
69
+ it "raises an error if the signature is altered" do
70
+ expect do
71
+ signature_module.verify!(payload, signature + "altered", tolerance)
72
+ end.to raise_error(Ibanity::Error, /The signature verification failed/)
73
+ end
74
+ end
75
+ end
@@ -5,4 +5,14 @@ module Fixture
5
5
  path = Pathname([File.dirname(__FILE__ ), "fixtures", "json", filename].join("/"))
6
6
  JSON.parse(File.read(path))
7
7
  end
8
+
9
+ def self.load_unparsed_json(filename)
10
+ path = Pathname([File.dirname(__FILE__ ), "fixtures", "json", filename].join("/"))
11
+ File.read(path)
12
+ end
13
+
14
+ def self.load_signature(filename)
15
+ path = Pathname([File.dirname(__FILE__ ), "fixtures", "signature", filename].join("/"))
16
+ File.read(path)
17
+ end
8
18
  end
@@ -0,0 +1 @@
1
+ {"data":{"attributes":{"createdAt":"2023-08-10T12:24:42.224Z","synchronizationSubtype":"accountTransactions"},"id":"3465fd2f-6c9a-484e-971e-56bf44c9181a","relationships":{"account":{"data":{"id":"1d89d411-e310-4802-bbb7-aad7d98371e7","type":"account"}},"organization":{"data":{"id":"9315d6b3-129a-481a-9604-e2a95e0f967d","type":"organization"}},"synchronization":{"data":{"id":"178b59a6-679f-43d4-b5ad-28caea0bc08b","type":"synchronization"}}},"type":"pontoConnect.synchronization.succeededWithoutChange"}}
@@ -0,0 +1 @@
1
+ eyJhbGciOiJSUzUxMiIsImtpZCI6InNhbmRib3hfZXZlbnRzX3NpZ25hdHVyZV8xIn0.eyJhdWQiOiJlNjQzYjViZS1jY2IxLTRhMzgtYjdiNi0zNjY4OTg1M2JlZjUiLCJkaWdlc3QiOiJDZEJPWGwzMGsyM0lsVVdkM2JPL1FtazhXek5lN1NERjVCV3FUQnIyVzQ0VjlqRnhjZW4wSEFHVGsra0Nka0NjWVZsRW9PYmtZM0JCc2lPRnNFV0hIUT09IiwiZXhwIjoxNjkxNjcwOTg1LCJpYXQiOjE2OTE2NzA5MjUsImlzcyI6Imh0dHBzOi8vYXBpLmliYW5pdHkuY29tIiwianRpIjoiMWMyOTNkNTYtOTk1Yi00NDg5LTlhODMtMDEzZTg2ZTk4NjQ2In0.ooEZQDTfGmfYr5DmD7wVxdIN_0YXDBiwwL1_fFwSlTiIcr8vehAbDtcsyf3XrycySkUbWvtZYbhYXJw1BH4eDKdhCd2MnrxNX8BzxEjbAKzsGTT4l1YnrbBEIfcL_Z3qCzxJOzWErkSAgIi1XbiNFbw1YTU0c_Y0bBwxY6EEZgQVpG0-OXbzxhT7GRnsdSmYCAEkbLUzhRDj37fOz0oTfuimn04ANqVNJbTrOnQ2md_lOIS1XWl_WaDrk8pExUQ5JQii7dIxnzaHLB5J6U8zAOUchNLYJW1tcJmpVE_3baO_W6MvsWfEG9wY5Jud55WsnSHOOA4ak0KQJx3owqXt3uzlKH0MjPe6UeuJagZo1nXPW8IV6QCyvqtJ6TMwXqN67GHzh2YTS-Ct7qFxgHKFutlSfBfV4PaTTRd0ywqgTIVXY9P_hQmTSL_J0WiTg_2Di0ty10ehCau9rF1Bmpf2ZnSMK2Rr4JRPIMnFQsU-W5jZbZNF_tDaECzzkDoZgMDRrQT1TsdfSWU-XWG2f0ytiHKVcFJE9mMd25b2SJ6rx2e-TZaQwb5jO61uL1Q8zjvPWqofWZ5emiby9FWNFS-0fqF0UUJdlb7XU1fo_cQi0q4pYGgN2_2sgNETXr99zcTT2vpCf9x9RZGUzXne_WhZP_gp49m0kTX1W0s8O_2g2Do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ibanity
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.3'
4
+ version: 2.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ibanity
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-20 00:00:00.000000000 Z
11
+ date: 2023-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.8.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: jose
28
+ name: jwt
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.1.3
33
+ version: 2.0.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 1.1.3
40
+ version: 2.0.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +136,7 @@ files:
136
136
  - spec/lib/ibanity/base_resource_spec.rb
137
137
  - spec/lib/ibanity/http_signature_spec.rb
138
138
  - spec/lib/ibanity/util_spec.rb
139
+ - spec/lib/ibanity/webhook_signature_spec.rb
139
140
  - spec/spec_helper.rb
140
141
  - spec/support/crypto_helper.rb
141
142
  - spec/support/fixture.rb
@@ -143,9 +144,11 @@ files:
143
144
  - spec/support/fixtures/json/relationships/data_without_type.json
144
145
  - spec/support/fixtures/json/relationships/meta_with_type.json
145
146
  - spec/support/fixtures/json/relationships/no_links_related.json
147
+ - spec/support/fixtures/json/webhooks/example_payload.json
146
148
  - spec/support/fixtures/signature/test-certificate.pem
147
149
  - spec/support/fixtures/signature/test-private_key.pem
148
150
  - spec/support/fixtures/signature/test-public_key.pem
151
+ - spec/support/fixtures/signature/webhooks/example_signature.sig
149
152
  homepage: https://documentation.ibanity.com/api/ruby
150
153
  licenses:
151
154
  - MIT
@@ -173,6 +176,7 @@ test_files:
173
176
  - spec/lib/ibanity/base_resource_spec.rb
174
177
  - spec/lib/ibanity/http_signature_spec.rb
175
178
  - spec/lib/ibanity/util_spec.rb
179
+ - spec/lib/ibanity/webhook_signature_spec.rb
176
180
  - spec/spec_helper.rb
177
181
  - spec/support/crypto_helper.rb
178
182
  - spec/support/fixture.rb
@@ -180,6 +184,8 @@ test_files:
180
184
  - spec/support/fixtures/json/relationships/data_without_type.json
181
185
  - spec/support/fixtures/json/relationships/meta_with_type.json
182
186
  - spec/support/fixtures/json/relationships/no_links_related.json
187
+ - spec/support/fixtures/json/webhooks/example_payload.json
183
188
  - spec/support/fixtures/signature/test-certificate.pem
184
189
  - spec/support/fixtures/signature/test-private_key.pem
185
190
  - spec/support/fixtures/signature/test-public_key.pem
191
+ - spec/support/fixtures/signature/webhooks/example_signature.sig