onion: add keys and certs to database

This commit is contained in:
Iain Learmonth 2022-11-09 13:36:12 +00:00
parent f603cb9101
commit d5824aa518
4 changed files with 107 additions and 6 deletions

View file

@ -1,3 +1,6 @@
import base64
import hashlib
from app.brm.brn import BRN from app.brm.brn import BRN
from app.extensions import db from app.extensions import db
from app.models import AbstractConfiguration, AbstractResource from app.models import AbstractConfiguration, AbstractResource
@ -18,8 +21,31 @@ class Onion(AbstractConfiguration):
domain_name = db.Column(db.String(255), nullable=False) domain_name = db.Column(db.String(255), nullable=False)
onion_name = db.Column(db.String(56), nullable=False, unique=True) 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") 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): class Eotk(AbstractResource):
group_id = db.Column(db.Integer(), db.ForeignKey("group.id"), nullable=False) group_id = db.Column(db.Integer(), db.ForeignKey("group.id"), nullable=False)

View file

@ -4,9 +4,10 @@ from typing import Optional
from flask import flash, redirect, url_for, render_template, Response, Blueprint from flask import flash, redirect, url_for, render_template, Response, Blueprint
from flask.typing import ResponseReturnValue from flask.typing import ResponseReturnValue
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed, FileRequired
from sqlalchemy import exc from sqlalchemy import exc
from wtforms import StringField, SelectField, SubmitField from wtforms import StringField, SelectField, SubmitField, FileField
from wtforms.validators import DataRequired, Length from wtforms.validators import DataRequired
from app.extensions import db from app.extensions import db
from app.models.base import Group from app.models.base import Group
@ -18,9 +19,17 @@ bp = Blueprint("onion", __name__)
class NewOnionForm(FlaskForm): # type: ignore class NewOnionForm(FlaskForm): # type: ignore
domain_name = StringField('Domain Name', validators=[DataRequired()]) domain_name = StringField('Domain Name', validators=[DataRequired()])
onion_name = StringField('Onion Name', validators=[DataRequired(), Length(min=56, max=56)], # onion_name = StringField('Onion Name', validators=[DataRequired(), Length(min=56, max=56)],
description="Onion service hostname, excluding the .onion suffix") # description="Onion service hostname, excluding the .onion suffix")
description = StringField('Description', validators=[DataRequired()]) 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()]) group = SelectField('Group', validators=[DataRequired()])
submit = SubmitField('Save Changes') submit = SubmitField('Save Changes')
@ -28,6 +37,13 @@ class NewOnionForm(FlaskForm): # type: ignore
class EditOnionForm(FlaskForm): # type: ignore class EditOnionForm(FlaskForm): # type: ignore
description = StringField('Description', validators=[DataRequired()]) description = StringField('Description', validators=[DataRequired()])
group = SelectField('Group', 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') submit = SubmitField('Save Changes')
@ -40,7 +56,17 @@ def onion_new(group_id: Optional[int] = None) -> ResponseReturnValue:
onion = Onion() onion = Onion()
onion.group_id = form.group.data onion.group_id = form.group.data
onion.domain_name = form.domain_name.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.description = form.description.data
onion.created = datetime.utcnow() onion.created = datetime.utcnow()
onion.updated = datetime.utcnow() onion.updated = datetime.utcnow()
@ -72,6 +98,16 @@ def onion_edit(onion_id: int) -> ResponseReturnValue:
if form.validate_on_submit(): if form.validate_on_submit():
onion.group_id = form.group.data onion.group_id = form.group.data
onion.description = form.description.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() onion.updated = datetime.utcnow()
try: try:
db.session.commit() db.session.commit()

View file

@ -331,7 +331,8 @@
<td> <td>
<a href="https://{{ onion.onion_name }}.onion" target="_bypass" rel="noopener noreferer" <a href="https://{{ onion.onion_name }}.onion" target="_bypass" rel="noopener noreferer"
class="btn btn-secondary btn-sm">⎋</a> class="btn btn-secondary btn-sm">⎋</a>
{{ onion.onion_name }} {{ onion.onion_name }}<br>
<span class="text-muted">{{ onion.calculated_onion_name }}</span>
</td> </td>
<td>{{ onion.description }}</td> <td>{{ onion.description }}</td>
<td> <td>

View file

@ -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 ###