55import asyncio
66import binascii
77import json
8-
8+ from . import core
99from .core import log_info , log_warn , ble , register_irq_handler
1010from .device import DeviceConnection
1111
2626
2727_DEFAULT_PATH = "ble_secrets.json"
2828
29+ # Maintain list of known keys, newest at the bottom / end.
2930_secrets = {}
3031_modified = False
3132_path = None
3233
34+ # If set, limit the pairing db to this many peers
35+ limit_peers = None
36+
37+ SEC_TYPES_SELF = (10 , )
38+ SEC_TYPES_PEER = (1 , 2 , 3 , 4 )
39+
3340
3441# Must call this before stack startup.
3542def load_secrets (path = None ):
36- global _path , _secrets
43+ global _path , _secrets , limit_peers
3744
3845 # Use path if specified, otherwise use previous path, otherwise use
3946 # default path.
4047 _path = path or _path or _DEFAULT_PATH
4148
4249 # Reset old secrets.
43- _secrets = {}
50+ _secrets . clear ()
4451 try :
4552 with open (_path , "r" ) as f :
4653 entries = json .load (f )
54+ # Newest entries at at the end, load them first
4755 for sec_type , key , value in entries :
56+ if sec_type not in _secrets :
57+ _secrets [sec_type ] = []
4858 # Decode bytes from hex.
49- _secrets [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_warn ("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+
5081 except :
5182 log_warn ("No secrets available" )
5283
@@ -61,17 +92,48 @@ def _save_secrets(arg=None):
6192 # Only save if the secrets changed.
6293 return
6394
95+ _log_peers ('save_secrets' )
96+
6497 with open (_path , "w" ) as f :
6598 # Convert bytes to hex strings (otherwise JSON will treat them like
6699 # strings).
67100 json_secrets = [
68101 (sec_type , binascii .b2a_base64 (key ), binascii .b2a_base64 (value ))
69- for ( sec_type , key ) , value in _secrets . items ()
102+ for sec_type in _secrets for key , value in _secrets [ sec_type ]
70103 ]
71104 json .dump (json_secrets , f )
72105 _modified = False
73106
74107
108+ def _remove_entry (sec_type , key ):
109+ secrets = _secrets [sec_type ]
110+
111+ # Delete existing secrets matching the type and key.
112+ deleted = False
113+ for to_delete in [
114+ entry for entry in secrets if entry [0 ] == key
115+ ]:
116+ log_info ("Removing existing secret matching key" )
117+ secrets .remove (to_delete )
118+ deleted = True
119+
120+ return deleted
121+
122+
123+ def _log_peers (heading = "" ):
124+ if core .log_level <= 2 :
125+ return
126+ log_info ("secrets:" , heading )
127+ for sec_type in SEC_TYPES_PEER :
128+ log_info ("-" , sec_type )
129+
130+ if sec_type not in _secrets :
131+ continue
132+ secrets = _secrets [sec_type ]
133+ for key , value in secrets :
134+ log_info (" - %s: %s..." % (key , value [0 :16 ]))
135+
136+
75137def _security_irq (event , data ):
76138 global _modified
77139
@@ -90,20 +152,43 @@ def _security_irq(event, data):
90152
91153 elif event == _IRQ_SET_SECRET :
92154 sec_type , key , value = data
93- key = sec_type , bytes (key )
155+ key = bytes (key )
94156 value = bytes (value ) if value else None
95157
96- log_info ("set secret:" , key , value )
97-
98- if value is None :
99- # Delete secret.
100- if key not in _secrets :
101- return False
102-
103- del _secrets [key ]
104- else :
105- # Save secret.
106- _secrets [key ] = value
158+ is_saving = value is not None
159+ is_deleting = not is_saving
160+
161+ if core .log_level > 2 :
162+ if is_deleting :
163+ log_info ("del secret:" , key )
164+ else :
165+ shortval = value
166+ if len (value ) > 16 :
167+ shortval = value [0 :16 ] + b"..."
168+ log_info ("set secret:" , sec_type , key , shortval )
169+
170+ if sec_type not in _secrets :
171+ _secrets [sec_type ] = []
172+ secrets = _secrets [sec_type ]
173+
174+ # Delete existing secrets matching the type and key.
175+ removed = _remove_entry (sec_type , key )
176+
177+ if is_deleting and not removed :
178+ # Delete mode, but no entries were deleted
179+ return False
180+
181+ if is_saving :
182+ # Save new secret.
183+ if limit_peers and sec_type in SEC_TYPES_PEER and len (secrets ) >= limit_peers :
184+ addr , _ = secrets [0 ]
185+ log_warn ("Removing old peer to make space for new one" )
186+ ble .gap_unpair (addr )
187+ log_info ("Removed:" , addr )
188+ # Add new value to database
189+ secrets .append ((key , value ))
190+
191+ _log_peers ("set_secret" )
107192
108193 # Queue up a save (don't synchronously write to flash).
109194 _modified = True
@@ -116,19 +201,23 @@ def _security_irq(event, data):
116201
117202 log_info ("get secret:" , sec_type , index , bytes (key ) if key else None )
118203
204+ secrets = _secrets .get (sec_type , [])
119205 if key is None :
120206 # Return the index'th secret of this type.
121- i = 0
122- for (t , _key ), value in _secrets .items ():
123- if t == sec_type :
124- if i == index :
125- return value
126- i += 1
207+ # This is used when loading "all" secrets at startup
208+ if len (secrets ) > index :
209+ key , val = secrets [index ]
210+ return val
211+
127212 return None
128213 else :
129214 # Return the secret for this key (or None).
130- key = sec_type , bytes (key )
131- return _secrets .get (key , None )
215+ key = bytes (key )
216+
217+ for k , v in secrets :
218+ if k == key :
219+ return v
220+ return None
132221
133222 elif event == _IRQ_PASSKEY_ACTION :
134223 conn_handle , action , passkey = data
0 commit comments