Skip to content
Snippets Groups Projects
Commit c808e09e authored by Tobias Dussa's avatar Tobias Dussa
Browse files

Initial functionality. Key-handling is missing.

parent 1cc2b5b1
No related branches found
No related tags found
No related merge requests found
......@@ -28,10 +28,7 @@ import getpass
import hashlib
import jinja2
import os
import magic
import requests
import secrets
import smtplib
import sys
parser = argparse.ArgumentParser()
......@@ -58,13 +55,13 @@ parser.add_argument('-i', '--input', dest='input', default='{basedir}/
parser.add_argument('-o', '--output', dest='output', default='{basedir}/{campaign}/{site}/Mail{infix}.eml', help='output file name template (default: "{basedir}/{campaign}/{site}/Mail{infix}.eml")')
parser.add_argument('-R', '--reply-to', dest='replyto', default=None, help='reply-to mail address (default: None)')
parser.add_argument( '--salt', dest='salt', default=None, help='salt to use for hashing (default: random 8-byte hex string)')
parser.add_argument( '--sign', dest='sign', default=None, choices=[None, 'gpg', 'smime'], help='signature method (one of "", "gpg", "smime"; default: "")')
parser.add_argument( '--sign-as', dest='sign-as', default=None, help='signature key to use (default: autoselect)')
parser.add_argument( '--sign', dest='sign', default='', choices=['', 'gpg', 'gpgsm', 'openssl'], help='signature method (one of "", "gpg", "gpgsm", "openssl"; default: "")')
parser.add_argument( '--sign-as', dest='signas', default=None, help='signature key to use (default: None meaning autoselect)')
parser.add_argument('-s', '--subject', dest='subject', default='Security Challenge for {site} -- {campaign}{infix}', help='mail subject (default: "Security Challenge Message -- {campaign}{infix}")')
parser.add_argument('-S', '--smtpserver', dest='smtpserver', default='localhost', help='SMTP server to use (default: "localhost"); port can be specified with "<host>:<port>" notation and takes precedence over implied ports and port specification')
parser.add_argument( '--smtpport', dest='smtpport', default=0, type=int, help='SMTP port to use (default: 25); takes precedence over implied ports')
parser.add_argument( '--smtpuser', dest='smtpuser', default=None, help='SMTP user to login with (default: none); implies TLS (port 465) unless STARTTLS is set as well')
parser.add_argument( '--smtppass', dest='smtppass', default=None, help='SMTP password to login with (default: none); implies TLS (port 465) unless STARTTLS is set as well; will be queried interactively if set to "-"')
parser.add_argument( '--smtpuser', dest='smtpuser', default=None, help='SMTP user to login with (default: none); implies TLS (port 465) unless STARTTLS is set as well')
parser.add_argument( '--smtppass', dest='smtppass', default=None, help='SMTP password to login with (default: none); implies TLS (port 465) unless STARTTLS is set as well; will be queried interactively if set to "-"')
parser.add_argument( '--starttls', dest='starttls', default=False, action='store_true', help='login using STARTTLS (default: False); implies port 587')
parser.add_argument('-t', '--template', dest='template', default='{basedir}/{campaign}/Mail.template', help='mail template file (default: "{basedir}/{campaign}/Mail.template")')
parser.add_argument('-T', '--to', dest='to', default='{firstname} {lastname} <{email}>', help='recipient mail address (default: "{firstname} {lastname} <{email}>")')
......@@ -83,6 +80,34 @@ if (args.sender == 'Nobody <nobody@example.com>') or \
(args.webserver == 'https://challenge.example.com'):
args.dryrun = True
# Import further necessary dependencies
# Import dependencies for actual SMTP interaction if necessary
if not args.dryrun:
import smtplib
# Import dependencies for HTTTP interaction if necessary
if not args.webserver:
import requests
# Import dependencies for attachment file-magic if necessary
if args.attach:
import magic
# Import dependencies for mail signing if necessary
if args.sign:
import magic
match args.sign:
case 'gpg' | 'gpgsm':
# Import dependencies for GPG-based mail signing if necessary
from email.mime.multipart import MIMEMultipart
import gnupg
case 'openssl':
# Import dependencies for OpenSSL-based mail signing if necessary
from M2Crypto import BIO, Rand, SMIME
if (len(args.smtpserver.split(':')) == 2) or \
(']:' in args.smtpserver):
args.smtpport = args.smtpserver.rsplit(':', 1)[1]
......@@ -132,6 +157,14 @@ if args.verbose:
print(f'Using "{args.input.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as input file.')
print(f'Using "{args.output.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as output file name template.')
print(f'Using "{args.salt.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as salt.')
if args.sign:
print(f'Using "{args.sign}" as signature method.')
if args.signas:
print(f'Using "{args.signas}" as signing key.')
else:
print(f'Using auto-selected signing key.')
else:
print(f'Using no signature method.')
print(f'Using "{args.subject.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as mail subject.')
print(f'Using "{args.template.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as template file.')
print(f'Using "{args.to.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as recipient mail address.')
......@@ -160,6 +193,7 @@ data = dict()
template = jinja2.Template(open(args.template.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))).read())
# Convert int to hex
def toHex(serial):
tmpString = hex(serial)[2:].upper()
if ((len(tmpString) % 2) == 1):
......@@ -167,6 +201,51 @@ def toHex(serial):
return ':'.join([tmpString[i:i+2] for i in range(0, len(tmpString), 2)])
# Sign message with GPG/GPGSM
def signMailGPG(binary, message):
# Set up GPG context
gpg = gnupg.GPG(gpgbinary=binary)
# Sign mail
try:
signature = str(gpg.sign(message.as_string().replace('\n', '\r\n'), detach=True))
except:
print('ERROR signing mail!')
# Assemble signature message
signatureMessage = EmailMessage()
signatureMessage['Content-Type'] = 'application/pgp-signature; name="signature.asc"'
signatureMessage['Content-Description'] = 'OpenPGP digital signature'
signatureMessage.set_payload(signature)
# Assemble new message
newMessage = MIMEMultipart(_subtype="signed", protocol="application/pgp-signature")
newMessage.attach(message)
newMessage.attach(signatureMessage)
return newMessage
# Sign message with OpenSSL
def signMailOpenSSL(message):
return message
# Sign message if requested
def signMail(message):
if not args.sign:
return message
match args.sign:
case 'gpg':
return signMailGPG('gpg', message)
case 'gpgsm':
return signMailGPG('gpgsm', message)
case 'openssl':
return signMailOpenSSL(message)
# Assemble and send the actual mail
def createMail(data):
output = args.output.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL']))
directory = os.path.dirname(output)
......@@ -178,6 +257,9 @@ def createMail(data):
with open(attach, 'rb') as attachment:
message.add_attachment(attachment.read(), *magic.from_file(attach, mime=True).split('/'))
# Sign mail if requested
message = signMail(message)
message['Date'] = formatdate(localtime=True)
message['Subject'] = args.subject.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL']))
message['From'] = args.sender.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL']))
......@@ -225,12 +307,14 @@ def createMail(data):
requests.get(f'{data["CreateURL"]}')
# Generate hash, URL, and CreateURL
def generateHashAndURL(data):
data['hash'] = hashlib.sha256(args.hashstring.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'])).encode('utf-8')).hexdigest()
data['URL'] = args.url.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash']))
data['CreateURL'] = args.createurl.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash']))
# Main loop
with open(args.input.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt)), 'r') as inputfile:
data = csv.DictReader(inputfile, fieldnames=['site', 'firstname', 'lastname', 'email'], delimiter=',')
for row in data:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment