From c63beef7419ce144bd907cc3d16ee71e76595ae0 Mon Sep 17 00:00:00 2001 From: jaseg Date: Tue, 20 Jul 2021 15:09:10 +0200 Subject: [PATCH] Add email encoder --- demo/fw/tools/create_age_email.py | 98 +++++++++++++++++++ demo/fw/tools/tb_data_encoder.py | 21 ++-- .../terminus-font-4.49.1/ter-u16b.bdf | 0 .../terminus-font-4.49.1/ter-u16n.bdf | 0 4 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 demo/fw/tools/create_age_email.py rename {demo/fpga => upstream}/terminus-font-4.49.1/ter-u16b.bdf (100%) rename {demo/fpga => upstream}/terminus-font-4.49.1/ter-u16n.bdf (100%) diff --git a/demo/fw/tools/create_age_email.py b/demo/fw/tools/create_age_email.py new file mode 100644 index 0000000..7f448da --- /dev/null +++ b/demo/fw/tools/create_age_email.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +import os, sys +from pathlib import Path +import math +import subprocess +from email.message import EmailMessage +from email.utils import make_msgid +import textwrap +import io +import binascii + +import click +import bdfparser +from PIL import Image +import numpy as np + +import tb_data_encoder + + +def age_encrypt(content, keys): + age_binary = os.environ.get('AGE', 'age') + proc = subprocess.run([age_binary, '-e', *(elem for key in keys for elem in ('-r', key))], + check=True, + capture_output=True, + text=False, + input=content.encode('utf-8')) + return proc.stdout + +def pack_html(payload_img_cid, hint_img_cid): + return textwrap.dedent(f'''\ + + + +
+ + + + ''').strip() + +def create_hint_img(text, max_w=200, fgcolor='#e1e31b', bgcolor='#d61ca1'): + relative_path = (Path(__file__).parent / '../../../upstream/terminus-font-4.49.1/ter-u16b.bdf').resolve() + font_file = os.getenv('HINT_FONT', str(relative_path)) + font = bdfparser.Font(font_file) + hex_to_np = lambda s: np.frombuffer(binascii.unhexlify(s.lstrip('#')), dtype=np.uint8) + colors = np.array([hex_to_np(bgcolor), hex_to_np(fgcolor)]) + + img = font.draw(text, linelimit=max_w) + return Image.fromarray(colors[img.todata(2)]) + +# because inexplicably, pillow doesn't have this already +def img_to_bytes(img, fmt='png'): + bio = io.BytesIO() + img.save(bio, fmt) + return bio.getvalue() + + +@click.command() +@click.option('-f', '--from', 'sender') +@click.option('-t', '--to', 'recipients') +@click.option('-c', '--cc', 'cc') +@click.option('-b', '--bcc', 'bcc') +@click.option('-k', '--key', 'keys', multiple=True, required=True) +@click.option('-s', '--subject', default='tachibana/age encrypted message') +@click.argument('content', type=click.File('r')) +def create_age_email(content, sender, recipients, cc, bcc, keys, subject): + + encrypted = age_encrypt(content.read(), keys) + + msg = EmailMessage() + msg['Subject'] = subject + if sender: + msg['From'] = sender + if recipients: + msg['To'] = ', '.join(recipients) + if cc: + msg['Cc'] = ', '.join(cc) + if bcc: + msg['Bcc'] = ', '.join(bcc) + + payload_img_cid, hint_img_cid = make_msgid(), make_msgid() + # remove <...> angle brackets from cids in tags + msg.set_content(pack_html(payload_img_cid[1:-1], hint_img_cid[1:-1]), subtype='html') + + w = max(200, math.ceil(math.sqrt(len(encrypted)))) + h = math.ceil((len(encrypted) + 64) / w) + img_bytes = img_to_bytes(tb_data_encoder.data_encode(encrypted, w, h)) + msg.add_related(img_bytes, 'image', 'png', cid=payload_img_cid) + + img_bytes = img_to_bytes(create_hint_img('tachibana/age\nencrypted email', max_w=200)) + msg.add_related(img_bytes, 'image', 'png', cid=hint_img_cid) + + sys.stdout.buffer.write(bytes(msg)) + + +if __name__ == '__main__': + #print(pack_html('foobar')) + create_age_email() diff --git a/demo/fw/tools/tb_data_encoder.py b/demo/fw/tools/tb_data_encoder.py index 0390837..454aef7 100644 --- a/demo/fw/tools/tb_data_encoder.py +++ b/demo/fw/tools/tb_data_encoder.py @@ -21,13 +21,7 @@ WINDOW_MAGIC = np.array([ def tb_int_to_px(val): return np.array([(val//0x10000)&0xff, (val//0x100)&0xff, val&0xff], dtype=np.uint8) -@click.command() -@click.option('-w', '--width', type=int, default=400) -@click.option('-h', '--height', type=int, default=300) -@click.argument('input_file', type=click.File('rb')) -@click.argument('output_file', type=click.Path(dir_okay=False, writable=True)) -def tb_data_encode(width, height, input_file, output_file): - data = input_file.read() +def data_encode(data, width, height): data = struct.pack('>I', len(data)) + data data = np.frombuffer(data, dtype=np.uint8) @@ -46,9 +40,18 @@ def tb_data_encode(width, height, input_file, output_file): win[0, 10] = tb_int_to_px(width) win[0, 11] = tb_int_to_px(height) - print('out:', ' '.join(f'{x:02x}' for x in win.flatten()[:256])) + #print('out:', ' '.join(f'{x:02x}' for x in win.flatten()[:256])) + return Image.fromarray(win) - Image.fromarray(win).save(output_file) +@click.command() +@click.option('-w', '--width', type=int, default=400) +@click.option('-h', '--height', type=int, default=300) +@click.argument('input_file', type=click.File('rb')) +@click.argument('output_file', type=click.Path(dir_okay=False, writable=True)) +def tb_data_encode(width, height, input_file, output_file): + data = input_file.read() + img = data_encode(data, width, height) + img.save(output_file) if __name__ == '__main__': tb_data_encode() diff --git a/demo/fpga/terminus-font-4.49.1/ter-u16b.bdf b/upstream/terminus-font-4.49.1/ter-u16b.bdf similarity index 100% rename from demo/fpga/terminus-font-4.49.1/ter-u16b.bdf rename to upstream/terminus-font-4.49.1/ter-u16b.bdf diff --git a/demo/fpga/terminus-font-4.49.1/ter-u16n.bdf b/upstream/terminus-font-4.49.1/ter-u16n.bdf similarity index 100% rename from demo/fpga/terminus-font-4.49.1/ter-u16n.bdf rename to upstream/terminus-font-4.49.1/ter-u16n.bdf