forked from Coldcard/ckbunker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
policy.py
150 lines (117 loc) · 4.51 KB
/
policy.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
#!/usr/bin/env python
#
# policy.py -- code which knows various details about HSM policy as defined by Coldcard.
#
import re, logging
from decimal import Decimal
from objstruct import ObjectStruct
from persist import BP, settings
from base64 import b64encode, b64decode
logging.getLogger(__name__).addHandler(logging.NullHandler())
def invalid_pincode(code):
return (not code) or (len(code) != 6) or (not code.isdigit())
def web_cleanup(p):
# takes policy details from Vue/Semantic/web browser format into proper JSON-able dict
# - final product should serialize into something the Coldcard will accept
def relist(n):
# split on spaces or commas, assume values don't have either; trim whitespace
if n is None: return n
return [i for i in re.split(r' |,|\n', n) if i]
for fn in ['msg_paths', 'share_xpubs', 'share_addrs']:
p[fn] = relist(p.get(fn, None))
p.period = int(p.period) if p.period else None
for idx, rule in enumerate(p.rules):
for fn in ['whitelist', 'users']:
rule[fn] = relist(rule[fn])
# change from BTC to satoshis (send as string here)
for fn in ['per_period', 'max_amount']:
v = rule.get(fn, None) or None
if v is not None:
try:
v = Decimal(v)
except:
raise ValueError(f"Rule #{idx+1} field {fn} is invalid: {rule[fn]}")
rule[fn] = int(v * Decimal('1E8'))
else:
# cleans up empty strings
rule[fn] = None
# text to number
if not rule.users:
rule.pop('min_users')
else:
rule.min_users = len(rule.users) if rule.min_users == 'all' else int(rule.min_users)
if p.pop('ewaste_enable', False):
p.boot_to_hsm = 'xyzzy' # impossible to enter
assert invalid_pincode(p.boot_to_hsm)
else:
p.boot_to_hsm = p.get('boot_to_hsm') or None
if p.boot_to_hsm:
assert not invalid_pincode(p.boot_to_hsm), \
"Boot to HSM code must be 6 numeric digits."
return p
def web_cookup(proposed):
# converse of above: take Coldcard policy file, and rework it so
# Vue can display on webpage
p = ObjectStruct.promote(proposed)
def unlist(n):
if not n: return ''
return ','.join(n)
for fn in ['msg_paths', 'share_xpubs', 'share_addrs']:
p[fn] = unlist(p.get(fn))
for rule in p.rules:
for fn in ['whitelist', 'users']:
rule[fn] = unlist(rule.get(fn))
for fn in ['per_period', 'max_amount']:
if rule[fn] is not None:
rule[fn] = str(Decimal(rule[fn]) / Decimal('1E8'))
if 'min_users' not in rule:
rule.min_users = 'all'
else:
rule.min_users = str(rule.min_users)
if ('boot_to_hsm' in p) and p.boot_to_hsm and invalid_pincode(p.boot_to_hsm):
p.ewaste_enable = True
else:
p.ewaste_enable = False
return p
def desensitize(policy):
# remove the most sensitive stuff in the policy.
bk = policy.copy()
bk.pop('set_sl', None)
bk.pop('allow_sl', None)
bk.pop('boot_to_hsm', None)
return bk
def decode_sl(xk):
# Unpack what we saved into the Storage Locker
# - 32 bytes of nacl secret box for BunkerPersistance, plus "Bunk" prefix => 36 bytes
# - base64 encoded => 48 bytes (and has no padding)
assert len(xk) == 48, repr(xk)
xk = b64decode(xk)
assert xk[0:4] == b'Bunk'
rv = xk[4:]
assert len(rv) == 32
return rv
def update_sl(proposed):
# We control the set_sl/allow_sl values solely for bunker purposes (sl=storage locker)
# try to use any value already provided (but unlikely)
xk = proposed.get('set_sl', None) or None
if xk:
try:
xk = decode_sl(xk)
except:
logging.error("Unable to decode existing storage locker; replacing", exc_info=1)
xk = None
if not xk:
# capture settings key
xk = BP.key
assert len(xk) == 32
proposed['set_sl'] = b64encode(b'Bunk' + xk).decode('ascii')
if xk != BP.key:
# re-use existing key, and switch over to using new/eixsting key
BP.delete_file()
BP.set_secret(xk)
BP.save()
else:
logging.info("Re-using old secret for holding Bunker settings")
# simple fixed value for how many times we can re-read the storage locker
proposed['allow_sl'] = 13 if BP.get('allow_reboots', True) else 1
# EOF