as2 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7cb3eb0153dae7ea4c9ddba6a2843c86982fb6a035bddf9f0638655d0e3c3cda
4
+ data.tar.gz: b4a7d09a451eb131ca1febc572e243144014029c9ac82a35ea43da12db5d3519
5
+ SHA512:
6
+ metadata.gz: ea8bb601045953e40aca4a13193f8377b2fdeacae2d0b143adfda5e434025d7ed38b7db3aca8e11612f38cb431fab80d590d13923c29a52c51ea118c2b2c2ce1
7
+ data.tar.gz: aa7b396aa6de6271ae86bc8094ed72f7dce59ed996198f1f31f21c3438de585cee5083a466720e593f80c85474f24fb276f31faa0be8b70813248efc8129db8d
@@ -0,0 +1,2 @@
1
+ certs
2
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in as2.gemspec
4
+ gemspec
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ as2 (0.2.4)
5
+ mail
6
+ rack
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ daemons (1.2.3)
12
+ eventmachine (1.0.8)
13
+ mail (2.6.3)
14
+ mime-types (>= 1.16, < 3)
15
+ mime-types (2.6.2)
16
+ minitest (5.8.1)
17
+ rack (1.6.4)
18
+ rake (10.4.2)
19
+ thin (1.6.4)
20
+ daemons (~> 1.0, >= 1.0.9)
21
+ eventmachine (~> 1.0, >= 1.0.4)
22
+ rack (~> 1.0)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ as2!
29
+ bundler (~> 1.10)
30
+ minitest
31
+ rake (~> 10.0)
32
+ thin
33
+
34
+ BUNDLED WITH
35
+ 1.10.6
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Andrew Fecheyr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,60 @@
1
+ # As2
2
+
3
+ This is a proof of concept implementation of AS2 protocol: http://www.ietf.org/rfc/rfc4130.txt.
4
+
5
+ Tested with the mendelson AS2 implementation from http://as2.mendelson-e-c.com
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'as2'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install as2
22
+
23
+ ## Usage
24
+
25
+ Generate self signed server certificate:
26
+
27
+ ### One step
28
+
29
+ `openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365`
30
+
31
+ ### Multi step
32
+
33
+ 1. Generate a key ` openssl genrsa -des3 -out server.key 1024 `
34
+ 2. Copy the protected key ` cp server.key server.key.org `
35
+ 3. Remove the passphrase ` openssl rsa -in server.key.org -out server.key `
36
+ 4. Generate signing request ` openssl req -new -key server.key -out server.csr `
37
+ 5. Sign the request with your key ` openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt `
38
+
39
+ ## Development
40
+
41
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
42
+
43
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
44
+
45
+ You can run a local server with `bundle exec ruby examples/server.rb` and send it a file with `bundle exec ruby examples/client.rb <file>`. You may need to generate new certificates under `test/certificates` first (using `localhost` as your common name).
46
+
47
+ ## Contributing
48
+
49
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/as2.
50
+
51
+
52
+ ## License
53
+
54
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
55
+
56
+ ## Acknowledgments
57
+
58
+ Original implementation by:
59
+ - [andruby](https://github.com/andruby)
60
+ - [datanoise](https://github.com/datanoise)
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'as2/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "as2"
8
+ spec.version = As2::VERSION
9
+ spec.authors = ["OfficeLuv"]
10
+ spec.email = ["development@officeluv.com"]
11
+
12
+ spec.summary = %q{Simple AS2 server and client implementation}
13
+ spec.description = %q{Simple AS2 server and client implementation. Follows the AS2 implementation from http://as2.mendelson-e-c.com}
14
+ spec.homepage = "https://github.com/officeluv/as2"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org/'
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "mail"
31
+ spec.add_dependency "rack"
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.10"
34
+ spec.add_development_dependency "rake", "~> 10.0"
35
+ spec.add_development_dependency "thin"
36
+ spec.add_development_dependency "minitest"
37
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "as2"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,19 @@
1
+ require 'as2'
2
+ # require 'pry'
3
+
4
+ As2.configure do |conf|
5
+ conf.name = 'MyClient'
6
+ conf.url = 'http://localhost:8080/as2/HttpReceiver'
7
+ conf.certificate = 'test/certificates/client.crt'
8
+ conf.pkey = 'test/certificates/client.key'
9
+ conf.domain = 'mydomain.com'
10
+ conf.add_partner do |partner|
11
+ partner.name = 'MyServer'
12
+ partner.url = 'http://localhost:3000/as2'
13
+ partner.certificate = 'test/certificates/server.crt'
14
+ end
15
+ end
16
+
17
+ client = As2::Client.new 'MyServer'
18
+ result = client.send_file(ARGV.first)
19
+ p result
@@ -0,0 +1,32 @@
1
+ require 'as2'
2
+ require 'rack'
3
+
4
+ As2.configure do |conf|
5
+ conf.name = 'MyServer'
6
+ conf.url = 'http://localhost:3000/as2'
7
+ conf.certificate = 'test/certificates/server.crt'
8
+ conf.pkey = 'test/certificates/server.key'
9
+ conf.domain = 'mydomain.com'
10
+ conf.add_partner do |partner|
11
+ partner.name = 'MyClient'
12
+ partner.url = 'http://localhost:8080/as2/HttpReceiver'
13
+ partner.certificate = 'test/certificates/client.crt'
14
+ end
15
+ end
16
+
17
+ handler = As2::Server.new do |filename, body|
18
+ puts "SUCCESSFUL DOWNLOAD"
19
+ puts "FILENAME: #{filename}"
20
+ puts
21
+ puts body
22
+ end
23
+
24
+ builder = Rack::Builder.new do
25
+ use Rack::CommonLogger
26
+ map '/as2' do
27
+ run handler
28
+ end
29
+ end
30
+
31
+ puts "As2 version: #{As2::VERSION}"
32
+ Rack::Handler::Thin.run builder, Port: 3000
@@ -0,0 +1,12 @@
1
+ require 'openssl'
2
+ require 'mail'
3
+ require 'as2/config'
4
+ require 'as2/server'
5
+ require 'as2/client'
6
+ require "as2/version"
7
+
8
+ module As2
9
+ def self.configure(&block)
10
+ Config.configure(&block)
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ require 'base64'
2
+
3
+ module As2
4
+ module Base64Helper
5
+ # Will base64 encoded string, unless it already is base64 encoded
6
+ def self.ensure_base64(string)
7
+ begin
8
+ # If string is not base64 encoded, this will raise an ArgumentError
9
+ Base64.strict_decode64(string.gsub("\n",""))
10
+ return string
11
+ rescue ArgumentError
12
+ # The string is not yet base64 encoded
13
+ return Base64.encode64(string)
14
+ end
15
+ end
16
+
17
+ # If the multipart body is binary encoded, replace it with base64 encoded version
18
+ def self.ensure_body_base64(multipart)
19
+ boundary = multipart.scan(/boundary="([^"]*)"/)[0][0]
20
+ boundary_split = Regexp.escape("--#{boundary}")
21
+ parts = multipart.split(/^#{boundary_split}-*\s*$/)
22
+ signature = parts[2]
23
+ transfer_encoding = signature.scan(/Content-Transfer-Encoding: (.*)/)[0][0].strip
24
+ if transfer_encoding == 'binary'
25
+ header, body = signature.split(/^\s*$/,2).map(&:lstrip)
26
+ body_base64 = Base64.encode64(body)
27
+ new_header = header.sub('Content-Transfer-Encoding: binary', 'Content-Transfer-Encoding: base64')
28
+ parts[2] = new_header + "\r\n" + body_base64
29
+ new_multipart = parts.join("--#{boundary}\r\n") + "--#{boundary}--\r\n"
30
+ return new_multipart
31
+ else
32
+ return multipart
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,97 @@
1
+ require 'net/http'
2
+
3
+ module As2
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"
9
+ end
10
+ @info = Config.server_info
11
+ end
12
+
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
62
+
63
+ mail = Mail.new smime.data
64
+ mail.parts.each do |part|
65
+ case part.content_type
66
+ when 'text/plain'
67
+ body = part.body
68
+ when 'message/disposition-notification'
69
+ options = {}
70
+ part.body.to_s.lines.each do |line|
71
+ if line =~ /^([^:]+): (.+)$/
72
+ options[$1] = $2
73
+ end
74
+ end
75
+
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
87
+
88
+ disp_code = options['Disposition']
89
+ success = disp_code.end_with?('processed')
90
+ end
91
+ end
92
+ end
93
+ Result.new success, resp, mic_matched, mid_matched, body, disp_code
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,82 @@
1
+ require 'uri'
2
+ module As2
3
+ module Config
4
+ class Partner < Struct.new :name, :url, :certificate
5
+ def url=(url)
6
+ if url.kind_of? String
7
+ self['url'] = URI.parse url
8
+ else
9
+ self['url'] = url
10
+ end
11
+ end
12
+
13
+ def certificate=(certificate)
14
+ self['certificate'] = OpenSSL::X509::Certificate.new File.read(certificate)
15
+ end
16
+ end
17
+
18
+ class ServerInfo < Struct.new :name, :url, :certificate, :pkey, :domain
19
+ def url=(url)
20
+ if url.kind_of? String
21
+ self['url'] = URI.parse url
22
+ else
23
+ self['url'] = url
24
+ end
25
+ end
26
+
27
+ def certificate=(certificate)
28
+ self['certificate'] = OpenSSL::X509::Certificate.new File.read(certificate)
29
+ end
30
+
31
+ def pkey=(pkey)
32
+ self['pkey'] = OpenSSL::PKey.read File.read(pkey)
33
+ end
34
+
35
+ def add_partner
36
+ partner = Partner.new
37
+ yield partner
38
+ unless partner.name
39
+ raise 'Partner name is required'
40
+ end
41
+ unless partner.certificate
42
+ raise 'Partner certificate is required'
43
+ end
44
+ unless partner.url
45
+ raise 'Partner URL is required'
46
+ end
47
+ Config.partners[partner.name] = partner
48
+ Config.store.add_cert partner.certificate
49
+ end
50
+ end
51
+
52
+ class << self
53
+ attr_reader :server_info
54
+
55
+ def configure
56
+ @server_info ||= ServerInfo.new
57
+ yield @server_info
58
+ unless @server_info.name
59
+ raise 'Your Partner name is required'
60
+ end
61
+ unless @server_info.certificate
62
+ raise 'Your certificate is required'
63
+ end
64
+ unless @server_info.url
65
+ raise 'Your URL is required'
66
+ end
67
+ unless @server_info.domain
68
+ raise 'Your domain name is required'
69
+ end
70
+ store.add_cert @server_info.certificate
71
+ end
72
+
73
+ def partners
74
+ @partners ||= {}
75
+ end
76
+
77
+ def store
78
+ @store ||= OpenSSL::X509::Store.new
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,49 @@
1
+ require 'as2/base64_helper'
2
+
3
+ module As2
4
+ class Message
5
+ attr_reader :original_message
6
+
7
+ def initialize(message, private_key, public_certificate)
8
+ @original_message = message
9
+ @private_key = private_key
10
+ @public_certificate = public_certificate
11
+ end
12
+
13
+ def decrypted_message
14
+ @decrypted_message ||= decrypt_smime(original_message)
15
+ end
16
+
17
+ def valid_signature?(partner_certificate)
18
+ store = OpenSSL::X509::Store.new
19
+ store.add_cert(partner_certificate)
20
+
21
+ smime = Base64Helper.ensure_body_base64(decrypted_message)
22
+ message = read_smime(smime)
23
+ message.verify [partner_certificate], store
24
+ end
25
+
26
+ # Return the attached file, use .filename and .body on the return value
27
+ def attachment
28
+ if mail.has_attachments?
29
+ mail.attachments.find{|a| a.content_type == "application/edi-consent"}
30
+ else
31
+ mail
32
+ end
33
+ end
34
+
35
+ private
36
+ def mail
37
+ @mail ||= Mail.new(decrypted_message)
38
+ end
39
+
40
+ def read_smime(smime)
41
+ OpenSSL::PKCS7.read_smime(smime)
42
+ end
43
+
44
+ def decrypt_smime(smime)
45
+ message = read_smime(smime)
46
+ message.decrypt @private_key, @public_certificate
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,70 @@
1
+ module As2
2
+ class MimeGenerator
3
+ class Part
4
+ def initialize
5
+ @parts = []
6
+ @body = ""
7
+ @headers = {}
8
+ end
9
+
10
+ def [](name)
11
+ @headers[name]
12
+ end
13
+
14
+ def []=(name, value)
15
+ @headers[name] = value
16
+ end
17
+
18
+ def body
19
+ @body
20
+ end
21
+
22
+ def body=(body)
23
+ unless @parts.empty?
24
+ raise "Cannot add plain budy to multipart"
25
+ end
26
+ @body = body
27
+ end
28
+
29
+ def add_part(part)
30
+ gen_id unless @id
31
+ @parts << part
32
+ @body = nil
33
+ end
34
+
35
+ def multipart?
36
+ ! @parts.empty?
37
+ end
38
+
39
+ def write(io)
40
+ @headers.each do |name, value|
41
+ if multipart? && name =~ /content-type/i
42
+ io.print "#{name}: #{value}; \r\n"
43
+ io.print "\tboundary=\"----=_Part_#{@id}\"\r\n"
44
+ else
45
+ io.print "#{name}: #{value}\r\n"
46
+ end
47
+ end
48
+ io.print "\r\n"
49
+ if @parts.empty?
50
+ io.print @body, "\r\n"
51
+ else
52
+ @parts.each do|p|
53
+ io.print "------=_Part_#{@id}\r\n"
54
+ p.write(io)
55
+ end
56
+ io.print "------=_Part_#{@id}--\r\n"
57
+ end
58
+ io.print "\r\n"
59
+ end
60
+
61
+ private
62
+
63
+ @@counter = 0
64
+ def gen_id
65
+ @@counter += 1
66
+ @id = "#{@@counter}_#{Time.now.strftime('%Y%m%d%H%M%S%L')}"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,139 @@
1
+ require 'rack'
2
+ require 'logger'
3
+ require 'stringio'
4
+ require 'as2/mime_generator'
5
+ require 'as2/base64_helper'
6
+ require 'as2/message'
7
+
8
+ module As2
9
+ class Server
10
+ HEADER_MAP = {
11
+ 'To' => 'HTTP_AS2_TO',
12
+ 'From' => 'HTTP_AS2_FROM',
13
+ 'Subject' => 'HTTP_SUBJECT',
14
+ 'MIME-Version' => 'HTTP_MIME_VERSION',
15
+ 'Content-Disposition' => 'HTTP_CONTENT_DISPOSITION',
16
+ 'Content-Type' => 'CONTENT_TYPE',
17
+ }
18
+
19
+ attr_accessor :logger
20
+
21
+ def initialize(options = {}, &block)
22
+ @block = block
23
+ @info = Config.server_info
24
+ @options = options
25
+ end
26
+
27
+ def call(env)
28
+ if env['HTTP_AS2_TO'] != @info.name
29
+ return send_error(env, "Invalid destination name #{env['HTTP_AS2_TO']}")
30
+ end
31
+
32
+ partner = Config.partners[env['HTTP_AS2_FROM']]
33
+ unless partner
34
+ return send_error(env, "Invalid partner name #{env['HTTP_AS2_FROM']}")
35
+ end
36
+
37
+ smime_string = build_smime_text(env)
38
+ message = Message.new(smime_string, @info.pkey, @info.certificate)
39
+ unless message.valid_signature?(partner.certificate)
40
+ if @options[:on_signature_failure]
41
+ @options[:on_signature_failure].call({env: env, smime_string: smime_string})
42
+ else
43
+ raise "Could not verify signature"
44
+ end
45
+ end
46
+
47
+ mic = OpenSSL::Digest::SHA1.base64digest(message.decrypted_message)
48
+
49
+ if @block
50
+ begin
51
+ @block.call message.attachment.filename, message.attachment.body
52
+ rescue Exception => e
53
+ return send_error(env, e.message)
54
+ end
55
+ end
56
+
57
+ send_mdn(env, mic)
58
+ end
59
+
60
+ private
61
+ def build_smime_text(env)
62
+ request = Rack::Request.new(env)
63
+ smime_data = StringIO.new
64
+
65
+ HEADER_MAP.each do |name, value|
66
+ smime_data.puts "#{name}: #{env[value]}"
67
+ end
68
+
69
+ smime_data.puts 'Content-Transfer-Encoding: base64'
70
+ smime_data.puts
71
+ smime_data.puts Base64Helper.ensure_base64(request.body.read)
72
+
73
+ return smime_data.string
74
+ end
75
+
76
+ def logger(env)
77
+ @logger ||= Logger.new env['rack.errors']
78
+ end
79
+
80
+ def send_error(env, msg)
81
+ logger(env).error msg
82
+ send_mdn env, nil, msg
83
+ end
84
+
85
+ def send_mdn(env, mic, failed = nil)
86
+ report = MimeGenerator::Part.new
87
+ report['Content-Type'] = 'multipart/report; report-type=disposition-notification'
88
+
89
+ text = MimeGenerator::Part.new
90
+ text['Content-Type'] = 'text/plain'
91
+ text['Content-Transfer-Encoding'] = '7bit'
92
+ text.body = "The AS2 message has been received successfully"
93
+
94
+ report.add_part text
95
+
96
+ notification = MimeGenerator::Part.new
97
+ notification['Content-Type'] = 'message/disposition-notification'
98
+ notification['Content-Transfer-Encoding'] = '7bit'
99
+
100
+ options = {
101
+ 'Reporting-UA' => @info.name,
102
+ 'Original-Recipient' => "rfc822; #{@info.name}",
103
+ 'Final-Recipient' => "rfc822; #{@info.name}",
104
+ 'Original-Message-ID' => env['HTTP_MESSAGE_ID']
105
+ }
106
+ if failed
107
+ options['Disposition'] = 'automatic-action/MDN-sent-automatically; failed'
108
+ options['Failure'] = failed
109
+ else
110
+ options['Disposition'] = 'automatic-action/MDN-sent-automatically; processed'
111
+ end
112
+ options['Received-Content-MIC'] = "#{mic}, sha1" if mic
113
+ notification.body = options.map{|n, v| "#{n}: #{v}"}.join("\r\n")
114
+ report.add_part notification
115
+
116
+ msg_out = StringIO.new
117
+
118
+ report.write msg_out
119
+
120
+ pkcs7 = OpenSSL::PKCS7.sign @info.certificate, @info.pkey, msg_out.string
121
+ pkcs7.detached = true
122
+ smime_signed = OpenSSL::PKCS7.write_smime pkcs7, msg_out.string
123
+
124
+ content_type = smime_signed[/^Content-Type: (.+?)$/m, 1]
125
+ smime_signed.sub!(/\A.+?^(?=---)/m, '')
126
+
127
+ headers = {}
128
+ headers['Content-Type'] = content_type
129
+ headers['MIME-Version'] = '1.0'
130
+ headers['Message-ID'] = "<#{@info.name}-#{Time.now.strftime('%Y%m%d%H%M%S')}@#{@info.domain}>"
131
+ headers['AS2-From'] = @info.name
132
+ headers['AS2-To'] = env['HTTP_AS2_FROM']
133
+ headers['AS2-Version'] = '1.2'
134
+ headers['Connection'] = 'close'
135
+
136
+ [200, headers, ["\r\n" + smime_signed]]
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,3 @@
1
+ module As2
2
+ VERSION = "0.2.4"
3
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: as2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.4
5
+ platform: ruby
6
+ authors:
7
+ - OfficeLuv
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-03-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mail
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: thin
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Simple AS2 server and client implementation. Follows the AS2 implementation
98
+ from http://as2.mendelson-e-c.com
99
+ email:
100
+ - development@officeluv.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - Gemfile
107
+ - Gemfile.lock
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - as2.gemspec
112
+ - bin/console
113
+ - bin/setup
114
+ - examples/client.rb
115
+ - examples/server.rb
116
+ - lib/as2.rb
117
+ - lib/as2/base64_helper.rb
118
+ - lib/as2/client.rb
119
+ - lib/as2/config.rb
120
+ - lib/as2/message.rb
121
+ - lib/as2/mime_generator.rb
122
+ - lib/as2/server.rb
123
+ - lib/as2/version.rb
124
+ homepage: https://github.com/officeluv/as2
125
+ licenses:
126
+ - MIT
127
+ metadata:
128
+ allowed_push_host: https://rubygems.org/
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 2.7.6
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Simple AS2 server and client implementation
149
+ test_files: []