5
5
import uasyncio as asyncio
6
6
import binascii
7
7
import json
8
-
8
+ from . import core
9
9
from .core import log_info , log_warn , ble , register_irq_handler
10
10
from .device import DeviceConnection
11
11
27
27
_DEFAULT_PATH = "ble_secrets.json"
28
28
29
29
# Maintain list of known keys, newest at the bottom / end.
30
- _secrets = []
30
+ _secrets = {}
31
31
_modified = False
32
32
_path = None
33
33
34
+ # If set, limit the pairing db to this many peers
35
+ limit_peers = None
36
+
37
+ SEC_TYPES_SELF = (10 , ) # todo add ident for btstack to tuple
38
+ SEC_TYPES_PEER = (1 , 2 , )
39
+
34
40
35
41
# Must call this before stack startup.
36
42
def load_secrets (path = None ):
37
- global _path , _secrets
43
+ global _path , _secrets , limit_peers
38
44
39
45
# Use path if specified, otherwise use previous path, otherwise use
40
46
# default path.
41
47
_path = path or _path or _DEFAULT_PATH
42
48
43
49
# Reset old secrets.
44
- _secrets = []
50
+ _secrets . clear ()
45
51
try :
46
52
with open (_path , "r" ) as f :
47
53
entries = json .load (f )
54
+ # Newest entries at at the end, load them first
48
55
for sec_type , key , value in entries :
56
+ if sec_type not in _secrets :
57
+ _secrets [sec_type ] = []
49
58
# Decode bytes from hex.
50
- _secrets .append (((sec_type , binascii .a2b_base64 (key )), binascii .a2b_base64 (value )))
59
+ _secrets [sec_type ].append ((binascii .a2b_base64 (key ), binascii .a2b_base64 (value )))
60
+
61
+ if limit_peers :
62
+ # If we need to limit loaded keys, ensure the same addresses of each type are loaded
63
+ keep_keys = None
64
+ for sec_type in SEC_TYPES_PEER :
65
+ if sec_type not in _secrets :
66
+ continue
67
+ secrets = _secrets [sec_type ]
68
+ if len (secrets ) > limit_peers :
69
+ if not keep_keys :
70
+ keep_keys = [key for key , _ in secrets [- limit_peers :]]
71
+ log_info ("Limiting keys to" , keep_keys )
72
+
73
+ keep_entries = [entry for entry in secrets if entry [0 ] in keep_keys ]
74
+ while len (keep_entries ) < limit_peers :
75
+ for entry in reversed (secrets ):
76
+ if entry not in keep_entries :
77
+ keep_entries .append (entry )
78
+ _secrets [sec_type ] = keep_entries
79
+ _log_peers ("loaded" )
80
+
51
81
except :
52
82
log_warn ("No secrets available" )
53
83
@@ -62,17 +92,33 @@ def _save_secrets(arg=None):
62
92
# Only save if the secrets changed.
63
93
return
64
94
95
+ _log_peers ('save_secrets' )
96
+
65
97
with open (_path , "w" ) as f :
66
98
# Convert bytes to hex strings (otherwise JSON will treat them like
67
99
# strings).
68
100
json_secrets = [
69
101
(sec_type , binascii .b2a_base64 (key ), binascii .b2a_base64 (value ))
70
- for ( sec_type , key ) , value in _secrets
102
+ for sec_type in _secrets for key , value in _secrets [ sec_type ]
71
103
]
72
104
json .dump (json_secrets , f )
73
105
_modified = False
74
106
75
107
108
+ def _log_peers (heading = "" ):
109
+ if core .log_level <= 2 :
110
+ return
111
+ log_info ("secrets:" , heading )
112
+ for sec_type in SEC_TYPES_PEER :
113
+ log_info ("-" , sec_type )
114
+
115
+ if sec_type not in _secrets :
116
+ continue
117
+ secrets = _secrets [sec_type ]
118
+ for key , value in secrets :
119
+ log_info (" - %s: %s..." % (key , value [0 :16 ]))
120
+
121
+
76
122
def _security_irq (event , data ):
77
123
global _modified
78
124
@@ -91,26 +137,42 @@ def _security_irq(event, data):
91
137
92
138
elif event == _IRQ_SET_SECRET :
93
139
sec_type , key , value = data
94
- key = sec_type , bytes (key )
140
+ key = bytes (key )
95
141
value = bytes (value ) if value else None
96
142
97
- log_info ("set secret:" , key , value )
98
-
99
143
if value is None :
100
- # Delete secret.
101
- for to_delete in [
102
- entry for entry in _secrets if entry [0 ] == key
103
- ]:
104
- _secrets .remove (to_delete )
105
- break
106
-
107
- else :
108
- # No entries to delete
144
+ log_info ("del secret:" , key )
145
+ else :
146
+ log_info ("set secret:" , sec_type , key , value )
147
+
148
+ if sec_type not in _secrets :
149
+ _secrets [sec_type ] = []
150
+ secrets = _secrets [sec_type ]
151
+
152
+ # Delete existing secrets matching the type and key.
153
+ for to_delete in [
154
+ entry for entry in secrets if entry [0 ] == key
155
+ ]:
156
+ log_info ("Removing existing secret matching key" )
157
+ secrets .remove (to_delete )
158
+ break
159
+
160
+ else :
161
+ if value is None :
162
+ # Explicit delete command: No entries to delete
109
163
return False
110
164
111
- else :
112
- # Save secret.
113
- _secrets .append ((key , value ))
165
+ if value is not None :
166
+ # Save new secret.
167
+ if limit_peers and sec_type in SEC_TYPES_PEER and len (secrets ) >= limit_peers :
168
+ addr , _ = secrets [0 ]
169
+ log_warn ("Removing old peer to make space for new one" )
170
+ ble .gap_unpair (addr )
171
+ log_info ("Removed:" , addr )
172
+ # Add new value to database
173
+ secrets .append ((key , value ))
174
+
175
+ _log_peers ("set_secret" )
114
176
115
177
# Queue up a save (don't synchronously write to flash).
116
178
if not _modified :
@@ -124,20 +186,20 @@ def _security_irq(event, data):
124
186
125
187
log_info ("get secret:" , sec_type , index , bytes (key ) if key else None )
126
188
189
+ secrets = _secrets .get (sec_type , [])
127
190
if key is None :
128
191
# Return the index'th secret of this type.
129
- i = 0
130
- for (t , _key ), value in _secrets :
131
- if t == sec_type :
132
- if i == index :
133
- return value
134
- i += 1
192
+ # This is used when loading "all" secrets at startup
193
+ if len (secrets ) > index :
194
+ key , val = secrets [index ]
195
+ return val
196
+
135
197
return None
136
198
else :
137
199
# Return the secret for this key (or None).
138
- key = sec_type , bytes (key )
200
+ key = bytes (key )
139
201
140
- for k , v in _secrets :
202
+ for k , v in secrets :
141
203
if k == key :
142
204
return v
143
205
return None
0 commit comments