mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
Automatic Tor Hidden Service Support (#257)
* Add Tor url to sidebar * Dynamic start/ stop Tor * Start Tor hidden service by default (if possible) * Updates * fixes * Update tor_address.jinja * Update tor_address.jinja * Allow starting Tor after running specter * Delete .env_example
This commit is contained in:
parent
779f8c8ac9
commit
92ebff592c
10 changed files with 339 additions and 122 deletions
|
|
@ -1,3 +0,0 @@
|
|||
# The cleartext password that was entered into:
|
||||
# $ tor --hash-password "your-tor-passphrase"
|
||||
TOR_PASSWORD=your-tor-passphrase
|
||||
35
docs/tor.md
35
docs/tor.md
|
|
@ -14,23 +14,12 @@ Install Tor on the same server that you'll be running Specter Desktop:
|
|||
* [Debian / Ubuntu](https://2019.www.torproject.org/docs/debian.html.en)
|
||||
* [macOS](https://2019.www.torproject.org/docs/tor-doc-osx.html.en)
|
||||
|
||||
### Configure Tor authentication
|
||||
```sh
|
||||
$ tor --hash-password "your-tor-passphrase"
|
||||
```
|
||||
That returns a password hash such as:
|
||||
```sh
|
||||
16:CE9058DA89498A4160373C70FF7FFF70CC2E20B6788FC48F5C35B2E85B
|
||||
```
|
||||
Update your `torrc` config file (usually `/etc/tor/torrc` or `/usr/local/etc/tor/torrc` on macOS Homebrew installs). Uncomment the `ControlPort` line as well as the `HashedControlPassword` line. Remember to paste in your own hashed password result from above.
|
||||
### Configure Tor port
|
||||
Update your `torrc` config file (usually `/etc/tor/torrc` or `/usr/local/etc/tor/torrc` on macOS Homebrew installs) and uncomment the `ControlPort` line.
|
||||
```sh
|
||||
## The port on which Tor will listen for local connections from Tor
|
||||
## controller applications, as documented in control-spec.txt.
|
||||
ControlPort 9051
|
||||
## If you enable the controlport, be sure to enable one of these
|
||||
## authentication methods, to prevent attackers from accessing it.
|
||||
HashedControlPassword 16:CE9058DA89498A4160373C70FF7FFF70CC2E20B6788FC48F5C35B2E85B
|
||||
#CookieAuthentication 1
|
||||
```
|
||||
|
||||
Restart the Tor service:
|
||||
|
|
@ -39,10 +28,10 @@ Restart the Tor service:
|
|||
|
||||
### Running with Tor using command line
|
||||
|
||||
You can start the server and provide your tor password using `--tor=` flag:
|
||||
You can start the server using `--tor` flag:
|
||||
|
||||
```sh
|
||||
$ python3 -m cryptoadvance.specter server --tor=your-tor-passphrase
|
||||
$ python3 -m cryptoadvance.specter server --tor
|
||||
```
|
||||
|
||||
### Configure environment variables
|
||||
|
|
@ -59,22 +48,6 @@ FLASK_ENV=production
|
|||
#FLASK_ENV=development
|
||||
```
|
||||
|
||||
### Specify Tor secrets
|
||||
|
||||
The Tor password that we hashed above will need to be shared with Specter Desktop.
|
||||
|
||||
Copy the example `.env_example` file:
|
||||
```sh
|
||||
$ cp .env_example .env
|
||||
```
|
||||
|
||||
And then edit `.env` and specify `TOR_PASSWORD`:
|
||||
```sh
|
||||
# The cleartext password that was entered into:
|
||||
# $ tor --hash-password "your-tor-passphrase"
|
||||
TOR_PASSWORD=your-tor-passphrase
|
||||
```
|
||||
|
||||
### Launch with Tor
|
||||
|
||||
Now just start Specter Desktop as usual:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ certifi==2019.9.11
|
|||
chardet==3.0.4
|
||||
Click==7.0
|
||||
daemonize==2.5.0
|
||||
ecdsa>=0.13.3
|
||||
Flask==1.1.2
|
||||
Flask-Cors==3.0.8
|
||||
Flask-Login==0.5.0
|
||||
|
|
@ -12,7 +11,7 @@ pyserial==3.4
|
|||
python-dotenv==0.13.0
|
||||
requests==2.23.0
|
||||
six==1.12.0
|
||||
stem==1.7.1
|
||||
stem==1.8.0
|
||||
# only for testing currently
|
||||
docker==4.1.0
|
||||
pytest==5.2.2
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
import atexit
|
||||
import logging
|
||||
from logging.config import dictConfig
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from stem.control import Controller
|
||||
from . import tor_util
|
||||
import click
|
||||
|
||||
import docker
|
||||
|
||||
from .bitcoind import (BitcoindDockerController,
|
||||
fetch_wallet_addresses_for_mining)
|
||||
from .helpers import which
|
||||
from .server import DATA_FOLDER, create_app, init_app
|
||||
|
||||
from os import path
|
||||
|
|
@ -30,15 +29,15 @@ def cli():
|
|||
@click.option("--force", is_flag=True)
|
||||
# options below can help to run it on a remote server,
|
||||
# but better use nginx
|
||||
@click.option("--port") # default - 25441 set to 80 for http, 443 for https
|
||||
@click.option("--host", default="127.0.0.1") # set to 0.0.0.0 to make it available outside
|
||||
@click.option("--port") # default - 25441 set to 80 for http, 443 for https
|
||||
@click.option("--host", default="127.0.0.1") # set to 0.0.0.0 to make it available outside
|
||||
# for https:
|
||||
@click.option("--cert")
|
||||
@click.option("--key")
|
||||
# provide tor password here
|
||||
@click.option("--tor")
|
||||
@click.option('--debug/--no-debug', default=None)
|
||||
@click.option('--tor', is_flag=True)
|
||||
@click.option("--hwibridge", is_flag=True)
|
||||
def server(daemon, stop, restart, force, port, host, cert, key, tor, hwibridge):
|
||||
def server(daemon, stop, restart, force, port, host, cert, key, debug, tor, hwibridge):
|
||||
# we will store our daemon PID here
|
||||
pid_file = path.expanduser(path.join(DATA_FOLDER, "daemon.pid"))
|
||||
toraddr_file = path.expanduser(path.join(DATA_FOLDER, "onion.txt"))
|
||||
|
|
@ -53,7 +52,7 @@ def server(daemon, stop, restart, force, port, host, cert, key, tor, hwibridge):
|
|||
time.sleep(0.3)
|
||||
try:
|
||||
os.remove(pid_file)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
pass
|
||||
elif daemon:
|
||||
if not force:
|
||||
|
|
@ -82,7 +81,7 @@ def server(daemon, stop, restart, force, port, host, cert, key, tor, hwibridge):
|
|||
filename = os.path.join(dirname, filename)
|
||||
if os.path.isfile(filename):
|
||||
extra_files.append(filename)
|
||||
|
||||
|
||||
# if port is not defined - get it from environment
|
||||
if port is None:
|
||||
port = int(os.getenv('PORT', 25441))
|
||||
|
|
@ -108,53 +107,74 @@ def server(daemon, stop, restart, force, port, host, cert, key, tor, hwibridge):
|
|||
protocol = "https"
|
||||
|
||||
if hwibridge:
|
||||
app.logger.info("Running HWI Bridge mode, you can configure access to the API at: %s://%s:%d/hwi/settings" % (protocol, host, port))
|
||||
|
||||
# if tor password is not provided but env variable is set
|
||||
if tor is None and os.getenv('CONNECT_TOR') == 'True':
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv() # Load the secrets from .env
|
||||
tor = os.getenv('TOR_PASSWORD')
|
||||
app.logger.info(
|
||||
"Running HWI Bridge mode, you can configure access \
|
||||
to the API at: %s://%s:%d/hwi/settings"
|
||||
% (protocol, host, port)
|
||||
)
|
||||
|
||||
# debug is false by default
|
||||
def run(debug=False):
|
||||
if tor is not None:
|
||||
from . import tor_util
|
||||
def run(debug=debug):
|
||||
with Controller.from_port() as controller:
|
||||
app.controller = controller
|
||||
port = 5000 # default flask port
|
||||
if 'port' in kwargs:
|
||||
port = kwargs['port']
|
||||
else:
|
||||
kwargs['port'] = port
|
||||
# if we have certificates
|
||||
if "ssl_context" in kwargs:
|
||||
tor_port = 443
|
||||
else:
|
||||
tor_port = 80
|
||||
tor_util.run_on_hidden_service(app,
|
||||
debug=False,
|
||||
tor_password=tor,
|
||||
tor_port=tor_port,
|
||||
save_address_to=toraddr_file,
|
||||
**kwargs)
|
||||
else:
|
||||
app.port = port
|
||||
app.tor_port = tor_port
|
||||
app.save_tor_address_to = toraddr_file
|
||||
if debug and (tor or os.getenv('CONNECT_TOR') == 'True'):
|
||||
print(
|
||||
'* Warning: Cannot use Tor in debug mode. Starting in production mode instead.'
|
||||
)
|
||||
if tor or os.getenv('CONNECT_TOR') == 'True':
|
||||
try:
|
||||
app.tor_enabled = True
|
||||
tor_util.start_hidden_service(app)
|
||||
except Exception as e:
|
||||
print('* Failed to start Tor hidden service: {}'.format(e))
|
||||
print('* Continuing process with Tor disabled')
|
||||
app.tor_service_id = None
|
||||
app.tor_enabled = False
|
||||
else:
|
||||
app.tor_service_id = None
|
||||
app.tor_enabled = False
|
||||
app.run(debug=debug, **kwargs)
|
||||
tor_util.stop_hidden_services(app)
|
||||
|
||||
# check if we should run a daemon or not
|
||||
if daemon or restart:
|
||||
print("Starting server in background...")
|
||||
print("* Hopefully running on %s://%s:%d/" % (protocol, host, port))
|
||||
if tor is not None:
|
||||
print("* For onion address check the file %s" % toraddr_file)
|
||||
# macOS + python3.7 is buggy
|
||||
if sys.platform=="darwin" and (sys.version_info.major==3 and sys.version_info.minor < 8):
|
||||
print("* WARNING: --daemon mode might not work properly in python 3.7 and lower on MacOS. Upgrade to python 3.8+")
|
||||
if sys.platform == "darwin" and \
|
||||
(sys.version_info.major == 3 and sys.version_info.minor < 8):
|
||||
print(
|
||||
"* WARNING: --daemon mode might not work properly in python 3.7 \
|
||||
and lower on MacOS. Upgrade to python 3.8+"
|
||||
)
|
||||
from daemonize import Daemonize
|
||||
d = Daemonize(app="specter", pid=pid_file, action=run)
|
||||
d.start()
|
||||
else:
|
||||
# if not a daemon we can use DEBUG
|
||||
run(app.config['DEBUG'])
|
||||
if debug is None:
|
||||
debug = app.config['DEBUG']
|
||||
run(debug=debug)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--debug/--no-debug', default=False)
|
||||
@click.option('--mining/--no-mining', default=True)
|
||||
@click.option('--docker-tag', "docker_tag", default="latest")
|
||||
def bitcoind(debug,mining, docker_tag):
|
||||
def bitcoind(debug, mining, docker_tag):
|
||||
mining_every_x_seconds = 15
|
||||
if debug:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
|
@ -164,20 +184,33 @@ def bitcoind(debug,mining, docker_tag):
|
|||
my_bitcoind.start_bitcoind()
|
||||
except docker.errors.ImageNotFound:
|
||||
click.echo(" --> Image with tag {} does not exist!".format(docker_tag))
|
||||
click.echo(" --> Try to download first with docker pull registry.gitlab.com/cryptoadvance/specter-desktop/python-bitcoind:{}".format(docker_tag))
|
||||
click.echo(
|
||||
" --> Try to download first with docker pull \
|
||||
registry.gitlab.com/cryptoadvance/specter-desktop/python-bitcoind:{}"
|
||||
.format(docker_tag)
|
||||
)
|
||||
sys.exit(1)
|
||||
tags_of_image = [ image.split(":")[-1] for image in my_bitcoind.btcd_container.image.tags]
|
||||
if not docker_tag in tags_of_image:
|
||||
tags_of_image = [image.split(":")[-1] for image in my_bitcoind.btcd_container.image.tags]
|
||||
if docker_tag not in tags_of_image:
|
||||
click.echo(" --> The running docker container is not the tag you requested!")
|
||||
click.echo(" --> please stop first with docker stop {}".format(my_bitcoind.btcd_container.id))
|
||||
click.echo(
|
||||
" --> please stop first with docker stop {}"
|
||||
.format(my_bitcoind.btcd_container.id)
|
||||
)
|
||||
sys.exit(1)
|
||||
click.echo(" --> containerImage: %s" % my_bitcoind.btcd_container.image.tags)
|
||||
click.echo(" --> url: %s" % my_bitcoind.rpcconn.render_url())
|
||||
click.echo(" --> user, password: bitcoin, secret")
|
||||
click.echo(" --> host, port: localhost, 18443")
|
||||
click.echo(" --> bitcoin-cli: bitcoin-cli -regtest -rpcuser=bitcoin -rpcpassword=secret getblockchaininfo ")
|
||||
click.echo(
|
||||
" --> bitcoin-cli: bitcoin-cli -regtest -rpcuser=bitcoin \
|
||||
-rpcpassword=secret getblockchaininfo "
|
||||
)
|
||||
if mining:
|
||||
click.echo(" --> Now, mining a block every %i seconds. Avoid it via --no-mining" % mining_every_x_seconds)
|
||||
click.echo(
|
||||
" --> Now, mining a block every %i seconds. Avoid it via --no-mining" %
|
||||
mining_every_x_seconds
|
||||
)
|
||||
# Get each address some coins
|
||||
try:
|
||||
for address in fetch_wallet_addresses_for_mining():
|
||||
|
|
@ -188,22 +221,21 @@ def bitcoind(debug,mining, docker_tag):
|
|||
|
||||
# make them spendable
|
||||
my_bitcoind.mine(block_count=100)
|
||||
click.echo(" --> ",nl=False)
|
||||
click.echo(" --> ", nl=False)
|
||||
i = 0
|
||||
while True:
|
||||
my_bitcoind.mine()
|
||||
click.echo("%i"% (i%10),nl=False)
|
||||
if i%10 == 9:
|
||||
click.echo(" ",nl=False)
|
||||
click.echo("%i" % (i % 10), nl=False)
|
||||
if i % 10 == 9:
|
||||
click.echo(" ", nl=False)
|
||||
i += 1
|
||||
if i >= 50:
|
||||
i=0
|
||||
i = 0
|
||||
click.echo(" ")
|
||||
click.echo(" --> ",nl=False)
|
||||
click.echo(" --> ", nl=False)
|
||||
time.sleep(mining_every_x_seconds)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# central and early configuring of logging
|
||||
# see https://flask.palletsprojects.com/en/1.1.x/logging/#basic-configuration
|
||||
|
|
@ -222,4 +254,4 @@ if __name__ == "__main__":
|
|||
'handlers': ['wsgi']
|
||||
}
|
||||
})
|
||||
cli()
|
||||
cli()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ from io import BytesIO
|
|||
import traceback
|
||||
from .devices.electrum import b43_decode
|
||||
from binascii import b2a_base64
|
||||
from .tor_util import start_hidden_service, stop_hidden_services
|
||||
from stem.control import Controller
|
||||
|
||||
from pathlib import Path
|
||||
env_path = Path('.') / '.flaskenv'
|
||||
|
|
@ -55,12 +57,31 @@ def selfcheck():
|
|||
if app.config.get('LOGIN_DISABLED'):
|
||||
app.login('admin')
|
||||
|
||||
|
||||
########## template injections #############
|
||||
@app.context_processor
|
||||
def inject_debug():
|
||||
''' Can be used in all jinja2 templates '''
|
||||
return dict(debug=app.config['DEBUG'])
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def inject_tor():
|
||||
if app.config['DEBUG']:
|
||||
return dict(tor_service_id='', tor_enabled=False)
|
||||
if request.args.get('action', '') == 'stoptor' or request.args.get('action', '') == 'starttor':
|
||||
if hasattr(current_user, 'is_admin') and current_user.is_admin:
|
||||
try:
|
||||
current_hidden_services = app.controller.list_ephemeral_hidden_services()
|
||||
except Exception:
|
||||
current_hidden_services = []
|
||||
if request.args.get('action', '') == 'stoptor' and len(current_hidden_services) != 0:
|
||||
stop_hidden_services(app)
|
||||
if request.args.get('action', '') == 'starttor' and len(current_hidden_services) == 0:
|
||||
start_hidden_service(app)
|
||||
return dict(tor_service_id=app.tor_service_id, tor_enabled=app.tor_enabled)
|
||||
|
||||
|
||||
################ routes ####################
|
||||
@app.route('/wallets/<wallet_alias>/combine/', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
|
|
@ -282,10 +303,10 @@ def general_settings():
|
|||
hwi_bridge_url = request.form['hwi_bridge_url']
|
||||
if current_user.is_admin:
|
||||
loglevel = request.form['loglevel']
|
||||
|
||||
|
||||
if action == "save":
|
||||
if current_user.is_admin:
|
||||
set_loglevel(app,loglevel)
|
||||
set_loglevel(app, loglevel)
|
||||
|
||||
app.specter.update_explorer(explorer, current_user)
|
||||
app.specter.update_hwi_bridge_url(hwi_bridge_url, current_user)
|
||||
|
|
@ -335,7 +356,7 @@ def bitcoin_core_settings():
|
|||
passwd = request.form['password']
|
||||
port = request.form['port']
|
||||
host = request.form['host']
|
||||
|
||||
|
||||
# protocol://host
|
||||
if "://" in host:
|
||||
arr = host.split("://")
|
||||
|
|
@ -706,7 +727,7 @@ def new_wallet(wallet_type):
|
|||
err = "%r" % e
|
||||
wallet.getdata()
|
||||
return redirect("/wallets/%s/" % wallet.alias)
|
||||
|
||||
|
||||
return render_template(
|
||||
"wallet/new_wallet/new_wallet.jinja",
|
||||
wallet_type=wallet_type,
|
||||
|
|
|
|||
31
src/cryptoadvance/specter/static/img/tor.svg
Normal file
31
src/cryptoadvance/specter/static/img/tor.svg
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
|
||||
<stop stop-color="#420C5D" offset="0%"></stop>
|
||||
<stop stop-color="#951AD1" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<path d="M25,29 C152.577777,29 256,131.974508 256,259 C256,386.025492 152.577777,489 25,489 L25,29 Z" id="path-2"></path>
|
||||
<filter x="-18.2%" y="-7.4%" width="129.4%" height="114.8%" filterUnits="objectBoundingBox" id="filter-3">
|
||||
<feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="tor-browser-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="icon_512x512">
|
||||
<g id="Group">
|
||||
<g id="tb_icon/Stable">
|
||||
<g id="Stable">
|
||||
<circle id="background" fill="#F2E4FF" fill-rule="nonzero" cx="256" cy="256" r="246"></circle>
|
||||
<path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,155.463547 256.525143,155.17259 L256.525143,124.146588 C329.115485,124.430449 387.881799,183.338693 387.881799,255.992903 C387.881799,328.654211 329.115485,387.562455 256.525143,387.846316 L256.525143,356.820314 Z M256.525143,201.718689 C286.266674,202.00255 310.3026,226.180407 310.3026,255.992903 C310.3026,285.812497 286.266674,309.990353 256.525143,310.274214 L256.525143,201.718689 Z M0,255.992903 C0,397.384044 114.60886,512 256,512 C397.384044,512 512,397.384044 512,255.992903 C512,114.60886 397.384044,0 256,0 C114.60886,0 0,114.60886 0,255.992903 Z" id="center" fill="url(#linearGradient-1)"></path>
|
||||
<g id="half" transform="translate(140.500000, 259.000000) scale(-1, 1) translate(-140.500000, -259.000000) ">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use>
|
||||
<use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -0,0 +1,144 @@
|
|||
<style>
|
||||
#onion-address-container {
|
||||
max-width: 220px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
background: #7D4698;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#tor-open-btn {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
margin-left: 210px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
#tor-close-btn {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 0.8em;
|
||||
float: right;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.onion-address {
|
||||
word-wrap: break-word;
|
||||
color: #fff;
|
||||
font-size: 0.7em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.onion-address:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dot {
|
||||
cursor: pointer;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dot-on {
|
||||
{% if tor_enabled %}
|
||||
background-color: #0f0;
|
||||
{% else %}
|
||||
background-color: #ccc;
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
.dot-off {
|
||||
{% if tor_enabled %}
|
||||
background-color: #f00;
|
||||
{% else %}
|
||||
background-color: #ccc;
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
.tor-btn {
|
||||
margin-top: 10px;
|
||||
width: 90px;
|
||||
border: none;
|
||||
font-size: 0.8em;
|
||||
color: #fff;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #59316B;
|
||||
background: #7D4698;
|
||||
}
|
||||
|
||||
.tor-btn:hover {
|
||||
cursor: pointer;
|
||||
background: #59316B;
|
||||
}
|
||||
</style>
|
||||
<div style="position: relative;">
|
||||
<img id="tor-open-btn" src="/static/img/tor.svg"/>
|
||||
<span class="dot {% if tor_service_id %}dot-on{% else %}dot-off{% endif %}" id="tor-symbol-status-dot" style="margin-left: 227px;"></span>
|
||||
</div>
|
||||
<div id="onion-address-container" class="hidden">
|
||||
<div style="position: relative;">
|
||||
<img id="tor-close-btn" src="/static/img/tor.svg" style="width: 24px; position: absolute; left: 177px;"/>
|
||||
<span class="dot {% if tor_service_id %}dot-on{% else %}dot-off{% endif %}" id="tor-popup-status-dot" style="left: 193px;"></span>
|
||||
</div>
|
||||
{% if tor_service_id %}
|
||||
<span style="font-size: 0.92em; color: #fff;">Specter Running on Tor</span><br>
|
||||
<div style="margin-top: 5px;"> <span style="font-size: 0.75em; color: #fff;">Running at:</span></div>
|
||||
<div class="center">
|
||||
<span title="Copy Tor address" class="onion-address centered" onclick="copyText('{{ tor_service_id }}.onion', 'Copied Tor hidden service address: {{ tor_service_id }}.onion')">
|
||||
{{ tor_service_id }}.onion
|
||||
</span>
|
||||
</div>
|
||||
{% if current_user.is_admin and (tor_service_id and tor_service_id + '.onion' not in request.url) %}
|
||||
<form action="?" class="center">
|
||||
<button class="tor-btn" type="submit" name="action" value="stoptor">Stop Tor</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if debug %}
|
||||
<span style="font-size: 0.92em; color: #fff;">Tor is unavailable...</span><br>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="font-size: 0.75em; color: #fff;">You are running Specter in debug mode, but Tor hidden serices are only available in production mode.</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<span style="font-size: 0.92em; color: #fff;">Tor service is down...</span><br>
|
||||
{% if current_user.is_admin and ('.onion' not in request.url) %}
|
||||
<form action="?" class="center">
|
||||
<button class="tor-btn" type="submit" name="action" value="starttor">Start Tor</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
let torOpenBtn = document.getElementById('tor-open-btn');
|
||||
let torCloseBtn = document.getElementById('tor-close-btn');
|
||||
let torDataContainer = document.getElementById('onion-address-container');
|
||||
let torPopupStatusDot = document.getElementById('tor-popup-status-dot');
|
||||
let torSymbolStatusDot = document.getElementById('tor-symbol-status-dot');
|
||||
|
||||
function openTorPopup() {
|
||||
torOpenBtn.style.display = 'none';
|
||||
torDataContainer.style.display = 'block';
|
||||
torPopupStatusDot.style.display = 'inline-block';
|
||||
torSymbolStatusDot.style.display = 'none';
|
||||
}
|
||||
|
||||
function closeTorPopup() {
|
||||
torOpenBtn.style.display = 'block';
|
||||
torDataContainer.style.display = 'none';
|
||||
torPopupStatusDot.style.display = 'none';
|
||||
torSymbolStatusDot.style.display = 'inline-block';
|
||||
}
|
||||
torSymbolStatusDot.addEventListener("click", openTorPopup);
|
||||
torOpenBtn.addEventListener("click", openTorPopup);
|
||||
|
||||
torPopupStatusDot.addEventListener("click", closeTorPopup);
|
||||
torCloseBtn.addEventListener("click", closeTorPopup);
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
{% from 'includes/sidebar/components/sidebar_btn.jinja' import sidebar_btn %}
|
||||
<div id="side-content">
|
||||
<nav class="side">
|
||||
{% include "includes/sidebar/components/tor_address.jinja" %}
|
||||
{% if specter.info["initialblockdownload"] %}
|
||||
<p class="warning">⚠️<br>Blockchain is still syncing...<br>Data may be shown incorrectly.</p>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,45 +1,62 @@
|
|||
import os
|
||||
import stem
|
||||
from stem.control import Controller
|
||||
from .server import DATA_FOLDER
|
||||
|
||||
def run_on_hidden_service(app, tor_password=None, tor_port=80, save_address_to=None, **kwargs):
|
||||
port = 5000 # default flask port
|
||||
if "port" in kwargs:
|
||||
port = kwargs["port"]
|
||||
|
||||
def start_hidden_service(app):
|
||||
app.controller.reconnect()
|
||||
key_path = os.path.abspath(
|
||||
os.path.expanduser(os.path.join(DATA_FOLDER, '.tor_service_key'))
|
||||
)
|
||||
app.tor_service_id = None
|
||||
|
||||
if not os.path.exists(key_path):
|
||||
service = app.controller.create_ephemeral_hidden_service(
|
||||
{app.tor_port: app.port}, await_publication=True
|
||||
)
|
||||
app.tor_service_id = service.service_id
|
||||
print(
|
||||
'* Started a new hidden service with the address of %s.onion'
|
||||
% app.tor_service_id
|
||||
)
|
||||
|
||||
with open(key_path, 'w') as key_file:
|
||||
key_file.write(
|
||||
'%s:%s' % (service.private_key_type, service.private_key)
|
||||
)
|
||||
else:
|
||||
kwargs["port"] = port
|
||||
with open(key_path) as key_file:
|
||||
key_type, key_content = key_file.read().split(':', 1)
|
||||
|
||||
with Controller.from_port() as controller:
|
||||
print(' * Connecting to tor')
|
||||
controller.authenticate(tor_password)
|
||||
service = app.controller.create_ephemeral_hidden_service(
|
||||
{app.tor_port: app.port},
|
||||
key_type=key_type,
|
||||
key_content=key_content,
|
||||
await_publication=True,
|
||||
)
|
||||
app.tor_service_id = service.service_id
|
||||
print('* Resumed %s.onion' % app.tor_service_id)
|
||||
|
||||
key_path = os.path.abspath(os.path.expanduser(os.path.join(DATA_FOLDER,'.tor_service_key')))
|
||||
tor_service_id = None
|
||||
# save address to file
|
||||
if app.save_tor_address_to is not None:
|
||||
with open(app.save_tor_address_to, 'w') as f:
|
||||
f.write('%s.onion' % app.tor_service_id)
|
||||
app.tor_service_id = app.tor_service_id
|
||||
app.tor_enabled = True
|
||||
|
||||
if not os.path.exists(key_path):
|
||||
service = controller.create_ephemeral_hidden_service({tor_port: port}, await_publication = True)
|
||||
tor_service_id = service.service_id
|
||||
print("* Started a new hidden service with the address of %s.onion" % tor_service_id)
|
||||
|
||||
with open(key_path, 'w') as key_file:
|
||||
key_file.write('%s:%s' % (service.private_key_type, service.private_key))
|
||||
def stop_hidden_services(app):
|
||||
try:
|
||||
app.controller.reconnect()
|
||||
hidden_services = app.controller.list_ephemeral_hidden_services()
|
||||
print(' * Shutting down our hidden service')
|
||||
for tor_service_id in hidden_services:
|
||||
app.controller.remove_ephemeral_hidden_service(tor_service_id)
|
||||
# Sanity
|
||||
if (len(app.controller.list_ephemeral_hidden_services()) != 0):
|
||||
print(' * Failed to shut down our hidden services...')
|
||||
else:
|
||||
with open(key_path) as key_file:
|
||||
key_type, key_content = key_file.read().split(':', 1)
|
||||
|
||||
service = controller.create_ephemeral_hidden_service({tor_port: port}, key_type = key_type, key_content = key_content, await_publication = True)
|
||||
tor_service_id = service.service_id
|
||||
print("* Resumed %s.onion" % tor_service_id)
|
||||
|
||||
# save address to file
|
||||
if save_address_to is not None:
|
||||
with open(save_address_to, "w") as f:
|
||||
f.write("%s.onion" % tor_service_id)
|
||||
|
||||
try:
|
||||
app.run(**kwargs)
|
||||
finally:
|
||||
if tor_service_id:
|
||||
print(" * Shutting down our hidden service")
|
||||
controller.remove_ephemeral_hidden_service(tor_service_id)
|
||||
print(' * Hidden services were shut down successfully')
|
||||
app.tor_service_id = None
|
||||
except Exception:
|
||||
pass # we tried...
|
||||
|
|
|
|||
|
|
@ -241,6 +241,8 @@ def app(specter_regtest_configured):
|
|||
app.app_context().push()
|
||||
app.config["TESTING"]=True
|
||||
app.testing = True
|
||||
app.tor_service_id = None
|
||||
app.tor_enabled = False
|
||||
init_app(app, specter=specter_regtest_configured)
|
||||
return app
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue