as2 0.3.0 → 0.4.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: 3c28a94ec5915cd8930ba91814dea748017f86d0cbe2be94719fa54df5e6774f
4
- data.tar.gz: 47239349434164df4c1833561b6942e458f9b919415d221b9ae376b4a28755fe
3
+ metadata.gz: '094edd2c08516e42061e5623b49e4b2f355a3ca3a19a6ed17bc2764ea1073f9c'
4
+ data.tar.gz: c7678c44a070eda090eec89d3e3bacfdc2a1400b880bb39d52f2ba3e08ffe300
5
5
  SHA512:
6
- metadata.gz: b7f77c9176b2f279d5a678f72494f746b687432487df3b2c1d338754b89e5942e9af4798360a39528514cb826cc923148ac4a812cc64d65b7f9f4a104eb71b2f
7
- data.tar.gz: efdc7d01786e56ad1aabbbd9c373aa9d835bb299c775423df0470e622916efe080a0abc3b7bc6173e39b80223824d3c3bb885b6fe8d6b93405329dcaf9f82fd5
6
+ metadata.gz: c4ecd6838eb1e62174c584638141ba0f2af696bfd906765a0f3ffd4928e03ed3d203e9160c2ce5731439e282283fbf2b511b891719c98eeff262834b9bb1ec11
7
+ data.tar.gz: 852b03c4eebc90ea86447a4074afa417556b89f8613851eb59364dc6f8b4f9f1904452da3c4160929992c2263085774ff6c8cdf46345a92e75411daf945b8123
@@ -0,0 +1,34 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: test suite
9
+
10
+ on:
11
+ push:
12
+ branches: '**'
13
+
14
+ jobs:
15
+ test:
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ ruby: ['2.5', '2.6', '2.7', '3.0', '3.1']
20
+
21
+ runs-on: ubuntu-latest
22
+
23
+ steps:
24
+ - uses: actions/checkout@v2
25
+ - name: Set up Ruby
26
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
27
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
28
+ uses: ruby/setup-ruby@v1
29
+ with:
30
+ ruby-version: ${{ matrix.ruby }}
31
+ - name: Install dependencies
32
+ run: bundle install
33
+ - name: Run tests
34
+ run: bundle exec rake test
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  certs
2
2
  .DS_Store
3
3
  Gemfile.lock
