as2 0.9.0 → 0.11.0

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: e5d44130a76edfd15f3938df800fd0b82a0a14446ed064844250c33f0ba28c0c
4
- data.tar.gz: b2436bd0539cefe5387e08042ba5bf1798282515c17d6c884eda08c7e9abd913
3
+ metadata.gz: 7a467f304fd7955e9f787078b8bbda6277e111e84e75d2cc02c935d6ccc00916
4
+ data.tar.gz: b75996e0b5064d1b4133f9ed8db0b9f629cb44746ade335edcd3a2fa04de0edf
5
5
  SHA512:
6
- metadata.gz: c7ec66eff025813c21849d375933cef2e06c70ae658363ffce640d22ea72be73650908862307380e70da03510765016b99bf60a65f6f56f432d4e3381eab8ee4
7
- data.tar.gz: 5f9747317e5675e292ea80f02b01de73a0d14e78c60236970f81a8a68a3cefb0535b2bd9c4d2b6fee7e95399746fd773f40de5481db2975fe2f21c9ce5c7178e
6
+ metadata.gz: b8b50c0291eed98d9f74333e094bf1a59cf4bedb987592082dddc04118e319dd85121f8434391fb473783320b6388ec836d0fe54901b02b8c3cdf70f95eb93b6
7
+ data.tar.gz: db4e30dba47bd613b3b9b5e0e47d33951e527a31249af79ce7f2073c66035cc9a3aa3a8ba5a88c0163b4e06eec814d6ac2bf48ee060935cc22ec57762bdd2b10
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 0.11.0 September 14, 2023
2
+
3
+ * Allow configuration of which encryption cipher to use when sending outbound messages. [#35](https://github.com/alexdean/as2/pull/35)
4
+
5
+ ## 0.10.0 September 13, 2023
6
+
7
+ support for separate signing & encryption certificates for partners. [#34](https://github.com/alexdean/as2/pull/34)
8
+
9
+ BREAKING CHANGES:
10
+
11
+ * `As2::Config::Partner`
12
+ * Added `signing_certificate` and `encryption_certificate`
13
+ * Removed `certificate`.
14
+ * `certificate=` is still supported, and assigns the same certificate to both.
15
+ * `As2::Client#parse_signed_mdn`: requires `signing_certificate:` rather than `certificate:`.
16
+ * `As2::Message.verify`: requires `signing_certificate:` rather than `certificate:`.
17
+
1
18
  ## 0.9.0, August 28, 2023
2
19
 
3
20
  * Bugfix for quoting AS2-From/AS2-To identifiers
data/lib/as2/client.rb CHANGED
@@ -8,6 +8,10 @@ module As2
8
8
  ['v0', 'v1']
9
9
  end
10
10
 
11
+ def self.valid_encryption_ciphers
12
+ OpenSSL::Cipher.ciphers
13
+ end
14
+
11
15
  # @param [As2::Config::Partner,String] partner The partner to send a message to.
12
16
  # If a string is given, it should be a partner name which has been registered
13
17
  # via a call to #add_partner.
@@ -45,6 +49,10 @@ module As2
45
49
  # * If content parameter is specified, file_name is only used to tell the
46
50
  # partner the original name of the file.
47
51
  #
52
+ # TODO: refactor to separate "build an outbound message" from "send an outbound message"
53
+ # main benefit would be allowing the test suite to be more straightforward.
54
+ # (wouldn't need webmock just to verify what kind of message we built...)
55
+ #
48
56
  # @param [String] file_name
49
57
  # @param [String] content
50
58
  # @param [String] content_type This is the MIME Content-Type describing the `content` param,
@@ -82,8 +90,11 @@ module As2
82
90
  file_name: file_name
83
91
  )
84
92
 
85
- cipher = OpenSSL::Cipher::AES256.new(:CBC) # default, but we might have to make this configurable
86
- encrypted = OpenSSL::PKCS7.encrypt([@partner.certificate], request_body, cipher)
93
+ encrypted = OpenSSL::PKCS7.encrypt(
94
+ [@partner.encryption_certificate],
95
+ request_body,
96
+ @partner.encryption_cipher_instance
97
+ )
87
98
 
88
99
  # > HTTP can handle binary data and so there is no need to use the
89
100
  # > content transfer encodings of MIME
@@ -257,7 +268,7 @@ module As2
257
268
  if mdn_content_type.start_with?('multipart/signed')
258
269
  result = parse_signed_mdn(
259
270
  multipart_signed_message: response_content,
260
- certificate: @partner.certificate
271
+ signing_certificate: @partner.signing_certificate
261
272
  )
262
273
  mdn_report = result[:mdn_report]
263
274
  report[:signature_verification_error] = result[:signature_verification_error]
@@ -314,7 +325,7 @@ module As2
314
325
  # * :mdn_mime_body [Mail::Message] The 'inner' MDN body, with signature removed
315
326
  # * :signature_verification_error [String] Any error which resulted when checking the
316
327
  # signature. If this is empty it means the signature was valid.
317
- def parse_signed_mdn(multipart_signed_message:, certificate:)
328
+ def parse_signed_mdn(multipart_signed_message:, signing_certificate:)
318
329
  smime = nil
319
330
 
320
331
  begin
@@ -347,7 +358,7 @@ module As2
347
358
  # based on As2::Message version
348
359
  # TODO: test cases based on valid/invalid responses. (response signed with wrong certificate, etc.)
349
360
  # See notes in As2::Message.verify for reasoning on flag usage
350
- smime.verify [certificate], OpenSSL::X509::Store.new, nil, OpenSSL::PKCS7::NOVERIFY | OpenSSL::PKCS7::NOINTERN
361
+ smime.verify [signing_certificate], OpenSSL::X509::Store.new, nil, OpenSSL::PKCS7::NOVERIFY | OpenSSL::PKCS7::NOINTERN
351
362
 
352
363
  signature_verification_error = smime.error_string
353
364
  else
@@ -383,7 +394,7 @@ module As2
383
394
  result = As2::Message.verify(
384
395
  content: content,
385
396
  signature_text: signature_text,
386
- certificate: @partner.certificate
397
+ signing_certificate: signing_certificate
387
398
  )
388
399
 
389
400
  signature_verification_error = result[:error]
data/lib/as2/config.rb CHANGED
@@ -12,7 +12,12 @@ module As2
12
12
  end
13
13
  end
14
14
 
15
- class Partner < Struct.new :name, :url, :certificate, :tls_verify_mode, :mdn_format, :outbound_format
15
+ class Partner < Struct.new :name, :url, :encryption_certificate, :encryption_cipher, :signing_certificate, :tls_verify_mode, :mdn_format, :outbound_format
16
+ def initialize
17
+ # set default.
18
+ self.encryption_cipher = 'aes-256-cbc'
19
+ end
20
+
16
21
  def url=(url)
17
22
  if url.kind_of? String
18
23
  self['url'] = URI.parse url
@@ -40,7 +45,30 @@ module As2
40
45
  end
41
46
 
42
47
  def certificate=(certificate)
43
- self['certificate'] = As2::Config.build_certificate(certificate)
48
+ cert = As2::Config.build_certificate(certificate)
49
+ self['encryption_certificate'] = cert
50
+ self['signing_certificate'] = cert
51
+ end
52
+
53
+ def encryption_certificate=(certificate)
54
+ self['encryption_certificate'] = As2::Config.build_certificate(certificate)
55
+ end
56
+
57
+ def encryption_cipher=(cipher)
58
+ cipher_s = cipher.to_s
59
+ valid_ciphers = As2::Client.valid_encryption_ciphers
60
+ if !valid_ciphers.include?(cipher_s)
61
+ raise ArgumentError, "encryption_cipher '#{cipher_s}' must be one of #{valid_ciphers.inspect}"
62
+ end
63
+ self['encryption_cipher'] = cipher_s
64
+ end
65
+
66
+ def encryption_cipher_instance
67
+ OpenSSL::Cipher.new(encryption_cipher)
68
+ end
69
+
70
+ def signing_certificate=(certificate)
71
+ self['signing_certificate'] = As2::Config.build_certificate(certificate)
44
72
  end
45
73
 
46
74
  # if set, will be used for SSL transmissions.
@@ -87,14 +115,18 @@ module As2
87
115
  unless partner.name
88
116
  raise 'Partner name is required'
89
117
  end
90
- unless partner.certificate
91
- raise 'Partner certificate is required'
118
+ unless partner.signing_certificate
119
+ raise 'Partner signing certificate is required'
120
+ end
121
+ unless partner.encryption_certificate
122
+ raise 'Partner encryption certificate is required'
92
123
  end
93
124
  unless partner.url
94
125
  raise 'Partner URL is required'
95
126
  end
96
127
  Config.partners[partner.name] = partner
97
- Config.store.add_cert partner.certificate
128
+ Config.store.add_cert partner.signing_certificate
129
+ Config.store.add_cert partner.encryption_certificate
98
130
  end
99
131
  end
100
132
 
@@ -123,6 +155,7 @@ module As2
123
155
  @partners ||= {}
124
156
  end
125
157
 
158
+ # TODO: deprecate this.
126
159
  def store
127
160
  @store ||= OpenSSL::X509::Store.new
128
161
  end
data/lib/as2/message.rb CHANGED
@@ -53,7 +53,7 @@ module As2
53
53
  # * :valid [boolean] was the verification successful or not?
54
54
  # * :error [String, nil] a verification error message.
55
55
  # will be empty when `valid` is true.
56
- def self.verify(content:, signature_text:, certificate:)
56
+ def self.verify(content:, signature_text:, signing_certificate:)
57
57
  begin
58
58
  signature = OpenSSL::PKCS7.new(signature_text)
59
59
 
@@ -76,9 +76,9 @@ module As2
76
76
  #
77
77
  # https://www.openssl.org/docs/manmaster/man3/PKCS7_verify.html
78
78
  #
79
- # we want this so we can be sure that the `partner_certificate` we supply
79
+ # we want this so we can be sure that the `signing_certificate` we supply
80
80
  # was actually used to sign the message. otherwise we could get a positive
81
- # verification even if `partner_certificate` didn't sign the message
81
+ # verification even if `signing_certificate` didn't sign the message
82
82
  # we're checking.
83
83
  #
84
84
  # ## NOVERIFY
@@ -87,9 +87,9 @@ module As2
87
87
  #
88
88
  # ie: we won't attempt to connect signer (in the first param) to a root
89
89
  # CA (in `store`, which is empty). alternately, we could instead remove
90
- # this flag, and add `partner_certificate` to `store`. but what's the point?
91
- # we'd only be verifying that `partner_certificate` is connected to `partner_certificate`.
92
- valid = signature.verify([certificate], store, content, OpenSSL::PKCS7::NOVERIFY | OpenSSL::PKCS7::NOINTERN)
90
+ # this flag, and add `signing_certificate` to `store`. but what's the point?
91
+ # we'd only be verifying that `signing_certificate` is connected to `signing_certificate`.
92
+ valid = signature.verify([signing_certificate], store, content, OpenSSL::PKCS7::NOVERIFY | OpenSSL::PKCS7::NOINTERN)
93
93
 
94
94
  # when `signature.verify` fails, signature.error_string will be populated.
95
95
  error = signature.error_string
@@ -121,7 +121,7 @@ module As2
121
121
  @decrypted_message ||= @pkcs7.decrypt @private_key, @public_certificate
122
122
  end
123
123
 
124
- def valid_signature?(partner_certificate)
124
+ def valid_signature?(partner_signing_certificate)
125
125
  content_type = mail.header_fields.find { |h| h.name == 'Content-Type' }.content_type
126
126
  # TODO: substantial overlap between this code & the fallback/rescue code in
127
127
  # As2::Client#verify_mdn_signature
@@ -149,7 +149,7 @@ module As2
149
149
  result = self.class.verify(
150
150
  content: content,
151
151
  signature_text: signature_text,
152
- certificate: partner_certificate
152
+ signing_certificate: partner_signing_certificate
153
153
  )
154
154
 
155
155
  output = result[:valid]
@@ -186,7 +186,7 @@ module As2
186
186
  retry_output = self.class.verify(
187
187
  content: content,
188
188
  signature_text: signature_text,
189
- certificate: partner_certificate
189
+ signing_certificate: partner_signing_certificate
190
190
  )
191
191
 
192
192
  if retry_output[:valid]
data/lib/as2/server.rb CHANGED
@@ -40,7 +40,7 @@ module As2
40
40
  request = Rack::Request.new(env)
41
41
  message = Message.new(request.body.read, @server_info.pkey, @server_info.certificate)
42
42
 
43
- unless message.valid_signature?(partner.certificate)
43
+ unless message.valid_signature?(partner.signing_certificate)
44
44
  if @signature_failure_handler
45
45
  @signature_failure_handler.call({
46
46
  env: env,
@@ -48,7 +48,7 @@ module As2
48
48
  verification_error: message.verification_error
49
49
  })
50
50
  else
51
- raise "Could not verify signature"
51
+ raise "Could not verify signature. #{message.verification_error}"
52
52
  end
53
53
  end
54
54
 
data/lib/as2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module As2
2
- VERSION = "0.9.0"
2
+ VERSION = "0.11.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: as2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OfficeLuv
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-08-28 00:00:00.000000000 Z
12
+ date: 2023-09-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mail