tfc-mirror/src/common/db_onion.py

105 lines
4.0 KiB
Python

#!/usr/bin/env python3.7
# -*- coding: utf-8 -*-
"""
TFC - Onion-routed, endpoint secure messaging system
Copyright (C) 2013-2019 Markus Ottela
This file is part of TFC.
TFC is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TFC. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import typing
import nacl.signing
from src.common.crypto import auth_and_decrypt, csprng, encrypt_and_sign
from src.common.encoding import pub_key_to_onion_address, pub_key_to_short_address
from src.common.exceptions import CriticalError
from src.common.misc import ensure_dir
from src.common.output import phase
from src.common.statics import CONFIRM_CODE_LENGTH, DIR_USER_DATA, DONE, ONION_SERVICE_PRIVATE_KEY_LENGTH, TX
if typing.TYPE_CHECKING:
from src.common.db_masterkey import MasterKey
class OnionService(object):
"""\
OnionService object manages the persistent Ed25519 key used
to create a v3 Tor Onion Service on the Networked Computer.
The reason the key is generated by Transmitter Program on Source
Computer, is this ensures that even when Networked Computer runs an
amnesic Linux distribution like Tails, the long term private
signing key is not lost between sessions.
The private key for Onion Service can not be kept as protected as
TFC's other private message/header keys (that never leave
Source/Destination computer). This is however OK, as the Onion
Service private key is only as secure as the networked endpoint
anyway.
"""
def __init__(self, master_key: 'MasterKey') -> None:
"""Create a new OnionService object."""
self.master_key = master_key
self.file_name = f'{DIR_USER_DATA}{TX}_onion_db'
self.is_delivered = False
self.conf_code = csprng(CONFIRM_CODE_LENGTH)
ensure_dir(DIR_USER_DATA)
if os.path.isfile(self.file_name):
self.onion_private_key = self.load_onion_service_private_key()
else:
self.onion_private_key = self.new_onion_service_private_key()
self.store_onion_service_private_key()
self.public_key = bytes(nacl.signing.SigningKey(seed=self.onion_private_key).verify_key)
self.user_onion_address = pub_key_to_onion_address(self.public_key)
self.user_short_address = pub_key_to_short_address(self.public_key)
@staticmethod
def new_onion_service_private_key() -> bytes:
"""Generate a new Onion Service private key and store it."""
phase("Generate Tor OS key")
onion_private_key = csprng(ONION_SERVICE_PRIVATE_KEY_LENGTH)
phase(DONE)
return onion_private_key
def store_onion_service_private_key(self) -> None:
"""Store Onion Service private key to an encrypted database."""
ct_bytes = encrypt_and_sign(self.onion_private_key, self.master_key.master_key)
ensure_dir(DIR_USER_DATA)
with open(self.file_name, 'wb+') as f:
f.write(ct_bytes)
def load_onion_service_private_key(self) -> bytes:
"""Load the Onion Service private key from the encrypted database."""
with open(self.file_name, 'rb') as f:
ct_bytes = f.read()
onion_private_key = auth_and_decrypt(ct_bytes, self.master_key.master_key, database=self.file_name)
if len(onion_private_key) != ONION_SERVICE_PRIVATE_KEY_LENGTH:
raise CriticalError("Invalid Onion Service private key length.")
return onion_private_key
def new_confirmation_code(self) -> None:
"""Generate new confirmation code for Onion Service data."""
self.conf_code = csprng(CONFIRM_CODE_LENGTH)