4
+ pkg
data/CHANGELOG.md CHANGED
@@ -1,9 +1,24 @@
1
+ ## 0.4.0, March 3, 2022
2
+
3
+ * client: correct MIC & signature verification when processing MDN response [#7](https://github.com/andjosh/as2/pull/7)
4
+ * also improves detection of successful & unsuccessful transmissions.
5
+ * client can transmit content which is not in a local file [#5](https://github.com/andjosh/as2/pull/5)
6
+ * also enables `As2::Client` and `As2::Server` can be used without reference to
7
+ the `As2::Config` global configuration.
8
+ * This allows certificate selection to be determined at runtime, making certificate
9
+ expiration & changeover much easier to orchestrate.
10
+
1
11
  ## 0.3.0, Dec 22, 2021
2
12
 
3
13
  * fix MIC calculation. [#1](https://github.com/andjosh/as2/pull/1)
4
14
  * allow loading of private key and certificates without local files. [#2](https://github.com/andjosh/as2/pull/2)
5
15
  * fix signature verification. [#3](https://github.com/andjosh/as2/pull/3)
6
16
 
17
+ ### breaking changes
18
+
19
+ * removed `As2::Message#original_message`
20
+ * removed `As2::Server::HEADER_MAP`
21
+
7
22
  ## prior to 0.3.0
8
23
 
9
24
  Initial work by [@andjosh](https://github.com/andjosh) and [@datanoise](https://github.com/datanoise).
data/Gemfile CHANGED
@@ -2,3 +2,12 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in as2.gemspec
4
4
  gemspec
5
+
6
+ # from https://github.com/rails/rails/pull/42308/files
7
+ if RUBY_VERSION >= "3.1"
8
+ # net-smtp, net-imap and net-pop were removed from default gems in Ruby 3.1, but is used by the `mail` gem.
9
+ # So we need to add them as dependencies until `mail` is fixed: https://github.com/mikel/mail/pull/1439
10
+ gem "net-smtp", require: false
11
+ gem "net-imap", require: false
12
+ gem "net-pop", require: false
13
+ end
data/README.md CHANGED
@@ -4,6 +4,40 @@ This is a proof of concept implementation of AS2 protocol: http://www.ietf.org/r
4
4
 
5
5
  Tested with the mendelson AS2 implementation from http://as2.mendelson-e-c.com
6
6
 
7
+ ## Build Status
8
+
9
+ [![Test Suite](https://github.com/andjosh/as2/actions/workflows/test.yml/badge.svg)](https://github.com/andjosh/as2/actions/workflows/test.yml)
10
+
11
+ ## Known Limitations
12
+
13
+ These limitations may be removed over time as demand (and pull requests!) come
14
+ along.
15
+
16
+ 1. RFC defines a number of optional features that partners can pick and choose
17
+ amongst. We currently have hard-coded options for many of these. Our current
18
+ choices are likely the most common ones in use, but we do not offer all the
19
+ configuration options needed for a fully-compliant implementation. https://datatracker.ietf.org/doc/html/rfc4130#section-2.4.2
20
+ 1. Encrypted or Unencrypted Data: We assume all messages are encrypted. An
21
+ error will result if partner sends us an unencrypted message.
22
+ 2. Signed or Unsigned Data: We error if partner sends an unsigned message.
23
+ Partners can request unsigned MDNs, but we always send signed MDNs.
24
+ 3. Optional Use of Receipt: We always send a receipt.
25
+ 4. Use of Synchronous or Asynchronous Receipts: We do not support asynchronous
26
+ delivery of MDNs.
27
+ 5. Security Formatting: We should be reasonably compliant here.
28
+ 6. Hash Function, Message Digest Choices: We currently always use sha1. If a
29
+ partner asks for a different algorithm, we'll always use sha1 and partner
30
+ will see a MIC verification failure. AS2 RFC specifically prefers sha1 and
31
+ mentions md5. Mendelson AS2 server supports a number of other algorithms.
32
+ (sha256, sha512, etc)
33
+ 2. Payload bodies (typically EDI files) can be binary or base64 encoded. We
34
+ error if the body is not base64-encoded.
35
+ 3. Payload bodies can have a few different mime types. We expect only
36
+ `application/EDI-Consent`. We're unable to receive content that has any other
37
+ mime type. https://datatracker.ietf.org/doc/html/rfc1767#section-1
38
+ 4. AS2 partners may agree to use separate certificates for data encryption and data signing.
39
+ We do not support separate certificates for these purposes.
40
+
7
41
  ## Installation
8
42
 
9
43
  Add this line to your application's Gemfile:
data/as2.gemspec CHANGED
@@ -6,8 +6,8 @@ require 'as2/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "as2"
8
8
  spec.version = As2::VERSION
9
- spec.authors = ["OfficeLuv", "Transfix"]
10
- spec.email = ["development@officeluv.com", "alexdean@transfix.io"]
9
+ spec.authors = ["OfficeLuv", "Alex Dean"]
10
+ spec.email = ["development@officeluv.com", "github@mostlyalex.com"]
11
11
 
12
12
  spec.summary = %q{Simple AS2 server and client implementation}
13
13
  spec.description = %q{Simple AS2 server and client implementation. Follows the AS2 implementation from http://as2.mendelson-e-c.com}
@@ -34,4 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency "rake", ">= 10.0"
35
35
  spec.add_development_dependency "thin"
36
36
  spec.add_development_dependency "minitest"
37
+ spec.add_development_dependency "minitest-focus"
38
+ spec.add_development_dependency "webmock"
39
+ spec.add_development_dependency "pry"
37
40
  end
@@ -0,0 +1,40 @@
1
+ module As2
2
+ class Client
3
+ class Result
4
+ attr_reader :response, :mic_matched, :mid_matched, :body, :disposition, :signature_verification_error, :exception, :outbound_message_id
5
+
6
+ def initialize(response:, mic_matched:, mid_matched:, body:, disposition:, signature_verification_error:, exception:, outbound_message_id:)
7
+ @response = response
8
+ @mic_matched = mic_matched
9
+ @mid_matched = mid_matched
10
+ @body = body
11
+ @disposition = disposition
12
+ @signature_verification_error = signature_verification_error
13
+ @exception = exception
14
+ @outbound_message_id = outbound_message_id
15
+ end
16
+
17
+ def signature_verified
18
+ self.signature_verification_error.nil?
19
+ end
20
+
21
+ # legacy name. accessor for backwards-compatibility.
22
+ def disp_code
23
+ self.disposition
24
+ end
25
+
26
+ def success
27
+ # 'processed' is good (though may include warnings.)
28
+ # 'processed/error' is not.
29
+ downcased_disposition = self.disposition.to_s.downcase
30
+
31
+ # TODO: we'll never have success if MDN is unsigned.
32
+ self.signature_verified &&
33
+ self.mic_matched &&
34
+ self.mid_matched &&
35
+ downcased_disposition.include?('processed') &&
36
+ !downcased_disposition.include?('processed/error')
37
+ end
38
+ end
39
+ end
40
+ end
data/lib/as2/client.rb CHANGED
@@ -2,96 +2,135 @@ require 'net/http'
2
2
 
3
3
  module As2
4
4
  class Client
5
- def initialize(partner_name)
6
- @partner = Config.partners[partner_name]
7
- unless @partner
8
- raise "Partner #{partner_name} is not registered"
5
+ attr_reader :partner, :server_info
6
+
7
+ # @param [As2::Config::Partner,String] partner The partner to send a message to.
8
+ # If a string is given, it should be a partner name which has been registered
9
+ # via a call to #add_partner.
10
+ # @param [As2::Config::ServerInfo,nil] server_info The server info used to identify
11
+ # this client to the partner. If omitted, the main As2::Config.server_info will be used.
12
+ def initialize(partner, server_info: nil)
13
+ if partner.is_a?(As2::Config::Partner)
14
+ @partner = partner
15
+ else
16
+ @partner = Config.partners[partner]
17
+ unless @partner
18
+ raise "Partner #{partner} is not registered"
19
+ end
9
20
  end
10
- @info = Config.server_info
21
+
22
+ @server_info = server_info || Config.server_info
11
23
  end
12
24
 
13
- Result = Struct.new :success, :response, :mic_matched, :mid_matched, :body, :disp_code
14
-
15
- def send_file(file_name)
16
- http = Net::HTTP.new(@partner.url.host, @partner.url.port)
17
- http.use_ssl = @partner.url.scheme == 'https'
18
- # http.set_debug_output $stderr
19
- http.start do
20
- req = Net::HTTP::Post.new @partner.url.path
21
- req['AS2-Version'] = '1.2'
22
- req['AS2-From'] = @info.name
23
- req['AS2-To'] = @partner.name
24
- req['Subject'] = 'AS2 EDI Transaction'
25
- req['Content-Type'] = 'application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m'
26
- req['Disposition-Notification-To'] = @info.url.to_s
27
- req['Disposition-Notification-Options'] = 'signed-receipt-protocol=optional, pkcs7-signature; signed-receipt-micalg=optional, sha1'
28
- req['Content-Disposition'] = 'attachment; filename="smime.p7m"'
29
- req['Recipient-Address'] = @info.url.to_s
30
- req['Content-Transfer-Encoding'] = 'base64'
31
- req['Message-ID'] = "<#{@info.name}-#{Time.now.strftime('%Y%m%d%H%M%S')}@#{@info.url.host}>"
32
-
33
- body = StringIO.new
34
- body.puts "Content-Type: application/EDI-Consent"
35
- body.puts "Content-Transfer-Encoding: base64"
36
- body.puts "Content-Disposition: attachment; filename=#{file_name}"
37
- body.puts
38
- body.puts [File.read(file_name)].pack("m*")
39
-
40
- mic = OpenSSL::Digest::SHA1.base64digest(body.string.gsub(/\n/, "\r\n"))
41
-
42
- pkcs7 = OpenSSL::PKCS7.sign @info.certificate, @info.pkey, body.string
43
- pkcs7.detached = true
44
- smime_signed = OpenSSL::PKCS7.write_smime pkcs7, body.string
45
- pkcs7 = OpenSSL::PKCS7.encrypt [@partner.certificate], smime_signed
46
- smime_encrypted = OpenSSL::PKCS7.write_smime pkcs7
47
-
48
- req.body = smime_encrypted.sub(/^.+?\n\n/m, '')
49
-
50
- resp = http.request(req)
51
-
52
- success = resp.code == '200'
53
- mic_matched = false
54
- mid_matched = false
55
- disp_code = nil
56
- body = nil
57
- if success
58
- body = resp.body
59
-
60
- smime = OpenSSL::PKCS7.read_smime "Content-Type: #{resp['Content-Type']}\r\n#{body}"
61
- smime.verify [@partner.certificate], Config.store
25
+ # Send a file to a partner
26
+ #
27
+ # * If the content parameter is omitted, then `file_name` must be a path
28
+ # to a local file, whose contents will be sent to the partner.
29
+ # * If content parameter is specified, file_name is only used to tell the
30
+ # partner the original name of the file.
31
+ #
32
+ # @param [String] file_name
33
+ # @param [String] content
34
+ # @return [As2::Client::Result]
35
+ def send_file(file_name, content: nil)
36
+ outbound_message_id = As2.generate_message_id(@server_info)
37
+
38
+ req = Net::HTTP::Post.new @partner.url.path
39
+ req['AS2-Version'] = '1.2'
40
+ req['AS2-From'] = @server_info.name
41
+ req['AS2-To'] = @partner.name
42
+ req['Subject'] = 'AS2 EDI Transaction'
43
+ req['Content-Type'] = 'application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m'
44
+ req['Disposition-Notification-To'] = @server_info.url.to_s
45
+ req['Disposition-Notification-Options'] = 'signed-receipt-protocol=optional, pkcs7-signature; signed-receipt-micalg=optional, sha1'
46
+ req['Content-Disposition'] = 'attachment; filename="smime.p7m"'
47
+ req['Recipient-Address'] = @server_info.url.to_s
48
+ req['Content-Transfer-Encoding'] = 'base64'
49
+ req['Message-ID'] = outbound_message_id
50
+
51
+ document_content = content || File.read(file_name)
52
+
53
+ document_payload = "Content-Type: application/EDI-Consent\r\n"
54
+ document_payload << "Content-Transfer-Encoding: base64\r\n"
55
+ document_payload << "Content-Disposition: attachment; filename=#{file_name}\r\n"
56
+ document_payload << "\r\n"
57
+ document_payload << Base64.strict_encode64(document_content)
58
+
59
+ signature = OpenSSL::PKCS7.sign @server_info.certificate, @server_info.pkey, document_payload
60
+ signature.detached = true
61
+ container = OpenSSL::PKCS7.write_smime signature, document_payload
62
+ encrypted = OpenSSL::PKCS7.encrypt [@partner.certificate], container
63
+ smime_encrypted = OpenSSL::PKCS7.write_smime encrypted
64
+
65
+ req.body = smime_encrypted.sub(/^.+?\n\n/m, '')
66
+
67
+ resp = nil
68
+ signature_verification_error = :not_checked
69
+ exception = nil
70
+ mic_matched = nil
71
+ mid_matched = nil
72
+ disposition = nil
73
+ plain_text_body = nil
74
+
75
+ begin
76
+ http = Net::HTTP.new(@partner.url.host, @partner.url.port)
77
+ http.use_ssl = @partner.url.scheme == 'https'
78
+ # http.set_debug_output $stderr
79
+ http.start do
80
+ resp = http.request(req)
81
+ end
82
+
83
+ if resp.code == '200'
84
+ response_content = "Content-Type: #{resp['Content-Type']}\r\n#{resp.body}"
85
+ smime = OpenSSL::PKCS7.read_smime response_content
86
+ # based on As2::Message version
87
+ # TODO: test cases based on valid/invalid responses. (response signed with wrong certificate, etc.)
88
+ smime.verify [@partner.certificate], OpenSSL::X509::Store.new, nil, OpenSSL::PKCS7::NOVERIFY | OpenSSL::PKCS7::NOINTERN
89
+ signature_verification_error = smime.error_string
62
90
 
63
91
  mail = Mail.new smime.data
64
92
  mail.parts.each do |part|
65
93
  case part.content_type
66
94
  when 'text/plain'
67
- body = part.body
95
+ plain_text_body = part.body
68
96
  when 'message/disposition-notification'
97
+ # "The rules for constructing the AS2-disposition-notification content..."
98
+ # https://datatracker.ietf.org/doc/html/rfc4130#section-7.4.3
99
+
69
100
  options = {}
101
+ # TODO: can we use Mail built-ins for this?
70
102
  part.body.to_s.lines.each do |line|
71
103
  if line =~ /^([^:]+): (.+)$/
72
104
  options[$1] = $2
73
105
  end
74
106
  end
75
107
 
76
- if req['Message-ID'] == options['Original-Message-ID']
77
- mid_matched = true
78
- else
79
- success = false
80
- end
81
-
82
- if options['Received-Content-MIC'].start_with?(mic)
83
- mic_matched = true
84
- else
85
- success = false
86
- end
108
+ disposition = options['Disposition']
109
+ mid_matched = req['Message-ID'] == options['Original-Message-ID']
87
110
 
88
- disp_code = options['Disposition']
89
- success = disp_code.end_with?('processed')
111
+ # do mic calc using the algorithm specified by server.
112
+ # (even if we specify sha1, server may send back MIC using a different algo.)
113
+ received_mic, micalg = options['Received-Content-MIC'].split(',').map(&:strip)
114
+ micalg ||= 'sha1'
115
+ mic = As2::DigestSelector.for_code(micalg).base64digest(document_payload)
116
+ mic_matched = received_mic == mic
90
117
  end
91
118
  end
92
119
  end
93
- Result.new success, resp, mic_matched, mid_matched, body, disp_code
120
+ rescue => e
121
+ exception = e
94
122
  end
123
+
124
+ Result.new(
125
+ response: resp,
126
+ mic_matched: mic_matched,
127
+ mid_matched: mid_matched,
128
+ body: plain_text_body,
129
+ disposition: disposition,
130
+ signature_verification_error: signature_verification_error,
131
+ exception: exception,
132
+ outbound_message_id: outbound_message_id
133
+ )
95
134
  end
96
135
  end
97
136
  end
data/lib/as2/config.rb CHANGED
@@ -97,6 +97,11 @@ module As2
97
97
  def store
98
98
  @store ||= OpenSSL::X509::Store.new
99
99
  end
100
+
101
+ def reset!
102
+ @partners = {}
103
+ @store = OpenSSL::X509::Store.new
104
+ end
100
105
  end
101
106
  end
102
107
  end
@@ -0,0 +1,23 @@
1
+ require 'openssl'
2
+
3
+ module As2
4
+ class DigestSelector
5
+ @map = {
6
+ 'sha1' => OpenSSL::Digest::SHA1,
7
+ 'sha256' => OpenSSL::Digest::SHA256,
8
+ 'sha384' => OpenSSL::Digest::SHA384,
9
+ 'sha512' => OpenSSL::Digest::SHA512,
10
+ 'md5' => OpenSSL::Digest::MD5
11
+ }
12
+
13
+ def self.valid_codes
14
+ @map.keys
15
+ end
16
+
17
+ def self.for_code(code)
18
+ normalized = code.strip.downcase.gsub(/[^a-z0-9]/, '')
19
+
20
+ @map[normalized] || OpenSSL::Digest::SHA1
21
+ end
22
+ end
23
+ end
data/lib/as2/message.rb CHANGED
@@ -29,7 +29,10 @@ module As2
29
29
  # https://datatracker.ietf.org/doc/html/rfc3851#section-3.4.3.1
30
30
 
31
31
  # TODO: more robust detection of content vs signature (if they're ever out of order).
32
- content = mail.parts[0].raw_source.strip
32
+ content = mail.parts[0].raw_source
33
+ # remove any leading \r\n characters (between headers & body i think).
34
+ content = content.gsub(/\A\s+/, '')
35
+
33
36
  signature = OpenSSL::PKCS7.new(mail.parts[1].body.to_s)
34
37
 
35
38
  # using an empty CA store. see notes on NOVERIFY flag below.
@@ -77,13 +80,14 @@ module As2
77
80
  end
78
81
 
79
82
  def mic
83
+ # TODO: could use As2::DigestSelector if a different algo is needed.
80
84
  OpenSSL::Digest::SHA1.base64digest(attachment.raw_source.strip)
81
85
  end
82
86
 
83
87
  # Return the attached file, use .filename and .body on the return value
84
88
  def attachment
85
89
  if mail.has_attachments?
86
- mail.attachments.find{|a| a.content_type == "application/edi-consent"}
90
+ mail.parts.find{|a| a.content_type == "application/edi-consent"}
87
91
  else
88
92
  mail
89
93
  end
@@ -5,6 +5,7 @@ module As2
5
5
  @parts = []
6
6
  @body = ""
7
7
  @headers = {}
8
+ @id = nil
8
9
  end
9
10
 
10
11
  def [](name)
data/lib/as2/server.rb CHANGED
@@ -8,28 +8,36 @@ module As2
8
8
  class Server
9
9
  attr_accessor :logger
10
10
 
11
- def initialize(options = {}, &block)
11
+ # @param [As2::Config::ServerInfo] server_info Config used for naming of this
12
+ # server and key/certificate selection. If omitted, the main As2::Config.server_info is used.
13
+ # @param [As2::Config::Partner] partner Which partner to receive messages from.
14
+ # If omitted, the partner is determined by incoming HTTP headers.
15
+ # @param [Proc] on_signature_failure A proc which will be called if signature verification fails.
16
+ # @param [Proc] block A proc which will be called with file_name and file content.
17
+ def initialize(server_info: nil, partner: nil, on_signature_failure: nil, &block)
12
18
  @block = block
13
- @info = Config.server_info
14
- @options = options
19
+ @server_info = server_info || Config.server_info
20
+ @partner = partner
21
+ @signature_failure_handler = on_signature_failure
15
22
  end
16
23
 
17
24
  def call(env)
18
- if env['HTTP_AS2_TO'] != @info.name
25
+ if env['HTTP_AS2_TO'] != @server_info.name
19
26
  return send_error(env, "Invalid destination name #{env['HTTP_AS2_TO']}")
20
27
  end
21
28
 
22
- partner = Config.partners[env['HTTP_AS2_FROM']]
23
- unless partner
29
+ partner = @partner || Config.partners[env['HTTP_AS2_FROM']]
30
+
31
+ if !partner || env['HTTP_AS2_FROM'] != partner.name
24
32
  return send_error(env, "Invalid partner name #{env['HTTP_AS2_FROM']}")
25
33
  end
26
34
 
27
35
  request = Rack::Request.new(env)
28
- message = Message.new(request.body.read, @info.pkey, @info.certificate)
36
+ message = Message.new(request.body.read, @server_info.pkey, @server_info.certificate)
29
37
 
30
38
  unless message.valid_signature?(partner.certificate)
31
- if @options[:on_signature_failure]
32
- @options[:on_signature_failure].call({
39
+ if @signature_failure_handler
40
+ @signature_failure_handler.call({
33
41
  env: env,
34
42
  smime_string: message.decrypted_message,
35
43
  verification_error: message.verification_error
@@ -50,17 +58,6 @@ module As2
50
58
  send_mdn(env, message.mic)
51
59
  end
52
60
 
53
- private
54
-
55
- def logger(env)
56
- @logger ||= Logger.new env['rack.errors']
57
- end
58
-
59
- def send_error(env, msg)
60
- logger(env).error msg
61
- send_mdn env, nil, msg
62
- end
63
-
64
61
  def send_mdn(env, mic, failed = nil)
65
62
  report = MimeGenerator::Part.new
66
63
  report['Content-Type'] = 'multipart/report; report-type=disposition-notification'
@@ -77,9 +74,9 @@ module As2
77
74
  notification['Content-Transfer-Encoding'] = '7bit'
78
75
 
79
76
  options = {
80
- 'Reporting-UA' => @info.name,
81
- 'Original-Recipient' => "rfc822; #{@info.name}",
82
- 'Final-Recipient' => "rfc822; #{@info.name}",
77
+ 'Reporting-UA' => @server_info.name,
78
+ 'Original-Recipient' => "rfc822; #{@server_info.name}",
79
+ 'Final-Recipient' => "rfc822; #{@server_info.name}",
83
80
  'Original-Message-ID' => env['HTTP_MESSAGE_ID']
84
81
  }
85
82
  if failed
@@ -96,7 +93,7 @@ module As2
96
93
 
97
94
  report.write msg_out
98
95
 
99
- pkcs7 = OpenSSL::PKCS7.sign @info.certificate, @info.pkey, msg_out.string
96
+ pkcs7 = OpenSSL::PKCS7.sign @server_info.certificate, @server_info.pkey, msg_out.string
100
97
  pkcs7.detached = true
101
98
  smime_signed = OpenSSL::PKCS7.write_smime pkcs7, msg_out.string
102
99
 
@@ -106,13 +103,24 @@ module As2
106
103
  headers = {}
107
104
  headers['Content-Type'] = content_type
108
105
  headers['MIME-Version'] = '1.0'
109
- headers['Message-ID'] = "<#{@info.name}-#{Time.now.strftime('%Y%m%d%H%M%S')}@#{@info.domain}>"
110
- headers['AS2-From'] = @info.name
106
+ headers['Message-ID'] = As2.generate_message_id(@server_info)
107
+ headers['AS2-From'] = @server_info.name
111
108
  headers['AS2-To'] = env['HTTP_AS2_FROM']
112
109
  headers['AS2-Version'] = '1.2'
113
110
  headers['Connection'] = 'close'
114
111
 
115
112
  [200, headers, ["\r\n" + smime_signed]]
116
113
  end
114
+
115
+ private
116
+
117
+ def logger(env)
118
+ @logger ||= Logger.new env['rack.errors']
119
+ end
120
+
121
+ def send_error(env, msg)
122
+ logger(env).error msg
123
+ send_mdn env, nil, msg
124
+ end
117
125
  end
118
126
  end
data/lib/as2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module As2
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/as2.rb CHANGED
@@ -1,12 +1,23 @@
1
1
  require 'openssl'
2
2
  require 'mail'
3
+ require 'securerandom'
3
4
  require 'as2/config'
4
5
  require 'as2/server'
5
6
  require 'as2/client'
7
+ require 'as2/client/result'
8
+ require 'as2/digest_selector'
6
9
  require "as2/version"
7
10
 
8
11
  module As2
9
12
  def self.configure(&block)
10
13
  Config.configure(&block)
11
14
  end
15
+
16
+ def self.reset_config!
17
+ Config.reset!
18
+ end
19
+
20
+ def self.generate_message_id(server_info)
21
+ "<#{server_info.name}-#{Time.now.strftime('%Y%m%d-%H%M%S')}-#{SecureRandom.uuid}@#{server_info.domain}>"
22
+ end
12
23
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: as2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OfficeLuv
8
- - Transfix
8
+ - Alex Dean
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-12-22 00:00:00.000000000 Z
12
+ date: 2022-03-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mail
@@ -95,15 +95,58 @@ dependencies:
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: minitest-focus
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: webmock
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: pry
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
98
140
  description: Simple AS2 server and client implementation. Follows the AS2 implementation
99
141
  from http://as2.mendelson-e-c.com
100
142
  email:
101
143
  - development@officeluv.com
102
- - alexdean@transfix.io
144
+ - github@mostlyalex.com
103
145
  executables: []
104
146
  extensions: []
105
147
  extra_rdoc_files: []
106
148
  files:
149
+ - ".github/workflows/test.yml"
107
150
  - ".gitignore"
108
151
  - CHANGELOG.md
109
152
  - Gemfile
@@ -118,7 +161,9 @@ files:
118
161
  - lib/as2.rb
119
162
  - lib/as2/base64_helper.rb
120
163
  - lib/as2/client.rb
164
+ - lib/as2/client/result.rb
121
165
  - lib/as2/config.rb
166
+ - lib/as2/digest_selector.rb
122
167
  - lib/as2/message.rb
123
168
  - lib/as2/mime_generator.rb
124
169
  - lib/as2/server.rb