Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
SMPKeyBin.java
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25package org.direct_bt;
26
27import java.io.File;
28import java.io.FileInputStream;
29import java.io.FileOutputStream;
30import java.io.IOException;
31import java.io.InputStream;
32import java.io.OutputStream;
33import java.nio.ByteOrder;
34import java.text.SimpleDateFormat;
35import java.util.Date;
36
37import org.direct_bt.SMPKeyMask.KeyType;
38import org.jau.io.PrintUtil;
39import org.jau.net.EUI48;
40import org.jau.sys.Clock;
41
42/**
43 * Storage for SMP keys including required connection parameter per local adapter and remote device.
44 *
45 * File format version 5.
46 *
47 * Storage for a device's {@link BDAddressAndType}, its security connection setup {@link BTSecurityLevel} + {@link SMPIOCapability}
48 * and optionally the initiator and responder {@link SMPLongTermKey LTK}, {@link SMPSignatureResolvingKey CSRK}
49 * and {@link SMPLinkKey LK} within one file.
50 * <p>
51 * Since the {@link SMPLongTermKey LTK}, {@link SMPSignatureResolvingKey CSRK}
52 * and {@link SMPLinkKey LK}
53 * are optionally set depending on their availability per initiator and responder,
54 * implementation supports mixed mode for certain devices.
55 * E.g. LTK responder key only etc.
56 * </p>
57 * <p>
58 * Data is stored in {@link ByteOrder#LITTLE_ENDIAN} format, native to Bluetooth.
59 * </p>
60 * <p>
61 * Filename as retrieved by {@link #getFileBasename(BDAddressAndType)} and {@link #getFileBasename()}
62 * has the following form {@code bd_010203040506_C026DA01DAB11.key}:
63 * <ul>
64 * <li>{@code 'bd_'} prefix</li>
65 * <li>{@code '010203040506'} local {@link EUI48} local adapter address</li>
66 * <li>{@code '_'} separator</li>
67 * <li>{@code 'C026DA01DAB1'} remote {@link EUI48} remote device address</li>
68 * <li>{@code '1'} {@link BDAddressType}</li>
69 * <li>{@code '.key'} suffix</li>
70 * </li>
71 * </p>
72 * @see BTDevice#setSMPKeyBin(SMPKeyBin)
73 * @see BTDevice#uploadKeys()
74 */
75public class SMPKeyBin {
76 public static final short VERSION = (short)0b0101010101010101 + (short)6; // bitpattern + version
77
78 private short version; // 2
79 private short size; // 2
80 private long ts_creation_sec; // 8
81 private BTRole localRole; // 1
82 private final BDAddressAndType localAddress; // 7
83 private final BDAddressAndType remoteAddress; // 7
84 private BTSecurityLevel sec_level;; // 1
85 private SMPIOCapability io_cap; // 1
86
87 private final SMPKeyMask keys_init; // 1
88 private final SMPKeyMask keys_resp; // 1 -> 31
89
90 private SMPLongTermKey ltk_init; // 28 (optional)
91 private SMPIdentityResolvingKey irk_init; // 23 (optional)
92 private SMPSignatureResolvingKey csrk_init; // 17 (optional)
93 private SMPLinkKey lk_init; // 18 (optional)
94
95 private SMPLongTermKey ltk_resp; // 28 (optional)
96 private SMPIdentityResolvingKey irk_resp; // 23 (optional)
97 private SMPSignatureResolvingKey csrk_resp; // 17 (optional)
98 private SMPLinkKey lk_resp; // 18 (optional)
99
100 private static final int byte_size_max = 205;
101 private static final int byte_size_min = 31;
102
103 boolean verbose = true;
104
105 final private short calcSize() {
106 short s = 0;
107 s += 2; // sizeof(version);
108 s += 2; // sizeof(size);
109 s += 8; // sizeof(ts_creation_sec);
110 s += 1; // sizeof(localRole);
111 s += 6; // sizeof(localAddress.address);
112 s += 1; // sizeof(localAddress.type);
113 s += 6; // sizeof(remoteAddress.address);
114 s += 1; // sizeof(remoteAddress.type);
115 s += 1; // sizeof(sec_level);
116 s += 1; // sizeof(io_cap);
117
118 s += 1; // sizeof(keys_init);
119 s += 1; // sizeof(keys_resp); // -> 31
120
121 if( hasLTKInit() ) {
122 s += SMPLongTermKey.byte_size; // sizeof(ltk_init);
123 }
124 if( hasIRKInit() ) {
125 s += SMPIdentityResolvingKey.byte_size; // sizeof(irk_init);
126 }
127 if( hasCSRKInit() ) {
128 s += SMPSignatureResolvingKey.byte_size; // sizeof(csrk_init);
129 }
130 if( hasLKInit() ) {
131 s += SMPLinkKey.byte_size; // sizeof(lk_init);
132 }
133
134 if( hasLTKResp() ) {
135 s += SMPLongTermKey.byte_size; // sizeof(ltk_resp);
136 }
137 if( hasIRKResp() ) {
138 s += SMPIdentityResolvingKey.byte_size; // sizeof(irk_resp);
139 }
140 if( hasCSRKResp() ) {
141 s += SMPSignatureResolvingKey.byte_size; // sizeof(csrk_resp);
142 }
143 if( hasLKResp() ) {
144 s += SMPLinkKey.byte_size; // sizeof(lk_resp);
145 }
146 return s;
147 }
148
149 /**
150 * Create a new SMPKeyBin instance based upon given BTDevice's
151 * BTSecurityLevel, SMPPairingState, PairingMode and LTK keys.
152 *
153 * Returned SMPKeyBin shall be tested if valid via {@link SMPKeyBin#isValid()},
154 * whether the retrieved data from BTDevice is consistent and hence
155 * having BTDevice is a well connected state.
156 *
157 * @param device the BTDevice from which all required data is derived
158 * @return a valid SMPKeyBin instance if properly connected, otherwise an invalid instance.
159 * @see BTDevice
160 * @see #isValid()
161 */
162 static public SMPKeyBin create(final BTDevice device) {
163 final BTSecurityLevel sec_lvl = device.getConnSecurityLevel();
164 final SMPPairingState pstate = device.getPairingState();
165 final PairingMode pmode = device.getPairingMode(); // Skip PairingMode::PRE_PAIRED (write again)
166
167 final SMPKeyBin smpKeyBin = new SMPKeyBin(device.getAdapter().getRole(), device.getAdapter().getAddressAndType(), device.getAddressAndType(),
168 device.getConnSecurityLevel(), device.getConnIOCapability());
169
170 if( ( BTSecurityLevel.NONE.value < sec_lvl.value && SMPPairingState.COMPLETED == pstate && PairingMode.NEGOTIATING.value < pmode.value ) ||
171 ( BTSecurityLevel.NONE.value == sec_lvl.value && SMPPairingState.NONE == pstate && PairingMode.NONE == pmode ) )
172 {
173 final SMPKeyMask keys_resp = device.getAvailableSMPKeys(true /* responder */);
174 final SMPKeyMask keys_init = device.getAvailableSMPKeys(false /* responder */);
175
176 if( keys_init.isSet(SMPKeyMask.KeyType.ENC_KEY) ) {
177 smpKeyBin.setLTKInit( device.getLongTermKey(false /* responder */) );
178 }
179 if( keys_resp.isSet(SMPKeyMask.KeyType.ENC_KEY) ) {
180 smpKeyBin.setLTKResp( device.getLongTermKey(true /* responder */) );
181 }
182
183 if( keys_init.isSet(SMPKeyMask.KeyType.ID_KEY) ) {
184 smpKeyBin.setIRKInit( device.getIdentityResolvingKey(false /* responder */) );
185 }
186 if( keys_resp.isSet(SMPKeyMask.KeyType.ID_KEY) ) {
187 smpKeyBin.setIRKResp( device.getIdentityResolvingKey(true /* responder */) );
188 }
189
190 if( keys_init.isSet(SMPKeyMask.KeyType.SIGN_KEY) ) {
191 smpKeyBin.setCSRKInit( device.getSignatureResolvingKey(false /* responder */) );
192 }
193 if( keys_resp.isSet(SMPKeyMask.KeyType.SIGN_KEY) ) {
194 smpKeyBin.setCSRKResp( device.getSignatureResolvingKey(true /* responder */) );
195 }
196
197 if( keys_init.isSet(SMPKeyMask.KeyType.LINK_KEY) ) {
198 smpKeyBin.setLKInit( device.getLinkKey(false /* responder */) );
199 }
200 if( keys_resp.isSet(SMPKeyMask.KeyType.LINK_KEY) ) {
201 smpKeyBin.setLKResp( device.getLinkKey(true /* responder */) );
202 }
203 } else {
204 smpKeyBin.size = 0; // explicitly mark invalid
205 }
206 return smpKeyBin;
207 }
208
209 /**
210 * Create a new SMPKeyBin instance on the fly based upon given BTDevice's
211 * BTSecurityLevel, SMPPairingState, PairingMode and LTK keys.
212 * If valid, instance is stored to a file denoted by `path` and {@link BTDevice#getAddressAndType()}.
213 *
214 * If {@link BTDevice#getPairingMode()} is {@link PairingMode#PRE_PAIRED}, an existing file will not be overwritten.
215 * Otherwise, a new key is assumed and an existing file shall be overwritten.
216 *
217 * Method returns `false` if resulting SMPKeyBin is not {@link SMPKeyBin#isValid()}.
218 * Otherwise, method returns the {@link SMPKeyBin#write(String)} result.
219 *
220 * @param device the BTDevice from which all required data is derived
221 * @param path the path for the stored SMPKeyBin file.
222 * @param verbose_ set to true to have detailed write processing logged to stderr, otherwise false
223 * @return `true` if file has been successfully written, otherwise `false`.
224 * @see BTDevice
225 * @see #create(BTDevice)
226 * @see #write(String)
227 * @see #isValid()
228 */
229 static public boolean createAndWrite(final BTDevice device, final String path, final boolean verbose_) {
230 final SMPKeyBin smpKeyBin = SMPKeyBin.create(device);
231 if( smpKeyBin.isValid() ) {
232 smpKeyBin.setVerbose( verbose_ );
233 final boolean overwrite = PairingMode.PRE_PAIRED != device.getPairingMode();
234 return smpKeyBin.write( path, overwrite );
235 } else {
236 if( verbose_ ) {
237 PrintUtil.println(System.err, "Create SMPKeyBin: Invalid "+smpKeyBin+", "+device);
238 }
239 return false;
240 }
241 }
242
243 /**
244 * Create a new SMPKeyBin instance based upon stored file denoted by `fname`.
245 *
246 * Returned SMPKeyBin shall be tested if valid via {@link SMPKeyBin#isValid()},
247 * whether the read() operation was successful and data is consistent.
248 *
249 * If file is invalid, it is removed.
250 *
251 * @param fname full path of the stored SMPKeyBin file.
252 * @param removeInvalidFile if `true` and file is invalid, remove it. Otherwise keep it alive.
253 * @param verbose_ set to true to have detailed read processing logged to stderr, otherwise false
254 * @return valid SMPKeyBin instance if file exist and read successfully, otherwise invalid SMPKeyBin instance.
255 * @see #isValid()
256 * @see #read(String, String)
257 */
258 static public SMPKeyBin read(final String fname, final boolean verbose_) {
259 final SMPKeyBin smpKeyBin = new SMPKeyBin();
260 smpKeyBin.setVerbose( verbose_ );
261 smpKeyBin.read( fname ); // read failure -> !isValid()
262 return smpKeyBin;
263 }
264
265 /**
266 * Create a new SMPKeyBin instance based upon the given BTDevice's matching filename,
267 * see SMPKeyBin API doc for filename naming scheme.
268 *
269 * Returned SMPKeyBin shall be tested if valid via SMPKeyBin::isValid(),
270 * whether the read() operation was successful and data is consistent.
271 *
272 * If file is invalid, it is removed.
273 *
274 * @param path directory for the stored SMPKeyBin file.
275 * @param device BTDevice used to derive the filename, see getFilename()
276 * @param verbose_ set to true to have detailed read processing logged to stderr, otherwise false
277 * @return valid SMPKeyBin instance if file exist and read successfully, otherwise invalid SMPKeyBin instance.
278 * @see getFilename()
279 * @see isValid()
280 * @see read()
281 */
282 static public SMPKeyBin read(final String path, final BTDevice device, final boolean verbose_) {
283 return read(getFilename(path, device), verbose_);
284 }
285
286 public SMPKeyBin(final BTRole localRole_, final BDAddressAndType localAddress_,
287 final BDAddressAndType remoteAddress_,
288 final BTSecurityLevel sec_level_, final SMPIOCapability io_cap_)
289 {
290 version = VERSION;
291 this.size = 0;
292 this.ts_creation_sec = Clock.wallClockSeconds();
293 this.localRole = localRole_;
294 this.localAddress = localAddress_;
295 this.remoteAddress = remoteAddress_;
296 this.sec_level = sec_level_;
297 this.io_cap = io_cap_;
298
299 this.keys_init = new SMPKeyMask();
300 this.keys_resp = new SMPKeyMask();
301
302 this.ltk_init = new SMPLongTermKey();
303 this.irk_init = new SMPIdentityResolvingKey();
304 this.csrk_init = new SMPSignatureResolvingKey();
305 this.lk_init = new SMPLinkKey();
306
307 this.ltk_resp = new SMPLongTermKey();
308 this.irk_resp = new SMPIdentityResolvingKey();
309 this.csrk_resp = new SMPSignatureResolvingKey();
310 this.lk_resp = new SMPLinkKey();
311
312 this.size = calcSize();
313 }
314
315 public SMPKeyBin() {
316 version = VERSION;
317 size = 0;
318 ts_creation_sec = 0;
319 localRole = BTRole.None;
320 localAddress = new BDAddressAndType();
321 remoteAddress = new BDAddressAndType();
322 sec_level = BTSecurityLevel.UNSET;
323 io_cap = SMPIOCapability.UNSET;
324
325 keys_init = new SMPKeyMask();
326 keys_resp = new SMPKeyMask();
327
328 ltk_init = new SMPLongTermKey();
329 irk_init = new SMPIdentityResolvingKey();
330 csrk_init = new SMPSignatureResolvingKey();
331 lk_init = new SMPLinkKey();
332
333 ltk_resp = new SMPLongTermKey();
334 irk_resp = new SMPIdentityResolvingKey();
335 csrk_resp = new SMPSignatureResolvingKey();
336 lk_resp = new SMPLinkKey();
337
338 size = calcSize();
339 }
340
341 final public boolean isVersionValid() { return VERSION==version; }
342 final public short getVersion() { return version;}
343
344 final public boolean isSizeValid() { return calcSize() == size;}
345 final public short getSize() { return size;}
346
347 /** Returns the creation timestamp in seconds since Unix epoch */
348 final public long getCreationTime() { return ts_creation_sec; }
349
350 /** Return the local adapter {@link BTRole}. */
351 final public BTRole getLocalRole() { return localRole; }
352
353 /** Return the local adapter address. */
354 final public BDAddressAndType getLocalAddrAndType() { return localAddress; }
355
356 /** Return the remote device address. */
357 final public BDAddressAndType getRemoteAddrAndType() { return remoteAddress; }
358
359 /** Return whether Secure Connection (SC) is being used via LTK keys. */
360 final public boolean uses_SC() {
361 return ( hasLTKInit() && getLTKInit().properties.isSet( SMPLongTermKey.PropertyType.SC ) ) ||
363 }
364
365 final public BTSecurityLevel getSecLevel() { return sec_level; }
366 final public SMPIOCapability getIOCap() { return io_cap; }
367
368 final public boolean hasLTKInit() { return keys_init.isSet(KeyType.ENC_KEY); }
369 final public boolean hasIRKInit() { return keys_init.isSet(KeyType.ID_KEY); }
370 final public boolean hasCSRKInit() { return keys_init.isSet(KeyType.SIGN_KEY); }
371 final public boolean hasLKInit() { return keys_init.isSet(KeyType.LINK_KEY); }
372 final public SMPLongTermKey getLTKInit() { return ltk_init; }
373 final public SMPIdentityResolvingKey getIRKInit() { return irk_init; }
374 final public SMPSignatureResolvingKey getCSRKInit() { return csrk_init; }
375 final public SMPLinkKey getLKInit() { return lk_init; }
376 final public void setLTKInit(final SMPLongTermKey v) {
377 ltk_init = v;
378 keys_init.set(KeyType.ENC_KEY);
379 size = calcSize();
380 }
381 final public void setIRKInit(final SMPIdentityResolvingKey v) {
382 irk_init = v;
383 keys_init.set(KeyType.ID_KEY);
384 size = calcSize();
385 }
386 final public void setCSRKInit(final SMPSignatureResolvingKey v) {
387 csrk_init = v;
388 keys_init.set(KeyType.SIGN_KEY);
389 size = calcSize();
390 }
391 final public void setLKInit(final SMPLinkKey v) {
392 lk_init = v;
393 keys_init.set(KeyType.LINK_KEY);
394 size = calcSize();
395 }
396
397 final public boolean hasLTKResp() { return keys_resp.isSet(KeyType.ENC_KEY); }
398 final public boolean hasIRKResp() { return keys_resp.isSet(KeyType.ID_KEY); }
399 final public boolean hasCSRKResp() { return keys_resp.isSet(KeyType.SIGN_KEY); }
400 final public boolean hasLKResp() { return keys_resp.isSet(KeyType.LINK_KEY); }
401 final public SMPLongTermKey getLTKResp() { return ltk_resp; }
402 final public SMPIdentityResolvingKey getIRKResp() { return irk_resp; }
403 final public SMPSignatureResolvingKey getCSRKResp() { return csrk_resp; }
404 final public SMPLinkKey getLKResp() { return lk_resp; }
405 final public void setLTKResp(final SMPLongTermKey v) {
406 ltk_resp = v;
407 keys_resp.set(KeyType.ENC_KEY);
408 size = calcSize();
409 }
410 final public void setIRKResp(final SMPIdentityResolvingKey v) {
411 irk_resp = v;
412 keys_resp.set(KeyType.ID_KEY);
413 size = calcSize();
414 }
415 final public void setCSRKResp(final SMPSignatureResolvingKey v) {
416 csrk_resp = v;
417 keys_resp.set(KeyType.SIGN_KEY);
418 size = calcSize();
419 }
420 final public void setLKResp(final SMPLinkKey v) {
421 lk_resp = v;
422 keys_resp.set(KeyType.LINK_KEY);
423 size = calcSize();
424 }
425
426 final public void setVerbose(final boolean v) { verbose = v; }
427
428 final public boolean isValid() {
429 // local_is_responder == true: responder's IRK info (LL slave), else the initiator's (LL master)
430 final boolean local_is_responder = BTRole.Slave.equals(localRole);
431 final BDAddressAndType responderAddress = local_is_responder ? localAddress : remoteAddress;
432 final BDAddressAndType initiatorAddress = local_is_responder ? remoteAddress : localAddress;
433
434 return isVersionValid() && isSizeValid() &&
435 BTSecurityLevel.UNSET != sec_level &&
436 SMPIOCapability.UNSET != io_cap &&
437 ( !hasLTKInit() || ltk_init.isValid() ) &&
438 ( !hasLTKResp() || ltk_resp.isValid() ) &&
439 ( !hasLKInit() || lk_init.isValid() ) &&
440 ( !hasLKResp() || lk_resp.isValid() ) &&
441 ( !hasIRKInit() || irk_init.id_address.equals(initiatorAddress.address) ) &&
442 ( !hasIRKResp() || irk_resp.id_address.equals(responderAddress.address) );
443 }
444
445 /**
446 * Returns the base filename, see {@link SMPKeyBin} API doc for naming scheme.
447 */
448 final public String getFileBasename() {
449 final String r = "bd_"+localAddress.address.toString()+"_"+remoteAddress.address.toString()+remoteAddress.type.value+".key";
450 return r.replace(":", "");
451 }
452 /**
453 * Returns the base filename, see {@link SMPKeyBin} API doc for naming scheme.
454 */
455 final public static String getFileBasename(final BDAddressAndType localAddress_, final BDAddressAndType remoteAddress_) {
456 final String r = "bd_"+localAddress_.address.toString()+"_"+remoteAddress_.address.toString()+remoteAddress_.type.value+".key";
457 return r.replace(":", "");
458 }
459 final public static String getFilename(final String path, final BDAddressAndType localAddress_, final BDAddressAndType remoteAddress_) {
460 return path + "/" + getFileBasename(localAddress_, remoteAddress_);
461 }
462 final public static String getFilename(final String path, final BTDevice remoteDevice) {
463 return getFilename(path, remoteDevice.getAdapter().getAddressAndType(), remoteDevice.getAddressAndType());
464 }
465 final public String getFilename(final String path) {
466 return getFilename(path, localAddress, remoteAddress);
467 }
468
469 @Override
470 final public String toString() {
471 final StringBuilder res = new StringBuilder();
472 res.append("SMPKeyBin[local[").append(localRole.toString()).append(", ").append(localAddress.toString()).append("], remote ").append(remoteAddress.toString()).append(", SC ").append(uses_SC()).append(", sec ").append(sec_level).append(", io ").append(io_cap).append(", ");
473 if( isVersionValid() ) {
474 boolean comma = false;
475 res.append("Init[");
476 if( hasLTKInit() && null != ltk_init ) {
477 res.append(ltk_init.toString());
478 comma = true;
479 }
480 if( hasIRKInit() && null != irk_init ) {
481 if( comma ) {
482 res.append(", ");
483 }
484 res.append(irk_init.toString());
485 comma = true;
486 }
487 if( hasCSRKInit() && null != csrk_init ) {
488 if( comma ) {
489 res.append(", ");
490 }
491 res.append(csrk_init.toString());
492 comma = true;
493 }
494 if( hasLKInit() && null != lk_init ) {
495 if( comma ) {
496 res.append(", ");
497 }
498 res.append(lk_init.toString());
499 comma = true;
500 }
501 comma = false;
502 res.append("], Resp[");
503 if( hasLTKResp() && null != ltk_resp ) {
504 res.append(ltk_resp.toString());
505 comma = true;
506 }
507 if( hasIRKResp() && null != irk_resp ) {
508 if( comma ) {
509 res.append(", ");
510 }
511 res.append(irk_resp.toString());
512 comma = true;
513 }
514 if( hasCSRKResp() && null != csrk_resp ) {
515 if( comma ) {
516 res.append(", ");
517 }
518 res.append(csrk_resp.toString());
519 comma = true;
520 }
521 if( hasLKResp() && null != lk_resp ) {
522 if( comma ) {
523 res.append(", ");
524 }
525 res.append(lk_resp.toString());
526 comma = true;
527 }
528 res.append("], ");
529 }
530 res.append("ver[0x").append(Integer.toHexString(version)).append(", ok ").append(isVersionValid()).append("], size[").append(size);
531 if( verbose ) {
532 res.append(", calc ").append(calcSize());
533 }
534 res.append(", valid ").append(isSizeValid()).append("], ");
535 {
536 final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
537 final Date d = new Date(ts_creation_sec*1000); // s -> ms
538 res.append(sdf.format(d));
539 }
540 res.append(", valid ").append(isValid()).append("]");
541
542 return res.toString();
543 }
544
545 final private static boolean remove_impl(final String fname) {
546 final File file = new File( fname );
547 try {
548 return file.delete(); // alternative to truncate, if existing
549 } catch (final Exception ex) {
550 PrintUtil.println(System.err, "Remove SMPKeyBin: Failed "+fname+": "+ex.getMessage());
551 ex.printStackTrace();
552 return false;
553 }
554 }
555 final public static boolean remove(final String path, final BTDevice remoteDevice_) {
556 return remove(path, remoteDevice_.getAdapter().getAddressAndType(), remoteDevice_.getAddressAndType());
557 }
558 final public static boolean remove(final String path, final BDAddressAndType localAddress_, final BDAddressAndType remoteAddress_) {
559 return remove_impl( getFilename(path, localAddress_, remoteAddress_) );
560 }
561 final public boolean remove(final String path) {
562 return remove_impl( getFilename(path) );
563 }
564
565 final public boolean write(final String path, final boolean overwrite) {
566 if( !isValid() ) {
567 PrintUtil.println(System.err, "Write SMPKeyBin: Invalid (skipped) "+toString());
568 return false;
569 }
570 final String fname = getFilename(path);
571 final File file = new File( fname );
572 OutputStream out = null;
573 try {
574 if( file.exists() ) {
575 if( overwrite ) {
576 if( !file.delete() ) {
577 PrintUtil.println(System.err, "Write SMPKeyBin: Failed deletion of existing file "+fname+": "+toString());
578 return false;
579 }
580 } else {
581 PrintUtil.println(System.err, "Write SMPKeyBin: Not overwriting existing file "+fname+": "+toString());
582 return false;
583 }
584 }
585 final byte[] buffer = new byte[byte_size_max];
586
587 out = new FileOutputStream(file);
588 out.write( (byte) version );
589 out.write( (byte)( version >> 8 ) );
590 out.write( (byte) size );
591 out.write( (byte)( size >> 8 ) );
592
593 writeLong(ts_creation_sec, out, buffer);
594 out.write(localRole.value);
595 {
596 localAddress.address.put(buffer, 0, ByteOrder.LITTLE_ENDIAN);
597 out.write(buffer, 0, localAddress.address.b.length);
598 }
599 out.write(localAddress.type.value);
600 {
601 remoteAddress.address.put(buffer, 0, ByteOrder.LITTLE_ENDIAN);
602 out.write(buffer, 0, remoteAddress.address.b.length);
603 }
604 out.write(remoteAddress.type.value);
605 out.write(sec_level.value);
606 out.write(io_cap.value);
607
608 out.write(keys_init.mask);
609 out.write(keys_resp.mask);
610
611 if( hasLTKInit() ) {
612 ltk_init.put(buffer, 0);
613 out.write(buffer, 0, SMPLongTermKey.byte_size);
614 }
615 if( hasIRKInit() ) {
616 irk_init.put(buffer, 0);
617 out.write(buffer, 0, SMPIdentityResolvingKey.byte_size);
618 }
619 if( hasCSRKInit() ) {
620 csrk_init.put(buffer, 0);
621 out.write(buffer, 0, SMPSignatureResolvingKey.byte_size);
622 }
623 if( hasLKInit() ) {
624 lk_init.put(buffer, 0);
625 out.write(buffer, 0, SMPLinkKey.byte_size);
626 }
627
628 if( hasLTKResp() ) {
629 ltk_resp.put(buffer, 0);
630 out.write(buffer, 0, SMPLongTermKey.byte_size);
631 }
632 if( hasIRKResp() ) {
633 irk_resp.put(buffer, 0);
634 out.write(buffer, 0, SMPIdentityResolvingKey.byte_size);
635 }
636 if( hasCSRKResp() ) {
637 csrk_resp.put(buffer, 0);
638 out.write(buffer, 0, SMPSignatureResolvingKey.byte_size);
639 }
640 if( hasLKResp() ) {
641 lk_resp.put(buffer, 0);
642 out.write(buffer, 0, SMPLinkKey.byte_size);
643 }
644
645 if( verbose ) {
646 PrintUtil.println(System.err, "Write SMPKeyBin: "+fname+": "+toString());
647 }
648 return true;
649 } catch (final Exception ex) {
650 PrintUtil.println(System.err, "Write SMPKeyBin: Failed "+fname+": "+toString()+": "+ex.getMessage());
651 ex.printStackTrace();
652 } finally {
653 try {
654 if( null != out ) {
655 out.close();
656 }
657 } catch (final IOException e) {
658 e.printStackTrace();
659 }
660 }
661 return false;
662 }
663
664 final public boolean read(final String fname) {
665 final File file = new File(fname);
666 InputStream in = null;
667 int remaining = 0;
668 boolean err = false;
669 try {
670 if( !file.canRead() ) {
671 if( verbose ) {
672 PrintUtil.println(System.err, "Read SMPKeyBin: Failed "+fname+": Not existing or readable: "+toString());
673 }
674 size = 0; // explicitly mark invalid
675 return false;
676 }
677 final byte[] buffer = new byte[byte_size_max];
678 in = new FileInputStream(file);
679 read(in, buffer, byte_size_min, fname);
680
681 int i=0;
682 version = (short) ( ( buffer[i++] & 0xff ) | ( ( buffer[i++] & 0xff ) << 8 ) );
683 size = (short) ( ( buffer[i++] & 0xff ) | ( ( buffer[i++] & 0xff ) << 8 ) );
684
685 remaining = size - 2 /* sizeof(version) */ - 2 /* sizeof(size) */;
686
687 if( !err && 8 <= remaining ) {
688 ts_creation_sec = getLong(buffer, i); i+=8;
689 remaining -= 8;
690 } else {
691 err = true;
692 }
693 if( !err && 1+7+7+4 <= remaining ) {
694 localRole = BTRole.get(buffer[i++]);
695 localAddress.address = new EUI48(buffer, i, ByteOrder.LITTLE_ENDIAN); i+=6;
696 localAddress.type = BDAddressType.get(buffer[i++]);
697 remoteAddress.address = new EUI48(buffer, i, ByteOrder.LITTLE_ENDIAN); i+=6;
698 remoteAddress.type = BDAddressType.get(buffer[i++]);
699 sec_level = BTSecurityLevel.get(buffer[i++]);
700 io_cap = SMPIOCapability.get(buffer[i++]);
701 keys_init.mask = buffer[i++];
702 keys_resp.mask = buffer[i++];
703
704 remaining -= 1+7+7+4;
705 } else {
706 err = true;
707 }
708
709 if( !err && hasLTKInit() ) {
710 if( SMPLongTermKey.byte_size <= remaining ) {
711 read(in, buffer, SMPLongTermKey.byte_size, fname);
712 ltk_init.get(buffer, 0);
713 remaining -= SMPLongTermKey.byte_size;
714 } else {
715 err = true;
716 }
717 }
718 if( !err && hasIRKInit() ) {
719 if( SMPIdentityResolvingKey.byte_size <= remaining ) {
720 read(in, buffer, SMPIdentityResolvingKey.byte_size, fname);
721 irk_init.get(buffer, 0);
723 } else {
724 err = true;
725 }
726 }
727 if( !err && hasCSRKInit() ) {
728 if( SMPSignatureResolvingKey.byte_size <= remaining ) {
729 read(in, buffer, SMPSignatureResolvingKey.byte_size, fname);
730 csrk_init.get(buffer, 0);
732 } else {
733 err = true;
734 }
735 }
736 if( !err && hasLKInit() ) {
737 if( SMPLinkKey.byte_size <= remaining ) {
738 read(in, buffer, SMPLinkKey.byte_size, fname);
739 lk_init.get(buffer, 0);
740 remaining -= SMPLinkKey.byte_size;
741 } else {
742 err = true;
743 }
744 }
745
746 if( !err && hasLTKResp() ) {
747 if( SMPLongTermKey.byte_size <= remaining ) {
748 read(in, buffer, SMPLongTermKey.byte_size, fname);
749 ltk_resp.get(buffer, 0);
750 remaining -= SMPLongTermKey.byte_size;
751 } else {
752 err = true;
753 }
754 }
755 if( !err && hasIRKResp() ) {
756 if( SMPIdentityResolvingKey.byte_size <= remaining ) {
757 read(in, buffer, SMPIdentityResolvingKey.byte_size, fname);
758 irk_resp.get(buffer, 0);
760 } else {
761 err = true;
762 }
763 }
764 if( !err && hasCSRKResp() ) {
765 if( SMPSignatureResolvingKey.byte_size <= remaining ) {
766 read(in, buffer, SMPSignatureResolvingKey.byte_size, fname);
767 csrk_resp.get(buffer, 0);
769 } else {
770 err = true;
771 }
772 }
773 if( !err && hasLKResp() ) {
774 if( SMPLinkKey.byte_size <= remaining ) {
775 read(in, buffer, SMPLinkKey.byte_size, fname);
776 lk_resp.get(buffer, 0);
777 remaining -= SMPLinkKey.byte_size;
778 } else {
779 err = true;
780 }
781 }
782
783 if( !err ) {
784 err = !isValid();
785 }
786 } catch (final Exception ex) {
787 PrintUtil.println(System.err, "Read SMPKeyBin: Failed "+fname+": "+toString()+": "+ex.getMessage());
788 ex.printStackTrace();
789 err = true;
790 } finally {
791 try {
792 if( null != in ) {
793 in.close();
794 }
795 } catch (final IOException e) {
796 e.printStackTrace();
797 }
798 }
799 if( err ) {
800 file.delete();
801 if( verbose ) {
802 PrintUtil.println(System.err, "Read SMPKeyBin: Failed "+fname+" (removed): "+toString()+", remaining "+remaining);
803 }
804 size = 0; // explicitly mark invalid
805 } else {
806 if( verbose ) {
807 PrintUtil.println(System.err, "Read SMPKeyBin: OK "+fname+": "+toString()+", remaining "+remaining);
808 }
809 }
810 return err;
811 }
812 final static private int read(final InputStream in, final byte[] buffer, final int rsize, final String fname) throws IOException {
813 final int read_count = in.read(buffer, 0, rsize);
814 if( read_count != rsize ) {
815 throw new IOException("Couldn't read "+rsize+" bytes, only "+read_count+" from "+fname);
816 }
817 return read_count;
818 }
819 final static private long getLong(final byte[] buffer, final int offset) throws IOException {
820 long res = 0;
821 for (int j = 0; j < 8; ++j) {
822 res |= ((long) buffer[offset+j] & 0xff) << j*8;
823 }
824 return res;
825 }
826 final static private void writeLong(final long value, final OutputStream out, final byte[] buffer) throws IOException {
827 for (int i = 0; i < 8; ++i) {
828 buffer[i] = (byte) (value >> i*8);
829 }
830 out.write(buffer, 0, 8);
831 }
832
833 public static void main(final String[] args) {
835
836 boolean verbose=false;
837 for(int i=0; i< args.length; i++) {
838 final String arg = args[i];
839 if( arg.equals("-verbose") || arg.equals("-v") ) {
840 verbose = true;
841 } else {
842 if( verbose ) {
843 System.err.println("Read: '"+arg+"'");
844 }
845 final SMPKeyBin key = SMPKeyBin.read(arg, verbose);
846 System.err.println(key.toString());
847 }
848 }
849 }
850};
Unique Bluetooth EUI48 address and BDAddressType tuple.
One stop BTManager API entry point.
Definition: BTFactory.java:52
static synchronized void initDirectBTLibrary()
Preloads the DirectBT native library w/o instantiating BTManager.
Definition: BTFactory.java:482
SMP Identity Resolving Key, used for platform agnostic persistence.
EUI48 id_address
Identity Address for the IRK.
static final int byte_size
Size of the byte stream representation in bytes.
void get(final byte[] source, int pos)
Method transfers all bytes representing a SMPIdentityResolvingKeyInfo from the given source array at ...
final void put(final byte[] sink, int pos)
Method transfers all bytes representing this instance into the given destination array at the given p...
Storage for SMP keys including required connection parameter per local adapter and remote device.
Definition: SMPKeyBin.java:75
final boolean uses_SC()
Return whether Secure Connection (SC) is being used via LTK keys.
Definition: SMPKeyBin.java:360
final short getSize()
Definition: SMPKeyBin.java:345
final SMPIOCapability getIOCap()
Definition: SMPKeyBin.java:366
final void setLKResp(final SMPLinkKey v)
Definition: SMPKeyBin.java:420
static SMPKeyBin read(final String fname, final boolean verbose_)
Create a new SMPKeyBin instance based upon stored file denoted by fname.
Definition: SMPKeyBin.java:258
final boolean hasIRKInit()
Definition: SMPKeyBin.java:369
final boolean isVersionValid()
Definition: SMPKeyBin.java:341
final void setLTKResp(final SMPLongTermKey v)
Definition: SMPKeyBin.java:405
final boolean write(final String path, final boolean overwrite)
Definition: SMPKeyBin.java:565
final BDAddressAndType getRemoteAddrAndType()
Return the remote device address.
Definition: SMPKeyBin.java:357
final BDAddressAndType getLocalAddrAndType()
Return the local adapter address.
Definition: SMPKeyBin.java:354
final static String getFilename(final String path, final BTDevice remoteDevice)
Definition: SMPKeyBin.java:462
final short getVersion()
Definition: SMPKeyBin.java:342
final boolean hasLKResp()
Definition: SMPKeyBin.java:400
final SMPLongTermKey getLTKResp()
Definition: SMPKeyBin.java:401
final void setVerbose(final boolean v)
Definition: SMPKeyBin.java:426
final boolean hasCSRKResp()
Definition: SMPKeyBin.java:399
final void setCSRKResp(final SMPSignatureResolvingKey v)
Definition: SMPKeyBin.java:415
final SMPSignatureResolvingKey getCSRKResp()
Definition: SMPKeyBin.java:403
static final short VERSION
Definition: SMPKeyBin.java:76
final boolean hasLTKResp()
Definition: SMPKeyBin.java:397
final boolean hasLKInit()
Definition: SMPKeyBin.java:371
final void setIRKInit(final SMPIdentityResolvingKey v)
Definition: SMPKeyBin.java:381
final boolean hasLTKInit()
Definition: SMPKeyBin.java:368
SMPKeyBin(final BTRole localRole_, final BDAddressAndType localAddress_, final BDAddressAndType remoteAddress_, final BTSecurityLevel sec_level_, final SMPIOCapability io_cap_)
Definition: SMPKeyBin.java:286
final SMPSignatureResolvingKey getCSRKInit()
Definition: SMPKeyBin.java:374
final BTRole getLocalRole()
Return the local adapter BTRole.
Definition: SMPKeyBin.java:351
static SMPKeyBin create(final BTDevice device)
Create a new SMPKeyBin instance based upon given BTDevice's BTSecurityLevel, SMPPairingState,...
Definition: SMPKeyBin.java:162
final SMPLinkKey getLKInit()
Definition: SMPKeyBin.java:375
final SMPLinkKey getLKResp()
Definition: SMPKeyBin.java:404
final BTSecurityLevel getSecLevel()
Definition: SMPKeyBin.java:365
final void setCSRKInit(final SMPSignatureResolvingKey v)
Definition: SMPKeyBin.java:386
final String getFileBasename()
Returns the base filename, see SMPKeyBin API doc for naming scheme.
Definition: SMPKeyBin.java:448
static SMPKeyBin read(final String path, final BTDevice device, final boolean verbose_)
Create a new SMPKeyBin instance based upon the given BTDevice's matching filename,...
Definition: SMPKeyBin.java:282
final static String getFilename(final String path, final BDAddressAndType localAddress_, final BDAddressAndType remoteAddress_)
Definition: SMPKeyBin.java:459
final void setLTKInit(final SMPLongTermKey v)
Definition: SMPKeyBin.java:376
final void setIRKResp(final SMPIdentityResolvingKey v)
Definition: SMPKeyBin.java:410
static boolean createAndWrite(final BTDevice device, final String path, final boolean verbose_)
Create a new SMPKeyBin instance on the fly based upon given BTDevice's BTSecurityLevel,...
Definition: SMPKeyBin.java:229
final String getFilename(final String path)
Definition: SMPKeyBin.java:465
final SMPIdentityResolvingKey getIRKResp()
Definition: SMPKeyBin.java:402
final SMPLongTermKey getLTKInit()
Definition: SMPKeyBin.java:372
static void main(final String[] args)
Definition: SMPKeyBin.java:833
final boolean isSizeValid()
Definition: SMPKeyBin.java:344
final static String getFileBasename(final BDAddressAndType localAddress_, final BDAddressAndType remoteAddress_)
Returns the base filename, see SMPKeyBin API doc for naming scheme.
Definition: SMPKeyBin.java:455
final boolean hasCSRKInit()
Definition: SMPKeyBin.java:370
final boolean isValid()
Definition: SMPKeyBin.java:428
final String toString()
Definition: SMPKeyBin.java:470
final SMPIdentityResolvingKey getIRKInit()
Definition: SMPKeyBin.java:373
final boolean hasIRKResp()
Definition: SMPKeyBin.java:398
final long getCreationTime()
Returns the creation timestamp in seconds since Unix epoch.
Definition: SMPKeyBin.java:348
final boolean read(final String fname)
Definition: SMPKeyBin.java:664
final void setLKInit(final SMPLinkKey v)
Definition: SMPKeyBin.java:391
SMP Key Type for Distribution, indicates keys distributed in the Transport Specific Key Distribution ...
Definition: SMPKeyMask.java:44
void set(final KeyType bit)
byte mask
The KeyType bit mask.
boolean isSet(final KeyType bit)
Local SMP Link Key, used for platform agnostic persistence, mapping to platform specific link keys fo...
Definition: SMPLinkKey.java:42
void get(final byte[] source, int pos)
Method transfers all bytes representing a SMPLinkKeyInfo from the given source array at the given pos...
final boolean isValid()
static final int byte_size
Size of the byte stream representation in bytes.
final void put(final byte[] sink, int pos)
Method transfers all bytes representing this instance into the given destination array at the given p...
boolean isSet(final PropertyType bit)
SMP Long Term Key, used for platform agnostic persistence.
void get(final byte[] source, int pos)
Method transfers all bytes representing a SMPLongTermKeyInfo from the given source array at the given...
Properties properties
Properties bit mask.
static final int byte_size
Size of the byte stream representation in bytes (28)
final void put(final byte[] sink, int pos)
Method transfers all bytes representing this instance into the given destination array at the given p...
SMP Signature Resolving Key, used for platform agnostic persistence.
final void put(final byte[] sink, int pos)
Method transfers all bytes representing this instance into the given destination array at the given p...
static final int byte_size
Size of the byte stream representation in bytes.
void get(final byte[] source, int pos)
Method transfers all bytes representing a SMPSignatureResolvingKeyInfo from the given source array at...
Bluetooth address type constants.
static BDAddressType get(final String name)
Maps the specified name to a constant of BDAddressType.
Bluetooth roles from the perspective of the link layer (connection initiator).
Definition: BTRole.java:36
None
Undefined role.
Definition: BTRole.java:38
static BTRole get(final String name)
Maps the specified name to a constant of BTRole.
Definition: BTRole.java:57
final byte value
Definition: BTRole.java:44
Slave
Slave or peripheral role, advertising and waiting for connections to accept.
Definition: BTRole.java:42
Bluetooth Security Level.
UNSET
Security Level not set, value 0.
NONE
No encryption and no authentication.
static BTSecurityLevel get(final String name)
Maps the specified name to a constant of BTSecurityLevel.
Bluetooth secure pairing mode.
NEGOTIATING
Pairing mode in negotiating, i.e.
NONE
No pairing mode, implying no secure connections, no encryption and no MITM protection.
SMP IO Capability value.
UNSET
Denoting unset value, i.e.
static SMPIOCapability get(final String name)
Maps the specified name to a constant of SMPIOCapability.
LINK_KEY
SMP on the LE transport: Indicate that the device would like to derive the Link Key from the LTK.
Definition: SMPKeyMask.java:82
ID_KEY
Indicates that the device shall distribute IRK using the Identity Information command followed by its...
Definition: SMPKeyMask.java:68
SIGN_KEY
Indicates that the device shall distribute CSRK using the Signing Information command.
Definition: SMPKeyMask.java:72
ENC_KEY
LE legacy pairing: Indicates device shall distribute LTK using the Encryption Information command,...
Definition: SMPKeyMask.java:63
SMP Pairing Process state definition.
NONE
No pairing in process.
COMPLETED
Phase 3: Key & value distribution completed by responding (slave) device sending SMPIdentInfoMsg (#1)...
BDAddressAndType getAddressAndType()
Returns the adapter's public BDAddressAndType.
BTRole getRole()
Return the current BTRole of this adapter.
BTDevice represents one remote Bluetooth device.
Definition: BTDevice.java:47
BTAdapter getAdapter()
Returns the adapter on which this device was discovered or connected.
BTSecurityLevel getConnSecurityLevel()
Return the BTSecurityLevel, determined when the connection is established.
SMPLinkKey getLinkKey(final boolean responder)
Returns a copy of the Link Key (LK), valid after connection and SMP pairing has been completed.
SMPIdentityResolvingKey getIdentityResolvingKey(final boolean responder)
Returns a copy of the Identity Resolving Key (IRK), valid after connection and SMP pairing has been c...
SMPKeyMask getAvailableSMPKeys(final boolean responder)
Returns the available SMPKeyMask.KeyType SMPKeyMask for the responder (LL slave) or initiator (LL mas...
SMPPairingState getPairingState()
Returns the current SMPPairingState.
SMPIOCapability getConnIOCapability()
Return the SMPIOCapability value, determined when the connection is established.
SMPSignatureResolvingKey getSignatureResolvingKey(final boolean responder)
Returns a copy of the Signature Resolving Key (CSRK), valid after connection and SMP pairing has been...
PairingMode getPairingMode()
Returns the current PairingMode used by the device.
BDAddressAndType getAddressAndType()
Returns the devices' unique EUI48 address and BDAddressType type tuple, might be its initially report...
SMPLongTermKey getLongTermKey(final boolean responder)
Returns a copy of the long term key (LTK), valid after connection and SMP pairing has been completed.