Organize directories

This commit is contained in:
Darren Clarke 2023-02-13 13:10:48 +00:00
parent 8a91c9b89b
commit 4898382f78
433 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,7 @@
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
class SecureMailing::PGP < SecureMailing::Backend
def self.active?
Setting.get('pgp_integration')
end
end

View file

@ -0,0 +1,170 @@
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
class SecureMailing::PGP::Incoming < SecureMailing::Backend::Handler
attr_accessor :mail, :content_type
EXPRESSION_ENCRYPTED = %r{application/pgp-encrypted}i.freeze
EXPRESSION_SIGNATURE = %r{application/pgp-signature}i.freeze
def initialize(mail)
super()
@mail = mail
@content_type = mail[:mail_instance].content_type
end
def process
return unless process?
initialize_article_preferences
decrypt
verify_signature
log
end
def initialize_article_preferences
article_preferences[:security] = {
type: 'PGP',
sign: {
success: false,
comment: nil
},
encryption: {
success: false,
comment: nil
}
}
end
def article_preferences
@article_preferences ||= begin
key = :'x-zammad-article-preferences'
mail[key] ||= {}
mail[key]
end
end
def process?
signed? || encrypted?
end
def signed?(check_content_type = content_type)
EXPRESSION_SIGNATURE.match?(check_content_type)
end
def encrypted?(check_content_type = content_type)
EXPRESSION_ENCRYPTED.match?(check_content_type)
end
def decrypt
return unless encrypted?
success = false
comment = 'Private key for decryption could not be found.'
::PGPKeypair.where.not(private_key: [nil, '']).find_each do |cert|
begin
index = mail[:attachments].index { |file| file[:preferences]['Content-Type'] == 'application/pgp-encrypted' }
data = mail[:attachments][index + 1][:data]
decrypted_data = Sequoia.decrypt_for(ciphertext: data.chop, recipient: cert.private_key,
password: cert.private_key_secret)
rescue StandardError
next
end
parse_new_mail(decrypted_data)
success = true
comment = cert.email_addresses.join(', ')
# overwrite content_type for signature checking
@content_type = mail[:mail_instance].content_type
break
end
article_preferences[:security][:encryption] = {
success: success,
comment: comment
}
end
def verify_signature
return unless signed?
success = false
comment = 'Certificate for verification could not be found.'
::PGPKeypair.where.not(public_key: [nil, '']).find_each do |cert|
next unless cert.email_addresses.include? mail[:from_email]
begin
index = mail[:attachments].index { |file| file[:preferences]['Mime-Type'] == 'application/pgp-signature' }
data = mail[:attachments][index][:data]
verified_data = Sequoia.verify_detached_from(plaintext: mail[:mail_instance].body.encoded, signature: data.chop,
sender: cert.public_key)
rescue StandardError
next
end
parse_new_mail(verified_data)
success = true
comment = cert.email_addresses.join(', ')
# overwrite content_type for signature checking
@content_type = mail[:mail_instance].content_type
break
end
article_preferences[:security][:sign] = {
success: success,
comment: comment
}
end
private
def log
%i[sign encryption].each do |action|
result = article_preferences[:security][action]
next if result.blank?
if result[:success]
status = 'success'
elsif result[:comment].blank?
# means not performed
next
else
status = 'failed'
end
HttpLog.create(
direction: 'in',
facility: 'PGP',
url: "#{mail[:from_email]} -> #{mail[:to]}",
status: status,
ip: nil,
request: {
message_id: mail[:message_id]
},
response: article_preferences[:security],
method: action,
created_by_id: 1,
updated_by_id: 1
)
end
end
def parse_new_mail(new_mail)
mail[:mail_instance].header['Content-Type'] = nil
mail[:mail_instance].header['Content-Disposition'] = nil
mail[:mail_instance].header['Content-Transfer-Encoding'] = nil
mail[:mail_instance].header['Content-Description'] = nil
new_raw_mail = "#{mail[:mail_instance].header}#{new_mail}"
mail_new = Channel::EmailParser.new.parse(new_raw_mail)
mail_new.each do |local_key, local_value|
mail[local_key] = local_value
end
end
end

View file

