-
Notifications
You must be signed in to change notification settings - Fork 23
/
persist.py
192 lines (149 loc) · 5.79 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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#!/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)
USB_NCRY_VERSION = 0x01 # default ncry version is 1
# USB encryption versions (default 1)
#
# V2 introduces a new ncry version to close a potential attack vector:
#
# A malicious program may re-initialize the connection encryption by sending the ncry command a second time during USB operation.
# This may prove particularly harmful in HSM mode.
#
# Sending version 0x02 changes the behavior in two ways:
# * All future commands must be encrypted
# * Returns an error if the ncry command is sent again for the duration of the power cycle
#
# If using 0x02 and ckbunker is killed - you also need to re-login to Coldcard
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