forked from Coldcard/ckbunker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpersist.py
178 lines (136 loc) · 5.12 KB
/
persist.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#!/usr/bin/env python
#
# Persistent data for Bunker itself. Trying to minimize this for privacy.
#
import os, yaml, nacl.secret, logging
from utils import Singleton, xfp2str, json_dumps, json_loads, WatchableMixin
from hashlib import sha256
from objstruct import ObjectStruct
logging.getLogger(__name__).addHandler(logging.NullHandler())
# globals, used system-wide
settings = None
BP = None
# System-wide settings for Bunker itself.
#
class Settings(metaclass=Singleton):
# web server port
PORT_NUMBER = 9823
# session idle time, before we kick you out and require re-auth (seconds)
MAX_IDLE_TIME = 10*60
# max time between showing login page, and the would-be user entering something useful (seconds)
MAX_LOGIN_WAIT_TIME = 5*60
# bogus fixed password to get started
MASTER_PW = 'test1234'
# default is harder captcha
EASY_CAPTCHA = False
# default for "allow reboot of bunker"
# - can you restart the bunker w/o restarting the Coldcard HSM?
ALLOW_REBOOTS = True
# path to data files
DATA_FILES = './data'
# endpoint to use for sending txn; we assume it's Explora protocol (Blockstream.info)
EXPLORA = 'http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion'
# port number for local instance of tord
# - will try 9051 and 9151
# - but first /var/run/tor/control as unix socket
TORD_PORT = 'default'
# for broadcasting, socks proxy via Tord
TOR_SOCKS = 'socks5h://127.0.0.1:9150'
# unix pipe for local Coldcard Simulator
SIMULATOR_SOCK = '/tmp/ckcc-simulator.sock'
# delay between retries connecting to missing/awol Coldcard
RECONNECT_DELAY = 10 # seconds between retries
PING_RATE = 15 # seconds between pings (CC status checks)
def read(self, fobj):
t = yaml.safe_load(fobj)
if not t: return
for k,v in t.items():
if k.upper() != k or k[0]=='_':
logging.error(f"{k}: must be upper case")
continue
if not hasattr(self, k):
logging.error(f"{k}: unknown setting")
continue
setattr(self, k, v)
@classmethod
def make_sample(cls):
# produce an example config file
d = {}
x = cls()
for k in dir(x):
if k.upper() != k or k[0]=='_': continue
d[k] = getattr(x, k)
return yaml.safe_dump(d)
@classmethod
def startup(cls, config_file=None):
# creates singleton
global settings, BP
# only safe place to create singletons is here
assert not settings and not BP
settings = Settings()
if config_file:
settings.read(config_file)
# load defaults into BP
BP = BunkerPersistance()
BP.reset()
# Store some state, encrypted.
# - inial values are the settings, but lower case for some reason
# - some are adjustable on "Bunker Setup" page
class BunkerPersistance(WatchableMixin, dict, metaclass=Singleton):
fields = ['tor_enabled', 'onion_pk', 'onion_addr', 'allow_reboots',
'easy_captcha', 'master_pw']
def __init__(self):
super(BunkerPersistance, self).__init__()
self.filename = None
self.reset()
def reset(self):
self.clear()
self.set_secret(os.urandom(32))
self.set_defaults()
def set_defaults(self):
# defaults here
for fn in self.fields:
if fn not in self:
self[fn] = getattr(settings, fn.upper(), None)
def set_secret(self, key):
# setup for reading/writing using indicated key
assert len(key) == 32
self.key = key
self.box = nacl.secret.SecretBox(self.key)
# calc filename
bn = 'bp-%s.dat' % sha256(sha256(b'salty' + self.key).digest()).hexdigest()[-16:].lower()
self.filename = os.path.join(settings.DATA_FILES, bn)
def open(self, key):
# Given a private key (via storage locker) open a Nacl secret box
# and use that for the data.
self.set_secret(key)
try:
with open(self.filename, 'rb') as fp:
d = self.box.decrypt(fp.read())
d = json_loads(d)
except FileNotFoundError:
logging.info("%s: not found (probably fine)" % self.filename)
return True
self.update(d)
# copy a setting to status (XXX feels wrong)
from status import STATUS
STATUS.tor_enabled = self.get('tor_enabled', False)
logging.info(f"Got bunker settings from: {self.filename}")
def save(self):
fn = self.filename
tmp = fn + '.tmp'
with open(tmp, 'wb') as fp:
d = json_dumps(dict(self)).encode('utf8')
d = self.box.encrypt(d)
fp.write(d)
os.rename(tmp, fn)
logging.info(f"Saved bunker settings to: {fn}")
self.notify_watchers()
def delete_file(self):
# useful when changing keys; old file won't be readable
try:
os.unlink(self.filename)
logging.info(f"Deleted bunker settings in: {self.filename}")
except:
pass
# EOF