Skip to content

Commit

Permalink
progress on configurable base64 schemes
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Dean committed Feb 9, 2024
1 parent 93cb9e1 commit d1cb2ec
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 5 deletions.
46 changes: 46 additions & 0 deletions lib/as2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,52 @@ def self.generate_message_id(server_info)
"<#{server_info.name}-#{Time.now.strftime('%Y%m%d-%H%M%S')}-#{SecureRandom.uuid}@#{server_info.domain}>"
end

def self.valid_base64_schemes
[
'rfc2045', # https://www.rfc-editor.org/rfc/rfc2045#section-6.8
'rfc4648' # https://www.rfc-editor.org/rfc/rfc4648#section-4
]
end

# create a base64 string from content, based on the given encoding scheme
#
# @param [String] content
# @param [String] scheme one of As2.valid_base64_schemes
# @return [String]
def self.base64_encode(content, scheme: 'rfc4648')
case scheme
when 'rfc2045'
# "This method complies with RFC 2045."
# https://ruby-doc.org/stdlib-3.0.4/libdoc/base64/rdoc/Base64.html#method-i-encode64
then Base64.encode64(content)
when 'rfc4648'
# "This method complies with RFC 4648."
# https://ruby-doc.org/stdlib-3.0.4/libdoc/base64/rdoc/Base64.html#method-i-strict_encode64
then Base64.strict_encode64(content)
else
raise "unsupported base64_scheme '#{@partner.base64_scheme}'"
end
end

# canonicalize all line endings in the given text.
#
# "\n" becomes "\r\n"
# "\r\n" remains "\r\n"
#
# Conversion to canonical form:
# The entire body ... is converted to a universal canonical
# form. ... For example, in the case of text/plain data, the text
# must be converted to a supported character set and lines must
# be delimited with CRLF delimiters in accordance with RFC 822.
#
# https://www.rfc-editor.org/rfc/rfc2049#page-9
#
# @param [String] content
# @return [String] content, but with all bare \n replaced by \r\n
def self.canonicalize_line_endings(content)
content.gsub(/(?<!\r)\n/, "\r\n")
end

# Select which algorithm to use for calculating a MIC, based on preferences
# stated by sender & our list of available algorithms.
#
Expand Down
11 changes: 8 additions & 3 deletions lib/as2/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def format_body_v0(document_content, content_type:, file_name:)
document_payload << "Content-Transfer-Encoding: base64\r\n"
document_payload << "Content-Disposition: attachment; filename=#{file_name}\r\n"
document_payload << "\r\n"
document_payload << Base64.strict_encode64(document_content)
document_payload << base64_encode(document_content)

signature = OpenSSL::PKCS7.sign(@server_info.certificate, @server_info.pkey, document_payload)
signature.detached = true
Expand Down Expand Up @@ -201,7 +201,7 @@ def format_body_v1(document_content, content_type:, file_name:)
document_payload << "Content-Transfer-Encoding: base64\r\n"
document_payload << "Content-Disposition: attachment; filename=#{file_name}\r\n"
document_payload << "\r\n"
document_payload << Base64.encode64(document_content)
document_payload << base64_encode(document_content)

signature = OpenSSL::PKCS7.sign(@server_info.certificate, @server_info.pkey, document_payload)
signature.detached = true
Expand All @@ -211,7 +211,7 @@ def format_body_v1(document_content, content_type:, file_name:)
# strip off the '-----BEGIN PKCS7-----' / '-----END PKCS7-----' delimiters
bare_pem_signature.gsub!(/^-----[^\n]+\n/, '')
# and update to canonical \r\n line endings
bare_pem_signature.gsub!(/(?<!\r)\n/, "\r\n")
bare_pem_signature = As2.canonicalize_line_endings(bare_pem_signature)

# this is a hack until i can determine a better way to get the micalg parameter
# from the pkcs7 signature generated above...
Expand Down Expand Up @@ -315,6 +315,11 @@ def evaluate_mdn(mdn_body:, mdn_content_type:, original_message_id:, original_bo

private

def base64_encode(content)
encoded = As2.base64_encode(content, scheme: @partner.base64_scheme)
As2.canonicalize_line_endings(encoded)
end

# extract the MDN body from a multipart/signed wrapper & attempt to verify
# the signature
#
Expand Down
12 changes: 11 additions & 1 deletion lib/as2/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,20 @@ def self.build_certificate(input)
end
end

class Partner < Struct.new :name, :url, :encryption_certificate, :encryption_cipher, :signing_certificate, :tls_verify_mode, :mdn_format, :outbound_format
class Partner < Struct.new :name, :url, :encryption_certificate, :encryption_cipher, :signing_certificate, :tls_verify_mode, :mdn_format, :outbound_format, :base64_scheme
def initialize
# set default.
self.encryption_cipher = 'aes-256-cbc'
self.base64_scheme = 'rfc4648'
end

def base64_scheme=(scheme)
scheme_s = scheme.to_s
valid_schemes = As2.valid_base64_schemes
if !valid_schemes.include?(scheme_s)
raise ArgumentError, "base64_scheme '#{scheme_s}' must be one of #{valid_schemes.inspect}"
end
self['base64_scheme'] = scheme_s
end

def url=(url)
Expand Down
2 changes: 1 addition & 1 deletion lib/as2/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def format_mdn_v1(mdn_text, as2_to:)
# strip off the '-----BEGIN PKCS7-----' / '-----END PKCS7-----' delimiters
bare_pem_signature.gsub!(/^-----[^\n]+\n/, '')
# and update to canonical \r\n line endings
bare_pem_signature.gsub!(/(?<!\r)\n/, "\r\n")
bare_pem_signature = As2.canonicalize_line_endings(bare_pem_signature)

# this is a hack until i can determine a better way to get the micalg parameter
# from the pkcs7 signature generated above...
Expand Down
12 changes: 12 additions & 0 deletions test/as2_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@
end
end

describe '.base64_encode' do
it 'can encode according to RFC-2045'
it 'can encode according to RFC-4648'
it 'raises if the given encoding scheme is not recognized'
it 'defaults to RFC-4648 for backwards-compatibility'
end

describe '.canonicalize_line_endings' do
it 'replaces \n with \r\n'
it 'does not alter existing \r\n sequences'
end

describe '.choose_mic_algorithm' do
it 'returns nil if no algorithm is found' do
assert_nil As2.choose_mic_algorithm(nil)
Expand Down
17 changes: 17 additions & 0 deletions test/config_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@
end
end

describe '#base64_scheme=' do
it 'accepts a valid scheme value' do
@partner_config.base64_scheme = 'rfc2045'
assert_equal 'rfc2045', @partner_config.base64_scheme

@partner_config.base64_scheme = 'rfc4648'
assert_equal 'rfc4648', @partner_config.base64_scheme
end

it 'raises if given an invalid format value' do
error = assert_raises(ArgumentError) do
@partner_config.base64_scheme = 'invalid'
end
assert_equal "base64_scheme 'invalid' must be one of [\"rfc2045\", \"rfc4648\"]", error.message
end
end

describe '#url=' do
it 'accepts a string' do
@partner_config.url = 'http://test.com'
Expand Down

0 comments on commit d1cb2ec

Please sign in to comment.