From d5824aa518d3e3386351be57b7dcef2d89163666 Mon Sep 17 00:00:00 2001 From: Iain Learmonth Date: Wed, 9 Nov 2022 13:36:12 +0000 Subject: [PATCH] onion: add keys and certs to database --- app/models/onions.py | 26 +++++++++++ app/portal/onion.py | 46 +++++++++++++++++-- app/portal/templates/tables.html.j2 | 3 +- ...e00f86823_adds_keys_and_certs_to_onions.py | 38 +++++++++++++++ 4 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 migrations/versions/c4ce00f86823_adds_keys_and_certs_to_onions.py diff --git a/app/models/onions.py b/app/models/onions.py index d8fdac2..8de5740 100644 --- a/app/models/onions.py +++ b/app/models/onions.py @@ -1,3 +1,6 @@ +import base64 +import hashlib + from app.brm.brn import BRN from app.extensions import db from app.models import AbstractConfiguration, AbstractResource @@ -18,8 +21,31 @@ class Onion(AbstractConfiguration): domain_name = db.Column(db.String(255), nullable=False) onion_name = db.Column(db.String(56), nullable=False, unique=True) + onion_public_key = db.Column(db.LargeBinary, nullable=False) + onion_private_key = db.Column(db.LargeBinary, nullable=False) + + tls_public_key = db.Column(db.LargeBinary, nullable=False) + tls_private_key = db.Column(db.LargeBinary, nullable=False) + group = db.relationship("Group", back_populates="onions") + @property + def calculated_onion_name(self): + p = self.onion_public_key[32:] + + h = hashlib.sha3_256() + h.update(b".onion checksum") + h.update(p) + h.update(b"\x03") + checksum = h.digest() + + result = bytearray(p) + result.extend(checksum[0:2]) + result.append(0x03) + + onion = base64.b32encode(result).decode("utf-8").strip("=") + return onion.lower() + class Eotk(AbstractResource): group_id = db.Column(db.Integer(), db.ForeignKey("group.id"), nullable=False) diff --git a/app/portal/onion.py b/app/portal/onion.py index cf0db29..11b4a63 100644 --- a/app/portal/onion.py +++ b/app/portal/onion.py @@ -4,9 +4,10 @@ from typing import Optional from flask import flash, redirect, url_for, render_template, Response, Blueprint from flask.typing import ResponseReturnValue from flask_wtf import FlaskForm +from flask_wtf.file import FileAllowed, FileRequired from sqlalchemy import exc -from wtforms import StringField, SelectField, SubmitField -from wtforms.validators import DataRequired, Length +from wtforms import StringField, SelectField, SubmitField, FileField +from wtforms.validators import DataRequired from app.extensions import db from app.models.base import Group @@ -18,9 +19,17 @@ bp = Blueprint("onion", __name__) class NewOnionForm(FlaskForm): # type: ignore domain_name = StringField('Domain Name', validators=[DataRequired()]) - onion_name = StringField('Onion Name', validators=[DataRequired(), Length(min=56, max=56)], - description="Onion service hostname, excluding the .onion suffix") + # onion_name = StringField('Onion Name', validators=[DataRequired(), Length(min=56, max=56)], + # description="Onion service hostname, excluding the .onion suffix") description = StringField('Description', validators=[DataRequired()]) + onion_private_key = FileField('Onion Private Key', validators=[FileRequired()]) + onion_public_key = FileField('Onion Public Key', + description="The onion hostname will be automatically calculated from the public key.", + validators=[FileRequired()]) + tls_private_key = FileField('TLS Private Key (PEM format)', + description=("If no TLS key and certificate are provided, a self-signed certificate " + "will be generated.")) + tls_public_key = FileField('TLS Certificate (PEM format)') group = SelectField('Group', validators=[DataRequired()]) submit = SubmitField('Save Changes') @@ -28,6 +37,13 @@ class NewOnionForm(FlaskForm): # type: ignore class EditOnionForm(FlaskForm): # type: ignore description = StringField('Description', validators=[DataRequired()]) group = SelectField('Group', validators=[DataRequired()]) + onion_private_key = FileField('Onion Private Key') + onion_public_key = FileField('Onion Public Key', + description="The onion hostname will be automatically calculated from the public key.") + tls_private_key = FileField('TLS Private Key (PEM format)', + description="If no file is submitted, the TLS key will remain unchanged.") + tls_public_key = FileField('TLS Certificate (PEM format)', + description="If no file is submitted, the TLS certificate will remain unchanged.") submit = SubmitField('Save Changes') @@ -40,7 +56,17 @@ def onion_new(group_id: Optional[int] = None) -> ResponseReturnValue: onion = Onion() onion.group_id = form.group.data onion.domain_name = form.domain_name.data - onion.onion_name = form.onion_name.data + # onion.onion_name = form.onion_name.data + for at in [ + "onion_private_key", + "onion_public_key", + "tls_private_key", + "tls_public_key" + ]: + print(f"testing {at}") + if form.__getattribute__(at).data is not None: + print(f"Setting {at}") + onion.__setattr__(at, form.__getattribute__(at).data.read()) onion.description = form.description.data onion.created = datetime.utcnow() onion.updated = datetime.utcnow() @@ -72,6 +98,16 @@ def onion_edit(onion_id: int) -> ResponseReturnValue: if form.validate_on_submit(): onion.group_id = form.group.data onion.description = form.description.data + for at in [ + "onion_private_key", + "onion_public_key", + "tls_private_key", + "tls_public_key" + ]: + print(f"testing {at}") + if form.__getattribute__(at).data is not None: + print(f"Setting {at}") + onion.__setattr__(at, form.__getattribute__(at).data.read()) onion.updated = datetime.utcnow() try: db.session.commit() diff --git a/app/portal/templates/tables.html.j2 b/app/portal/templates/tables.html.j2 index 9048004..df25044 100644 --- a/app/portal/templates/tables.html.j2 +++ b/app/portal/templates/tables.html.j2 @@ -331,7 +331,8 @@ - {{ onion.onion_name }} + {{ onion.onion_name }}
+ {{ onion.calculated_onion_name }} {{ onion.description }} diff --git a/migrations/versions/c4ce00f86823_adds_keys_and_certs_to_onions.py b/migrations/versions/c4ce00f86823_adds_keys_and_certs_to_onions.py new file mode 100644 index 0000000..aacf93f --- /dev/null +++ b/migrations/versions/c4ce00f86823_adds_keys_and_certs_to_onions.py @@ -0,0 +1,38 @@ +"""adds keys and certs to onions + +Revision ID: c4ce00f86823 +Revises: 45fedef32318 +Create Date: 2022-11-09 11:07:49.780172 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c4ce00f86823' +down_revision = '45fedef32318' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('onion', schema=None) as batch_op: + batch_op.add_column(sa.Column('onion_public_key', sa.LargeBinary())) + batch_op.add_column(sa.Column('onion_private_key', sa.LargeBinary())) + batch_op.add_column(sa.Column('tls_public_key', sa.LargeBinary())) + batch_op.add_column(sa.Column('tls_private_key', sa.LargeBinary())) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('onion', schema=None) as batch_op: + batch_op.drop_column('tls_private_key') + batch_op.drop_column('tls_public_key') + batch_op.drop_column('onion_private_key') + batch_op.drop_column('onion_public_key') + + # ### end Alembic commands ###