@ -0,0 +1,120 @@
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
class SecureMailing::PGP::Outgoing < SecureMailing::Backend::Handler
def initialize(mail, security)
super()
@mail = mail
@security = security
end
def process
return unless process?
if @security[:sign][:success]
sign
log('sign', 'success')
end
if @security[:encryption][:success]
encrypt
log('encryption', 'success')
end
end
def process?
return false if @security.blank?
return false if @security[:type] != 'PGP'
@security[:sign][:success] || @security[:encryption][:success]
end
def cleanup(mail)
part = Mail::Part.new
if mail.multipart?
if mail.content_type =~ /^(multipart[^;]+)/
part.content_type Regexp.last_match(1)
else
part.content_type 'multipart/mixed'
end
mail.body.parts.each do |p|
part.add_part cleanup(p)
end
else
# retain important headers if present
part.content_type mail.content_type
part.content_id mail.header['Content-ID'] if mail.header['Content-ID']
part.content_disposition mail.content_disposition if mail.content_disposition
# force base64 encoding
part.body Mail::Encodings::Base64.encode(mail.body.to_s)
part.body.encoding = 'base64'
end
part
end
def sign
from = @mail.from.first
cert = PGPKeypair.for_sender_email_address(from)
raise "Unable to find PGP private key for '#{from}'" unless cert
signature = Sequoia.sign_detached_with(plaintext: @mail.body.encoded, sender: cert.private_key,
password: cert.private_key_secret)
signature_part = Mail::Part.new do
content_type 'application/pgp-signature; name="signature.asc"'
content_disposition 'attachment; filename="signature.asc"'
content_description 'OpenPGP signature'
body signature
end
@mail.add_part signature_part
@mail.content_type "multipart/signed; protocol=\"application/pgp-signature\"; micalg=\"pgp-sha512\"; boundary=\"#{@mail.boundary}\""
rescue StandardError => e
log('sign', 'failed', e.message)
raise
end
def encrypt
recipients = []
recipients += @mail.to if @mail.to
recipients += @mail.cc if @mail.cc
recipients += @mail.bcc if @mail.bcc
certificates = PGPKeypair.for_recipient_email_addresses!(recipients)
encrypted_control = Mail::Part.new do
content_type 'application/pgp-encrypted'
content_description 'OpenPGP version'
body 'Version: 1'
end
plaintext = @mail.encoded
encrypted_part = Mail::Part.new do
content_type 'application/octet-stream; name="encrypted.asc"'
content_disposition 'inline; filename="encrypted.asc"'
content_description 'OpenPGP encrypted message'
body Sequoia.encrypt_for(plaintext: plaintext, recipients: certificates.map(&:public_key))
end
@mail.body = nil
@mail.add_part encrypted_control
@mail.add_part encrypted_part
@mail.content_type "multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"#{@mail.boundary}\""
rescue StandardError => e
log('encryption', 'failed', e.message)
raise
end
def log(action, status, error = nil)
HttpLog.create(
direction: 'out',
facility: 'PGP',
url: "#{@mail[:from_email]} -> #{@mail[:to]}",
status: status,
ip: nil,
request: @security,
response: { error: error },
method: action,
created_by_id: 1,
updated_by_id: 1
)
end
end

View file

@ -0,0 +1,93 @@
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
class SecureMailing::PGP::Retry < SecureMailing::Backend::Handler
def initialize(article)
super()
@article = article
end
def process
return existing_result if already_processed?
save_result if retry_succeeded?
retry_result
end
def signature_checked?
@signature_checked ||= existing_result&.dig('sign', 'success') || false
end
def decrypted?
@decrypted ||= existing_result&.dig('encryption', 'success') || false
end
def already_processed?
signature_checked? && decrypted?
end
def existing_result
@article.preferences['security']
end
def mail
@mail ||= begin
raw_mail = @article.as_raw.store_file.content
Channel::EmailParser.new.parse(raw_mail).tap do |parsed|
SecureMailing.incoming(parsed)
end
end
end
def retry_result
@retry_result ||= mail['x-zammad-article-preferences']['security']
end
def signature_found?
return false if signature_checked?
retry_result['sign']['success']
end
def decryption_succeeded?
return false if decrypted?
retry_result['encryption']['success']
end
def retry_succeeded?
return true if signature_found?
decryption_succeeded?
end
def save_result
save_decrypted if decryption_succeeded?
@article.preferences['security'] = retry_result
@article.save!
end
def save_decrypted
@article.content_type = mail['content_type']
@article.body = mail['body']
Store.remove(
object: 'Ticket::Article',
o_id: @article.id
)
mail[:attachments]&.each do |attachment|
filename = attachment[:filename].force_encoding('utf-8')
unless filename.force_encoding('UTF-8').valid_encoding?
filename = filename.utf8_encode(fallback: :read_as_sanitized_binary)
end
Store.add(
object: 'Ticket::Article',
o_id: @article.id,
data: attachment[:data],
filename: filename,
preferences: attachment[:preferences],
created_by_id: @article.created_by_id
)
end
end
end