Implement email encryption and signing
This commit is contained in:
parent
35804df168
commit
a423d3de86
|
@ -12,3 +12,4 @@ storage/
|
|||
pip-selfcheck.json
|
||||
.sass-cache/
|
||||
overrides/
|
||||
.pgp
|
||||
|
|
|
@ -54,8 +54,17 @@ site-name=sr.ht
|
|||
# The source code for your fork of sr.ht:
|
||||
source-url=https://git.sr.ht/~sircmpwn/sr.ht
|
||||
#
|
||||
# If "yes", payment will be required for account creation.
|
||||
# If "yes", paid features will be enabled.
|
||||
require-payment=yes
|
||||
#
|
||||
# Your PGP key information (DO NOT mix up pub and priv here)
|
||||
# You must remove the password from your secret key, if present.
|
||||
# You can do this with gpg --edit-key [key-id], then use the passwd
|
||||
# command and do not enter a new password.
|
||||
pgp-privkey=CHANGEME
|
||||
pgp-pubkey=CHANGEME
|
||||
# Hardcoded for effeciency's sake:
|
||||
pgp-key-id=CHANGEME
|
||||
|
||||
[network]
|
||||
files=https://sr.ht
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{{! vim: set ft=email }}
|
||||
This is a test email sent from {{site-name}} to confirm that PGP is working as you
|
||||
expect. This email is signed with this key:
|
||||
|
||||
{{site_key}}
|
||||
|
||||
{{#user.pgp_key}}and is encrypted with this key:
|
||||
|
||||
{{user.pgp_key.key_id}}
|
||||
|
||||
{{/user.pgp_key}}
|
||||
You may control your PGP settings here:
|
||||
|
||||
{{root}}/privacy
|
||||
|
||||
--
|
||||
{{owner-name}}
|
||||
{{site-name}}
|
|
@ -1,8 +1,11 @@
|
|||
from flask import Blueprint, render_template, request, redirect
|
||||
from flask import Blueprint, Response, render_template, request, redirect
|
||||
from flask_login import current_user
|
||||
from meta.audit import audit_log
|
||||
from meta.validation import Validation
|
||||
from meta.common import loginrequired
|
||||
from meta.types import User, PGPKey
|
||||
from meta.types import User, PGPKey, EventType
|
||||
from meta.email import send_email
|
||||
from meta.config import _cfg
|
||||
from meta.db import db
|
||||
|
||||
privacy = Blueprint('privacy', __name__, template_folder='../../templates')
|
||||
|
@ -12,6 +15,12 @@ privacy = Blueprint('privacy', __name__, template_folder='../../templates')
|
|||
def privacy_GET():
|
||||
return render_template("privacy.html")
|
||||
|
||||
@privacy.route("/privacy/pubkey")
|
||||
def privacy_pubkey_GET():
|
||||
with open(_cfg("sr.ht", "pgp-pubkey"), "r") as f:
|
||||
pubkey = f.read()
|
||||
return Response(pubkey, mimetype="text/plain")
|
||||
|
||||
@privacy.route("/privacy", methods=["POST"])
|
||||
@loginrequired
|
||||
def privacy_POST():
|
||||
|
@ -30,6 +39,17 @@ def privacy_POST():
|
|||
|
||||
user = User.query.get(current_user.id)
|
||||
user.pgp_key = key
|
||||
audit_log(EventType.changed_pgp_key,
|
||||
"Set default PGP key to {}".format(key.key_id if key else None))
|
||||
db.commit()
|
||||
|
||||
return redirect("/privacy")
|
||||
|
||||
@privacy.route("/privacy/test-email", methods=["POST"])
|
||||
@loginrequired
|
||||
def privacy_testemail_POST():
|
||||
user = User.query.get(current_user.id)
|
||||
send_email("test", user.email, "Test email",
|
||||
encrypt_key=user.pgp_key,
|
||||
site_key=_cfg("sr.ht", "pgp-key-id"))
|
||||
return redirect("/privacy")
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.message import Message
|
||||
from flask_login import current_user
|
||||
from meta.config import _cfg, _cfgi
|
||||
from flask import url_for
|
||||
import html.parser
|
||||
import smtplib
|
||||
import pystache
|
||||
import html.parser
|
||||
import pgpy
|
||||
import os
|
||||
|
||||
from email.mime.text import MIMEText
|
||||
from flask_login import current_user
|
||||
from flask import url_for
|
||||
|
||||
from meta.config import _cfg, _cfgi
|
||||
|
||||
# TODO: move this into celery worker
|
||||
|
||||
site_key, _ = pgpy.PGPKey.from_file(_cfg("sr.ht", "pgp-privkey"))
|
||||
|
||||
def _url_for(ep, **kw):
|
||||
return _cfg("server", "protocol") \
|
||||
+ _cfg("server", "domain") \
|
||||
+ url_for(ep, **kw)
|
||||
|
||||
def send_email(template, to, subject, **kwargs):
|
||||
def send_email(template, to, subject, encrypt_key=None, **kwargs):
|
||||
if _cfg("mail", "smtp-host") == "":
|
||||
return
|
||||
smtp = smtplib.SMTP(_cfg("mail", "smtp-host"), _cfgi("mail", "smtp-port"))
|
||||
|
@ -24,7 +27,7 @@ def send_email(template, to, subject, **kwargs):
|
|||
smtp.starttls()
|
||||
smtp.login(_cfg("mail", "smtp-user"), _cfg("mail", "smtp-password"))
|
||||
with open("emails/" + template) as f:
|
||||
message = MIMEText(html.parser.HTMLParser().unescape(\
|
||||
message = html.parser.HTMLParser().unescape(\
|
||||
pystache.render(f.read(), {
|
||||
'owner-name': _cfg('sr.ht', 'owner-name'),
|
||||
'site-name': _cfg('sr.ht', 'site-name'),
|
||||
|
@ -32,9 +35,38 @@ def send_email(template, to, subject, **kwargs):
|
|||
'root': '{}://{}'.format(
|
||||
_cfg('server', 'protocol'), _cfg('server', 'domain')),
|
||||
**kwargs
|
||||
})))
|
||||
message['Subject'] = subject
|
||||
message['From'] = _cfg("mail", "smtp-user")
|
||||
message['To'] = to
|
||||
smtp.sendmail(_cfg("mail", "smtp-user"), [to], message.as_string())
|
||||
}))
|
||||
multipart = MIMEMultipart(_subtype="signed", micalg="pgp-sha1",
|
||||
protocol="application/pgp-signature")
|
||||
text_part = MIMEText(message)
|
||||
signature = str(site_key.sign(text_part.as_string().replace('\n', '\r\n')))
|
||||
sig_part = Message()
|
||||
sig_part['Content-Type'] = 'application/pgp-signature; name="signature.asc"'
|
||||
sig_part['Content-Description'] = 'OpenPGP digital signature'
|
||||
sig_part.set_payload(signature)
|
||||
multipart.attach(text_part)
|
||||
multipart.attach(sig_part)
|
||||
if not encrypt_key:
|
||||
multipart['Subject'] = subject
|
||||
multipart['From'] = _cfg("mail", "smtp-user")
|
||||
multipart['To'] = to
|
||||
smtp.sendmail(_cfg("mail", "smtp-user"), [to], multipart.as_string(unixfrom=True))
|
||||
else:
|
||||
pubkey, _ = pgpy.PGPKey.from_blob(encrypt_key.key.replace('\r', '').encode('utf-8'))
|
||||
pgp_msg = pgpy.PGPMessage.new(multipart.as_string(unixfrom=True))
|
||||
encrypted = str(pubkey.encrypt(pgp_msg))
|
||||
ver_part = Message()
|
||||
ver_part['Content-Type'] = 'application/pgp-encrypted'
|
||||
ver_part.set_payload("Version: 1")
|
||||
enc_part = Message()
|
||||
enc_part['Content-Type'] = 'application/octet-stream; name="message.asc"'
|
||||
enc_part['Content-Description'] = 'OpenPGP encrypted message'
|
||||
enc_part.set_payload(encrypted)
|
||||
wrapped = MIMEMultipart(_subtype="encrypted", protocol="application/pgp-encrypted")
|
||||
wrapped.attach(ver_part)
|
||||
wrapped.attach(enc_part)
|
||||
wrapped['Subject'] = subject
|
||||
wrapped['From'] = _cfg("mail", "smtp-user")
|
||||
wrapped['To'] = to
|
||||
smtp.sendmail(_cfg("mail", "smtp-user"), [to], wrapped.as_string(unixfrom=True))
|
||||
smtp.quit()
|
||||
|
|
|
@ -26,6 +26,7 @@ class EventType(Enum):
|
|||
linked_external_account = "linked external account"
|
||||
updated_credit_card = "updated credit card"
|
||||
updated_billing_address = "updated billing address"
|
||||
changed_pgp_key = "changed pgp key"
|
||||
|
||||
class AuditLogEntry(Base):
|
||||
__tablename__ = 'audit_log_entry'
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
<h3>Encryption</h3>
|
||||
<p>
|
||||
All emails sent from sr.ht to you are signed with
|
||||
<a href="https://pgp.mit.edu/pks/lookup?search={{_cfg("sr.ht", "pgp-key-id")}}&op=index">
|
||||
{{_cfg("sr.ht", "pgp-key-id")}}
|
||||
</a>.
|
||||
<a href="/privacy/pubkey">{{_cfg("sr.ht", "pgp-key-id")}}</a>.
|
||||
</p>
|
||||
{% if any(current_user.pgp_keys) %}
|
||||
<form method="POST" action="/privacy">
|
||||
|
@ -37,6 +35,9 @@
|
|||
{% endfor %}
|
||||
<button type="submit" class="pull-right btn btn-default">Save</button>
|
||||
</form>
|
||||
<form method="POST" action="/privacy/test-email" style="clear: both; padding-top: 0.5rem">
|
||||
<button type="submit" class="pull-right btn btn-default">Send test email</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>If you <a href="/keys">add a PGP key</a> to your account, we can encrypt
|
||||
emails to you.</p>
|
||||
|
|
Loading…
Reference in New Issue