Direct-BT  2.3.1
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 
25 package org.direct_bt;
26 
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.text.SimpleDateFormat;
34 import java.util.Date;
35 
37 
38 /**
39  * Storage for SMP keys including the required connection parameter.
40  *
41  * Storage for a device's {@link BDAddressAndType}, its security connection setup {@link BTSecurityLevel} + {@link SMPIOCapability}
42  * and optionally the initiator and responder {@link SMPLongTermKeyInfo LTK} and {@link SMPSignatureResolvingKeyInfo CSRK} within one file.
43  * <p>
44  * Since the {@link SMPLongTermKeyInfo LTK} and {@link SMPSignatureResolvingKeyInfo CSRK}
45  * can be optionally set due to their availability per initiator and responder,
46  * implementation supports mixed mode for certain devices.
47  * E.g. LTK responder key only etc.
48  * </p>
49  * <p>
50  * Filename as retrieved by {@link #getFileBasename(BDAddressAndType)} and {@link #getFileBasename()}
51  * has the following form '{@code bd_C0_26_DA_01_DA_B1_1-smpkey.bin}':
52  * <ul>
53  * <li>{@code 'bd_'} denotes prefix</li>
54  * <li>{@code 'C0_26_DA_01_DA_B1'} denotes the {@link EUI48} address</li>
55  * <li>{@code '_1'} denotes the {@link BDAddressType}</li>
56  * <li>{@code '-smpkey.bin'} denotes the suffix</li>
57  * </li>
58  * </p>
59  */
60 public class SMPKeyBin {
61  public static final short VERSION = (short)0b0101010101010101 + (short)2; // bitpattern + version
62 
63  private short version; // 2
64  private short size; // 2
65  private long ts_creation_sec; // 8
66  private final BDAddressAndType addrAndType; // 7
67  private BTSecurityLevel sec_level;; // 1
68  private SMPIOCapability io_cap; // 1
69 
70  private final SMPKeyMask keys_init; // 1
71  private final SMPKeyMask keys_resp; // 1
72 
73  private SMPLongTermKeyInfo ltk_init; // 28 (optional)
74  private SMPSignatureResolvingKeyInfo csrk_init; // 17 (optional)
75 
76  private SMPLongTermKeyInfo ltk_resp; // 28 (optional)
77  private SMPSignatureResolvingKeyInfo csrk_resp; // 17 (optional)
78 
79  private static final int byte_size_max = 113;
80  private static final int byte_size_min = 23;
81  // Min-Max: 23 - 113 bytes
82 
83  boolean verbose;
84 
85  final private short calcSize() {
86  short s = 0;
87  s += 2; // sizeof(version);
88  s += 2; // sizeof(size);
89  s += 8; // sizeof(ts_creation_sec);
90  s += 6; // sizeof(addrAndType.address);
91  s += 1; // sizeof(addrAndType.type);
92  s += 1; // sizeof(sec_level);
93  s += 1; // sizeof(io_cap);
94 
95  s += 1; // sizeof(keys_init);
96  s += 1; // sizeof(keys_resp);
97 
98  if( hasLTKInit() ) {
99  s += 28; // sizeof(ltk_init);
100  }
101  if( hasCSRKInit() ) {
102  s += 17; // sizeof(csrk_init);
103  }
104 
105  if( hasLTKResp() ) {
106  s += 28; // sizeof(ltk_resp);
107  }
108  if( hasCSRKResp() ) {
109  s += 17; // sizeof(csrk_resp);
110  }
111  return s;
112  }
113 
114  /**
115  * Create a new SMPKeyBin instance based upon given BTDevice's
116  * BTSecurityLevel, SMPPairingState, PairingMode and LTK keys.
117  *
118  * Returned SMPKeyBin shall be tested if valid via {@link SMPKeyBin#isValid()},
119  * whether the retrieved data from BTDevice is consistent and hence
120  * having BTDevice is a well connected state.
121  *
122  * @param device the BTDevice from which all required data is derived
123  * @return a valid SMPKeyBin instance if properly connected, otherwise an invalid instance.
124  * @see BTDevice
125  * @see #isValid()
126  */
127  static public SMPKeyBin create(final BTDevice device) {
128  final BTSecurityLevel sec_lvl = device.getConnSecurityLevel();
129  final SMPPairingState pstate = device.getPairingState();
130  final PairingMode pmode = device.getPairingMode(); // Skip PairingMode::PRE_PAIRED (write again)
131 
132  final SMPKeyBin smpKeyBin = new SMPKeyBin(device.getAddressAndType(),
133  device.getConnSecurityLevel(), device.getConnIOCapability());
134 
135  if( ( BTSecurityLevel.NONE.value < sec_lvl.value && SMPPairingState.COMPLETED == pstate && PairingMode.NEGOTIATING.value < pmode.value ) ||
136  ( BTSecurityLevel.NONE.value == sec_lvl.value && SMPPairingState.NONE == pstate && PairingMode.NONE == pmode ) )
137  {
138  final SMPKeyMask keys_resp = device.getAvailableSMPKeys(true /* responder */);
139  final SMPKeyMask keys_init = device.getAvailableSMPKeys(false /* responder */);
140 
141  if( keys_init.isSet(SMPKeyMask.KeyType.ENC_KEY) ) {
142  smpKeyBin.setLTKInit( device.getLongTermKeyInfo(false /* responder */) );
143  }
144  if( keys_resp.isSet(SMPKeyMask.KeyType.ENC_KEY) ) {
145  smpKeyBin.setLTKResp( device.getLongTermKeyInfo(true /* responder */) );
146  }
147 
148  if( keys_init.isSet(SMPKeyMask.KeyType.SIGN_KEY) ) {
149  smpKeyBin.setCSRKInit( device.getSignatureResolvingKeyInfo(false /* responder */) );
150  }
151  if( keys_resp.isSet(SMPKeyMask.KeyType.SIGN_KEY) ) {
152  smpKeyBin.setCSRKResp( device.getSignatureResolvingKeyInfo(true /* responder */) );
153  }
154  } else {
155  smpKeyBin.size = 0; // explicitly mark invalid
156  }
157  return smpKeyBin;
158  }
159 
160  /**
161  * Create a new SMPKeyBin instance on the fly based upon given BTDevice's
162  * BTSecurityLevel, SMPPairingState, PairingMode and LTK keys.
163  * If valid, instance is stored to a file denoted by `path` and {@link BTDevice#getAddressAndType()}.
164  *
165  * Method returns `false` if resulting SMPKeyBin is not {@link SMPKeyBin#isValid()}.
166  *
167  * Otherwise, method returns the {@link SMPKeyBin#write(String)} result.
168  *
169  * @param device the BTDevice from which all required data is derived
170  * @param path the path for the stored SMPKeyBin file.
171  * @param overwrite if `true` and file already exists, delete file first. If `false` and file exists, return `false` w/o writing.
172  * @param verbose_ set to true to have detailed write processing logged to stderr, otherwise false
173  * @return `true` if file has been successfully written, otherwise `false`.
174  * @see BTDevice
175  * @see #create(BTDevice)
176  * @see #write(String)
177  * @see #isValid()
178  */
179  static public boolean createAndWrite(final BTDevice device, final String path, final boolean overwrite, final boolean verbose_) {
180  final SMPKeyBin smpKeyBin = SMPKeyBin.create(device);
181  if( smpKeyBin.isValid() ) {
182  smpKeyBin.setVerbose( verbose_ );
183  return smpKeyBin.write( getFilename(path, device.getAddressAndType()), overwrite );
184  } else {
185  if( verbose_ ) {
186  BTUtils.println(System.err, "Create SMPKeyBin: Invalid "+smpKeyBin+", "+device);
187  }
188  return false;
189  }
190  }
191 
192  /**
193  * Create a new SMPKeyBin instance based upon stored file denoted by `fname`.
194  *
195  * Returned SMPKeyBin shall be tested if valid via {@link SMPKeyBin#isValid()},
196  * whether the read() operation was successful and data is consistent.
197  *
198  * If file is invalid, it is removed.
199  *
200  * @param fname full path of the stored SMPKeyBin file.
201  * @param removeInvalidFile if `true` and file is invalid, remove it. Otherwise keep it alive.
202  * @param verbose_ set to true to have detailed read processing logged to stderr, otherwise false
203  * @return valid SMPKeyBin instance if file exist and read successfully, otherwise invalid SMPKeyBin instance.
204  * @see #isValid()
205  * @see #read(String, String)
206  */
207  static public SMPKeyBin read(final String fname, final boolean verbose_) {
208  final SMPKeyBin smpKeyBin = new SMPKeyBin();
209  smpKeyBin.setVerbose( verbose_ );
210  smpKeyBin.read( fname ); // read failure -> !isValid()
211  return smpKeyBin;
212  }
213 
214  /**
215  * Create a new SMPKeyBin instance on the fly based upon stored file denoted by `path` and {@link BTDevice#getAddressAndType()},
216  * i.e. `path/` + {@link #getFileBasename(BDAddressAndType)}.
217  *
218  * Method returns HCIStatusCode#INVALID_PARAMS if resulting SMPKeyBin is not {@link SMPKeyBin#isValid()}.
219  *
220  * Otherwise, method returns the HCIStatusCode of {@link SMPKeyBin#apply(BTDevice)}.
221  *
222  * If key file is invalid or key could not be applied, i.e. not returning {@link HCIStatusCode#SUCCESS}, it is removed.
223  *
224  * @param path the path of the stored SMPKeyBin file.
225  * @param device the BTDevice for which address the stored SMPKeyBin file will be read and applied to
226  * @param minSecLevel minimum BTSecurityLevel the read SMPKeyBin::sec_level must be compliant to.
227  * If SMPKeyBin::sec_level < minSecLevel method removes the key file and returns {@link HCIStatusCode#ENCRYPTION_MODE_NOT_ACCEPTED}.
228  * @param verbose_ set to true to have detailed read processing logged to stderr, otherwise false
229  * @return {@link HCIStatusCode#SUCCESS} or error code for failure
230  * @see #read(String, BDAddressAndType, boolean)
231  * @see #isValid()
232  * @see #read(String, String)
233  * @see #apply(BTDevice)
234  */
235  static public HCIStatusCode readAndApply(final String path, final BTDevice device, final BTSecurityLevel minSecLevel, final boolean verbose_) {
236  final String fname = getFilename(path, device.getAddressAndType());
237  final SMPKeyBin smpKeyBin = read(fname, verbose_);
238  if( smpKeyBin.isValid() ) {
239  if( smpKeyBin.sec_level.value < minSecLevel.value ) {
240  if( smpKeyBin.verbose ) {
241  BTUtils.fprintf_td(System.err, "SMPKeyBin::readAndApply: sec_level %s < minimum %s: Key ignored %s, removing file %s\n",
242  smpKeyBin.sec_level.toString(),
243  minSecLevel.toString(),
244  smpKeyBin.toString(), fname);
245  }
246  remove_impl(fname);
248  }
249  final HCIStatusCode res = smpKeyBin.apply(device);
250  if( HCIStatusCode.SUCCESS != res ) {
251  if( smpKeyBin.verbose ) {
252  BTUtils.println(System.err, "SMPKeyBin::readAndApply: Apply failed "+res+", "+device+", removing file "+fname);
253  }
254  remove_impl(fname);
255  }
256  return res;
257  } else {
259  }
260  }
261 
262  public SMPKeyBin(final BDAddressAndType addrAndType_,
263  final BTSecurityLevel sec_level_, final SMPIOCapability io_cap_)
264  {
265  version = VERSION;
266  this.size = 0;
267  this.ts_creation_sec = BTUtils.wallClockSeconds();
268  this.addrAndType = addrAndType_;
269  this.sec_level = sec_level_;
270  this.io_cap = io_cap_;
271 
272  this.keys_init = new SMPKeyMask();
273  this.keys_resp = new SMPKeyMask();
274 
275  this.ltk_init = new SMPLongTermKeyInfo();
276  this.csrk_init = new SMPSignatureResolvingKeyInfo();
277 
278  this.ltk_resp = new SMPLongTermKeyInfo();
279  this.csrk_resp = new SMPSignatureResolvingKeyInfo();
280 
281  this.size = calcSize();
282  }
283 
284  public SMPKeyBin() {
285  version = VERSION;
286  size = 0;
287  ts_creation_sec = 0;
288  addrAndType = new BDAddressAndType();
289  sec_level = BTSecurityLevel.UNSET;
290  io_cap = SMPIOCapability.UNSET;
291 
292  keys_init = new SMPKeyMask();
293  keys_resp = new SMPKeyMask();
294 
295  ltk_init = new SMPLongTermKeyInfo();
296  csrk_init = new SMPSignatureResolvingKeyInfo();
297 
298  ltk_resp = new SMPLongTermKeyInfo();
299  csrk_resp = new SMPSignatureResolvingKeyInfo();
300 
301  size = calcSize();
302  }
303 
304  final public boolean isVersionValid() { return VERSION==version; }
305  final public short getVersion() { return version;}
306 
307  final public boolean isSizeValid() { return calcSize() == size;}
308  final public short getSize() { return size;}
309 
310  /** Returns the creation timestamp in seconds since Unix epoch */
311  final public long getCreationTime() { return ts_creation_sec; }
312 
313  final public BDAddressAndType getAddrAndType() { return addrAndType; }
314  final public BTSecurityLevel getSecLevel() { return sec_level; }
315  final public SMPIOCapability getIOCap() { return io_cap; }
316 
317  final public boolean hasLTKInit() { return keys_init.isSet(KeyType.ENC_KEY); }
318  final public boolean hasCSRKInit() { return keys_init.isSet(KeyType.SIGN_KEY); }
319  final public SMPLongTermKeyInfo getLTKInit() { return ltk_init; }
320  final public SMPSignatureResolvingKeyInfo getCSRKInit() { return csrk_init; }
321  final public void setLTKInit(final SMPLongTermKeyInfo v) {
322  ltk_init = v;
323  keys_init.set(KeyType.ENC_KEY);
324  size = calcSize();
325  }
326  final public void setCSRKInit(final SMPSignatureResolvingKeyInfo v) {
327  csrk_init = v;
328  keys_init.set(KeyType.SIGN_KEY);
329  size = calcSize();
330  }
331 
332  final public boolean hasLTKResp() { return keys_resp.isSet(KeyType.ENC_KEY); }
333  final public boolean hasCSRKResp() { return keys_resp.isSet(KeyType.SIGN_KEY); }
334  final public SMPLongTermKeyInfo getLTKResp() { return ltk_resp; }
335  final public SMPSignatureResolvingKeyInfo getCSRKResp() { return csrk_resp; }
336  final public void setLTKResp(final SMPLongTermKeyInfo v) {
337  ltk_resp = v;
338  keys_resp.set(KeyType.ENC_KEY);
339  size = calcSize();
340  }
341  final public void setCSRKResp(final SMPSignatureResolvingKeyInfo v) {
342  csrk_resp = v;
343  keys_resp.set(KeyType.SIGN_KEY);
344  size = calcSize();
345  }
346 
347  final public void setVerbose(final boolean v) { verbose = v; }
348 
349  final public boolean isValid() {
350  return isVersionValid() && isSizeValid() &&
351  BTSecurityLevel.UNSET != sec_level &&
352  SMPIOCapability.UNSET != io_cap &&
353  ( !hasLTKInit() || ltk_init.isValid() ) &&
354  ( !hasLTKResp() || ltk_resp.isValid() );
355  }
356 
357  /**
358  * Returns the base filename, see {@link SMPKeyBin} API doc for naming scheme.
359  */
360  final public String getFileBasename() {
361  final String r = "bd_"+addrAndType.address.toString()+":"+addrAndType.type.value+"-smpkey.bin";
362  return r.replace(':', '_');
363  }
364  /**
365  * Returns the base filename, see {@link SMPKeyBin} API doc for naming scheme.
366  */
367  final public static String getFileBasename(final BDAddressAndType addrAndType_) {
368  final String r = "bd_"+addrAndType_.address.toString()+":"+addrAndType_.type.value+"-smpkey.bin";
369  return r.replace(':', '_');
370  }
371  final public static String getFilename(final String path, final BDAddressAndType addrAndType_) {
372  return path + "/" + getFileBasename(addrAndType_);
373  }
374 
375  @Override
376  final public String toString() {
377  final StringBuilder res = new StringBuilder();
378  res.append("SMPKeyBin[").append(addrAndType.toString()).append(", sec ").append(sec_level).append(", io ").append(io_cap).append(", ");
379  if( isVersionValid() ) {
380  res.append("Init[");
381  if( hasLTKInit() && null != ltk_init ) {
382  res.append(ltk_init.toString());
383  }
384  if( hasCSRKInit() && null != csrk_init ) {
385  if( hasLTKInit() ) {
386  res.append(", ");
387  }
388  res.append(csrk_init.toString());
389  }
390  res.append("], Resp[");
391  if( hasLTKResp() && null != ltk_resp ) {
392  res.append(ltk_resp.toString());
393  }
394  if( hasCSRKResp() && null != csrk_resp ) {
395  if( hasLTKResp() ) {
396  res.append(", ");
397  }
398  res.append(csrk_resp.toString());
399  }
400  res.append("], ");
401  }
402  res.append("ver[0x").append(Integer.toHexString(version)).append(", ok ").append(isVersionValid()).append("], size[").append(size);
403  if( verbose ) {
404  res.append(", calc ").append(calcSize());
405  }
406  res.append(", valid ").append(isSizeValid()).append("], ");
407  {
408  final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
409  final Date d = new Date(ts_creation_sec*1000); // s -> ms
410  res.append(sdf.format(d));
411  }
412  res.append(", valid ").append(isValid()).append("]");
413 
414  return res.toString();
415  }
416 
417  final public static boolean remove(final String path, final BDAddressAndType addrAndType_) {
418  final String fname = getFilename(path, addrAndType_);
419  return remove_impl(fname);
420  }
421  final private static boolean remove_impl(final String fname) {
422  final File file = new File( fname );
423  try {
424  return file.delete(); // alternative to truncate, if existing
425  } catch (final Exception ex) {
426  BTUtils.println(System.err, "Remove SMPKeyBin: Failed "+fname+": "+ex.getMessage());
427  ex.printStackTrace();
428  return false;
429  }
430  }
431 
432  final public boolean write(final String fname, final boolean overwrite) {
433  if( !isValid() ) {
434  BTUtils.println(System.err, "Write SMPKeyBin: Invalid (skipped) "+toString());
435  return false;
436  }
437  final File file = new File( fname );
438  OutputStream out = null;
439  try {
440  if( file.exists() ) {
441  if( overwrite ) {
442  if( !file.delete() ) {
443  BTUtils.println(System.err, "Write SMPKeyBin: Failed deletion of existing file "+fname+": "+toString());
444  return false;
445  }
446  } else {
447  BTUtils.println(System.err, "Write SMPKeyBin: Not overwriting existing file "+fname+": "+toString());
448  return false;
449  }
450  }
451  final byte[] buffer = new byte[byte_size_max];
452 
453  out = new FileOutputStream(file);
454  out.write( (byte) version );
455  out.write( (byte)( version >> 8 ) );
456  out.write( (byte) size );
457  out.write( (byte)( size >> 8 ) );
458 
459  writeLong(ts_creation_sec, out, buffer);
460 
461  out.write(addrAndType.address.b);
462  out.write(addrAndType.type.value);
463  out.write(sec_level.value);
464  out.write(io_cap.value);
465 
466  out.write(keys_init.mask);
467  out.write(keys_resp.mask);
468 
469  if( hasLTKInit() ) {
470  ltk_init.getStream(buffer, 0);
471  out.write(buffer, 0, SMPLongTermKeyInfo.byte_size);
472  }
473  if( hasCSRKInit() ) {
474  csrk_init.getStream(buffer, 0);
475  out.write(buffer, 0, SMPSignatureResolvingKeyInfo.byte_size);
476  }
477 
478  if( hasLTKResp() ) {
479  ltk_resp.getStream(buffer, 0);
480  out.write(buffer, 0, SMPLongTermKeyInfo.byte_size);
481  }
482  if( hasCSRKResp() ) {
483  csrk_resp.getStream(buffer, 0);
484  out.write(buffer, 0, SMPSignatureResolvingKeyInfo.byte_size);
485  }
486  if( verbose ) {
487  BTUtils.println(System.err, "Write SMPKeyBin: "+fname+": "+toString());
488  }
489  return true;
490  } catch (final Exception ex) {
491  BTUtils.println(System.err, "Write SMPKeyBin: Failed "+fname+": "+toString()+": "+ex.getMessage());
492  ex.printStackTrace();
493  } finally {
494  try {
495  if( null != out ) {
496  out.close();
497  }
498  } catch (final IOException e) {
499  e.printStackTrace();
500  }
501  }
502  return false;
503  }
504 
505  final public boolean read(final String fname) {
506  final File file = new File(fname);
507  InputStream in = null;
508  int remaining = 0;
509  boolean err = false;
510  try {
511  if( !file.canRead() ) {
512  if( verbose ) {
513  BTUtils.println(System.err, "Read SMPKeyBin: Failed "+fname+": Not existing or readable: "+toString());
514  }
515  size = 0; // explicitly mark invalid
516  return false;
517  }
518  final byte[] buffer = new byte[byte_size_max];
519  in = new FileInputStream(file);
520  read(in, buffer, byte_size_min, fname);
521 
522  int i=0;
523  version = (short) ( buffer[i++] | ( buffer[i++] << 8 ) );
524  size = (short) ( buffer[i++] | ( buffer[i++] << 8 ) );
525 
526  remaining = size - 2 /* sizeof(version) */ - 2 /* sizeof(size) */;
527 
528  if( !err && 8 <= remaining ) {
529  ts_creation_sec = getLong(buffer, i); i+=8;
530  remaining -= 8;
531  } else {
532  err = true;
533  }
534  if( !err && 11 <= remaining ) {
535  addrAndType.address.putStream(buffer, i); i+=6;
536  addrAndType.type = BDAddressType.get(buffer[i++]);
537  sec_level = BTSecurityLevel.get(buffer[i++]);
538  io_cap = SMPIOCapability.get(buffer[i++]);
539 
540  keys_init.mask = buffer[i++];
541  keys_resp.mask = buffer[i++];
542 
543  remaining -= 11;
544  } else {
545  err = true;
546  }
547 
548  if( !err && hasLTKInit() ) {
549  if( SMPLongTermKeyInfo.byte_size <= remaining ) {
550  read(in, buffer, SMPLongTermKeyInfo.byte_size, fname);
551  ltk_init.putStream(buffer, 0);
552  remaining -= SMPLongTermKeyInfo.byte_size;
553  } else {
554  err = true;
555  }
556  }
557  if( !err && hasCSRKInit() ) {
558  if( SMPSignatureResolvingKeyInfo.byte_size <= remaining ) {
559  read(in, buffer, SMPSignatureResolvingKeyInfo.byte_size, fname);
560  csrk_init.putStream(buffer, 0);
562  } else {
563  err = true;
564  }
565  }
566 
567  if( !err && hasLTKResp() ) {
568  if( SMPLongTermKeyInfo.byte_size <= remaining ) {
569  read(in, buffer, SMPLongTermKeyInfo.byte_size, fname);
570  ltk_resp.putStream(buffer, 0);
571  remaining -= SMPLongTermKeyInfo.byte_size;
572  } else {
573  err = true;
574  }
575  }
576  if( !err && hasCSRKResp() ) {
577  if( SMPSignatureResolvingKeyInfo.byte_size <= remaining ) {
578  read(in, buffer, SMPSignatureResolvingKeyInfo.byte_size, fname);
579  csrk_resp.putStream(buffer, 0);
581  } else {
582  err = true;
583  }
584  }
585  if( !err ) {
586  err = !isValid();
587  }
588  } catch (final Exception ex) {
589  BTUtils.println(System.err, "Read SMPKeyBin: Failed "+fname+": "+toString()+": "+ex.getMessage());
590  ex.printStackTrace();
591  err = true;
592  } finally {
593  try {
594  if( null != in ) {
595  in.close();
596  }
597  } catch (final IOException e) {
598  e.printStackTrace();
599  }
600  }
601  if( err ) {
602  file.delete();
603  if( verbose ) {
604  BTUtils.println(System.err, "Read SMPKeyBin: Failed "+fname+" (removed): "+toString()+", remaining "+remaining);
605  }
606  size = 0; // explicitly mark invalid
607  } else {
608  if( verbose ) {
609  BTUtils.println(System.err, "Read SMPKeyBin: OK "+fname+": "+toString()+", remaining "+remaining);
610  }
611  }
612  return err;
613  }
614  final static private int read(final InputStream in, final byte[] buffer, final int rsize, final String fname) throws IOException {
615  final int read_count = in.read(buffer, 0, rsize);
616  if( read_count != rsize ) {
617  throw new IOException("Couldn't read "+rsize+" bytes, only "+read_count+" from "+fname);
618  }
619  return read_count;
620  }
621  final static private long getLong(final byte[] buffer, final int offset) throws IOException {
622  long res = 0;
623  for (int j = 0; j < 8; ++j) {
624  res |= ((long) buffer[offset+j] & 0xff) << j*8;
625  }
626  return res;
627  }
628  final static private void writeLong(final long value, final OutputStream out, final byte[] buffer) throws IOException {
629  for (int i = 0; i < 8; ++i) {
630  buffer[i] = (byte) (value >> i*8);
631  }
632  out.write(buffer, 0, 8);
633  }
634 
635  /**
636  * If this instance isValid() and initiator or responder LTK available, i.e. hasLTKInit() or hasLTKResp(),
637  * the following procedure will be applied to the given BTDevice:
638  *
639  * - If BTSecurityLevel _is_ BTSecurityLevel::NONE
640  * + Setting security to ::BTSecurityLevel::NONE and SMPIOCapability::NO_INPUT_NO_OUTPUT via BTDevice::setConnSecurity()
641  * - else if BTSecurityLevel > BTSecurityLevel::NONE
642  * + Setting security to ::BTSecurityLevel::ENC_ONLY and SMPIOCapability::NO_INPUT_NO_OUTPUT via BTDevice::setConnSecurity()
643  * + Setting initiator LTK from getLTKInit() via BTDevice::setLongTermKeyInfo(), if available
644  * + Setting responder LTK from getLTKResp() via BTDevice::setLongTermKeyInfo(), if available
645  *
646  * If all three operations succeed, HCIStatusCode::SUCCESS will be returned,
647  * otherwise the appropriate status code below.
648  *
649  * BTSecurityLevel::ENC_ONLY is set to avoid a new SMP PairingMode negotiation,
650  * which is undesired as this instances' stored LTK shall be used for PairingMode::PRE_PAIRED.
651  *
652  * Method may fail for any of the following reasons:
653  *
654  * Reason | HCIStatusCode |
655  * :------------------------------------------------------ | :---------------------------------------- |
656  * ! isValid() | HCIStatusCode::INVALID_PARAMS |
657  * ! hasLTKInit() && ! hasLTKResp() | HCIStatusCode::INVALID_PARAMS |
658  * BTDevice::isValid() == false | HCIStatusCode::INVALID_PARAMS |
659  * BTDevice has already being connected | HCIStatusCode::CONNECTION_ALREADY_EXISTS |
660  * BTDevice::connectLE() or BTDevice::connectBREDR() called | HCIStatusCode::CONNECTION_ALREADY_EXISTS |
661  * BTDevice::setLongTermKeyInfo() failed | HCIStatusCode from BT adapter |
662  *
663  * On failure and after BTDevice::setConnSecurity() has been performed, the ::BTSecurityLevel
664  * and ::SMPIOCapability pre-connect values have been written and must be set by the caller again.
665  *
666  * @param device the BTDevice for which this instances' LTK shall be applied
667  *
668  * @see isValid()
669  * @see hasLTKInit()
670  * @see hasLTKResp()
671  * @see getLTKInit()
672  * @see getLTKResp()
673  * @see BTSecurityLevel
674  * @see SMPIOCapability
675  * @see BTDevice::isValid()
676  * @see BTDevice::setConnSecurity()
677  * @see BTDevice::setLongTermKeyInfo()
678  */
679  final public HCIStatusCode apply(final BTDevice device) {
681 
682  // Must be a valid SMPKeyBin instance and at least one LTK key if using encryption.
683  if( !isValid() || ( BTSecurityLevel.NONE != sec_level && !hasLTKInit() && !hasLTKResp() ) ) {
685  if( verbose ) {
686  BTUtils.println(System.err, "Apply SMPKeyBin failed: SMPKeyBin Status: "+res+", "+toString());
687  }
688  return res;
689  }
690  if( !device.isValid() ) {
692  if( verbose ) {
693  BTUtils.println(System.err, "Apply SMPKeyBin failed: Device Invalid: "+res+", "+toString()+", "+device);
694  }
695  return res;
696  }
697 
698  // Allow no encryption at all, i.e. BTSecurityLevel::NONE
699  final BTSecurityLevel applySecLevel = BTSecurityLevel.NONE == sec_level ?
701 
702  if( !device.setConnSecurity(applySecLevel, SMPIOCapability.NO_INPUT_NO_OUTPUT) ) {
704  if( verbose ) {
705  BTUtils.println(System.err, "Apply SMPKeyBin failed: Device Connected/ing: "+res+", "+toString()+", "+device);
706  }
707  return res;
708  }
709 
710  if( hasLTKInit() ) {
711  res = device.setLongTermKeyInfo( getLTKInit() );
712  if( HCIStatusCode.SUCCESS != res && verbose ) {
713  BTUtils.println(System.err, "Apply SMPKeyBin failed: Init-LTK Upload: "+res+", "+toString()+", "+device);
714  }
715  }
716 
717  if( HCIStatusCode.SUCCESS == res && hasLTKResp() ) {
718  res = device.setLongTermKeyInfo( getLTKResp() );
719  if( HCIStatusCode.SUCCESS != res && verbose ) {
720  BTUtils.println(System.err, "Apply SMPKeyBin failed: Resp-LTK Upload: "+res+", "+toString()+", "+device);
721  }
722  }
723 
724  return res;
725  }
726 };
org.direct_bt.BTSecurityLevel.ENC_ONLY
ENC_ONLY
Encryption and no authentication (no MITM).
Definition: BTSecurityLevel.java:44
org.direct_bt.SMPSignatureResolvingKeyInfo.putStream
void putStream(final byte[] source, int pos)
Method transfers all bytes representing a SMPLongTermKeyInfo from the given source array at the given...
Definition: SMPSignatureResolvingKeyInfo.java:153
org.direct_bt.BDAddressType.value
final byte value
Definition: BDAddressType.java:63
org.direct_bt.EUI48.toString
final String toString()
Definition: EUI48.java:278
org.direct_bt.BTSecurityLevel.value
final byte value
Definition: BTSecurityLevel.java:50
org.direct_bt.SMPKeyBin.isVersionValid
final boolean isVersionValid()
Definition: SMPKeyBin.java:304
org.direct_bt.SMPKeyBin.getAddrAndType
final BDAddressAndType getAddrAndType()
Definition: SMPKeyBin.java:313
org.direct_bt.SMPKeyBin.hasCSRKResp
final boolean hasCSRKResp()
Definition: SMPKeyBin.java:333
org.direct_bt.BTDevice.getPairingState
SMPPairingState getPairingState()
Returns the current SMPPairingState.
org.direct_bt.SMPKeyBin.setVerbose
final void setVerbose(final boolean v)
Definition: SMPKeyBin.java:347
org.direct_bt.SMPIOCapability
SMP IO Capability value.
Definition: SMPIOCapability.java:37
org.direct_bt.SMPKeyBin.readAndApply
static HCIStatusCode readAndApply(final String path, final BTDevice device, final BTSecurityLevel minSecLevel, final boolean verbose_)
Create a new SMPKeyBin instance on the fly based upon stored file denoted by path and BTDevice#getAdd...
Definition: SMPKeyBin.java:235
org.direct_bt.BTSecurityLevel
Bluetooth Security Level.
Definition: BTSecurityLevel.java:38
org.direct_bt.SMPIOCapability.NO_INPUT_NO_OUTPUT
NO_INPUT_NO_OUTPUT
No input not output, value 3.
Definition: SMPIOCapability.java:45
org.direct_bt.BTUtils.fprintf_td
static void fprintf_td(final PrintStream out, final String format, final Object ... args)
Convenient PrintStream#printf(String, Object...) invocation, prepending the elapsedTimeMillis() times...
Definition: BTUtils.java:68
org.direct_bt.SMPLongTermKeyInfo.putStream
void putStream(final byte[] source, int pos)
Method transfers all bytes representing a SMPLongTermKeyInfo from the given source array at the given...
Definition: SMPLongTermKeyInfo.java:171
org.direct_bt.BTDevice.getConnIOCapability
SMPIOCapability getConnIOCapability()
Return the SMPIOCapability value, determined when the connection is established.
org.direct_bt
Author: Sven Gothel sgothel@jausoft.com Copyright (c) 2020 Gothel Software e.K.
org.direct_bt.PairingMode
Bluetooth secure pairing mode.
Definition: PairingMode.java:40
org.direct_bt.SMPKeyBin.write
final boolean write(final String fname, final boolean overwrite)
Definition: SMPKeyBin.java:432
org.direct_bt.HCIStatusCode.CONNECTION_ALREADY_EXISTS
CONNECTION_ALREADY_EXISTS
Definition: HCIStatusCode.java:46
org.direct_bt.SMPLongTermKeyInfo.byte_size
static final int byte_size
Size of the byte stream representation in bytes (28)
Definition: SMPLongTermKeyInfo.java:139
org.direct_bt.SMPKeyMask.mask
byte mask
The KeyType bit mask.
Definition: SMPKeyMask.java:134
org.direct_bt.BTDevice.getLongTermKeyInfo
SMPLongTermKeyInfo getLongTermKeyInfo(final boolean responder)
Returns a copy of the long term key (LTK) info, valid after connection and SMP pairing has been compl...
org.direct_bt.SMPKeyBin.getIOCap
final SMPIOCapability getIOCap()
Definition: SMPKeyBin.java:315
org.direct_bt.SMPSignatureResolvingKeyInfo.toString
String toString()
Definition: SMPSignatureResolvingKeyInfo.java:183
org.direct_bt.BTDevice.setConnSecurity
boolean setConnSecurity(final BTSecurityLevel sec_level, final SMPIOCapability io_cap)
Sets the given BTSecurityLevel and SMPIOCapability used to connect to this device on the upcoming con...
org.direct_bt.SMPKeyBin.createAndWrite
static boolean createAndWrite(final BTDevice device, final String path, final boolean overwrite, final boolean verbose_)
Create a new SMPKeyBin instance on the fly based upon given BTDevice's BTSecurityLevel,...
Definition: SMPKeyBin.java:179
org.direct_bt.BTDevice.isValid
boolean isValid()
Returns whether the device is valid, i.e.
org.direct_bt.SMPSignatureResolvingKeyInfo.getStream
final void getStream(final byte[] sink, int pos)
Method transfers all bytes representing this instance into the given destination array at the given p...
Definition: SMPSignatureResolvingKeyInfo.java:172
org.direct_bt.HCIStatusCode.ENCRYPTION_MODE_NOT_ACCEPTED
ENCRYPTION_MODE_NOT_ACCEPTED
Definition: HCIStatusCode.java:72
org.direct_bt.SMPKeyBin.getFileBasename
final String getFileBasename()
Returns the base filename, see SMPKeyBin API doc for naming scheme.
Definition: SMPKeyBin.java:360
org.direct_bt.SMPKeyBin.apply
final HCIStatusCode apply(final BTDevice device)
If this instance isValid() and initiator or responder LTK available, i.e.
Definition: SMPKeyBin.java:679
org.direct_bt.SMPKeyBin.getVersion
final short getVersion()
Definition: SMPKeyBin.java:305
org.direct_bt.SMPKeyMask.isSet
boolean isSet(final KeyType bit)
Definition: SMPKeyMask.java:145
org.direct_bt.BDAddressAndType.toString
final String toString()
Definition: BDAddressAndType.java:154
org.direct_bt.SMPKeyBin
Storage for SMP keys including the required connection parameter.
Definition: SMPKeyBin.java:60
org.direct_bt.SMPLongTermKeyInfo
SMP Long Term Key Info, used for platform agnostic persistence.
Definition: SMPLongTermKeyInfo.java:39
org.direct_bt.SMPKeyBin.SMPKeyBin
SMPKeyBin(final BDAddressAndType addrAndType_, final BTSecurityLevel sec_level_, final SMPIOCapability io_cap_)
Definition: SMPKeyBin.java:262
org.direct_bt.PairingMode.NONE
NONE
No pairing mode, implying no secure connections, no encryption and no MITM protection.
Definition: PairingMode.java:42
org.direct_bt.SMPIOCapability.value
final byte value
Definition: SMPIOCapability.java:51
org.direct_bt.SMPKeyMask
SMP Key Type for Distribution, indicates keys distributed in the Transport Specific Key Distribution ...
Definition: SMPKeyMask.java:44
org.direct_bt.SMPKeyBin.getCSRKInit
final SMPSignatureResolvingKeyInfo getCSRKInit()
Definition: SMPKeyBin.java:320
org.direct_bt.SMPKeyMask.set
void set(final KeyType bit)
Definition: SMPKeyMask.java:146
org.direct_bt.SMPKeyBin.getFileBasename
final static String getFileBasename(final BDAddressAndType addrAndType_)
Returns the base filename, see SMPKeyBin API doc for naming scheme.
Definition: SMPKeyBin.java:367
org.direct_bt.EUI48.putStream
final void putStream(final byte[] source, final int pos)
Method transfers all bytes representing a EUI48 from the given source array at the given position int...
Definition: EUI48.java:207
org.direct_bt.BTUtils.println
static void println(final PrintStream out, final String msg)
Convenient PrintStream#println(String) invocation, prepending the elapsedTimeMillis() timestamp.
Definition: BTUtils.java:77
org.direct_bt.SMPKeyBin.setCSRKInit
final void setCSRKInit(final SMPSignatureResolvingKeyInfo v)
Definition: SMPKeyBin.java:326
org.direct_bt.SMPKeyBin.toString
final String toString()
Definition: SMPKeyBin.java:376
org.direct_bt.SMPKeyMask.KeyType
SMPKeyMask Key Type
Definition: SMPKeyMask.java:48
org.direct_bt.BDAddressAndType.type
BDAddressType type
Definition: BDAddressAndType.java:47
org.direct_bt.SMPSignatureResolvingKeyInfo.byte_size
static final int byte_size
Size of the byte stream representation in bytes.
Definition: SMPSignatureResolvingKeyInfo.java:126
org.direct_bt.BTDevice.getConnSecurityLevel
BTSecurityLevel getConnSecurityLevel()
Return the BTSecurityLevel, determined when the connection is established.
org.direct_bt.BTDevice.getAvailableSMPKeys
SMPKeyMask getAvailableSMPKeys(final boolean responder)
Returns the available SMPKeyMask.KeyType SMPKeyMask for the responder (LL slave) or initiator (LL mas...
org.direct_bt.HCIStatusCode.SUCCESS
SUCCESS
Definition: HCIStatusCode.java:35
org.direct_bt.SMPIOCapability.UNSET
UNSET
Denoting unset value, i.e.
Definition: SMPIOCapability.java:49
org.direct_bt.SMPKeyBin.isSizeValid
final boolean isSizeValid()
Definition: SMPKeyBin.java:307
org.direct_bt.SMPKeyBin.setLTKResp
final void setLTKResp(final SMPLongTermKeyInfo v)
Definition: SMPKeyBin.java:336
org.direct_bt.SMPPairingState.NONE
NONE
No pairing in process.
Definition: SMPPairingState.java:40
org.direct_bt.SMPPairingState
SMP Pairing Process state definition.
Definition: SMPPairingState.java:38
org.direct_bt.BDAddressAndType
Unique Bluetooth EUI48 address and BDAddressType tuple.
Definition: BDAddressAndType.java:36
org.direct_bt.PairingMode.NEGOTIATING
NEGOTIATING
Pairing mode in negotiating, i.e.
Definition: PairingMode.java:44
org.direct_bt.BTSecurityLevel.get
static BTSecurityLevel get(final String name)
Maps the specified name to a constant of BTSecurityLevel.
Definition: BTSecurityLevel.java:63
org.direct_bt.SMPKeyMask.KeyType.SIGN_KEY
SIGN_KEY
Indicates that the device shall distribute CSRK using the Signing Information command.
Definition: SMPKeyMask.java:72
org.direct_bt.SMPPairingState.COMPLETED
COMPLETED
Phase 3: Key & value distribution completed by responding (slave) device sending SMPIdentInfoMsg (#1)...
Definition: SMPPairingState.java:83
org.direct_bt.BTUtils
Definition: BTUtils.java:29
org.direct_bt.BTSecurityLevel.UNSET
UNSET
Security Level not set, value 0.
Definition: BTSecurityLevel.java:40
org.direct_bt.EUI48.b
final byte b[]
The 6 byte EUI48 address.
Definition: EUI48.java:52
org.direct_bt.SMPLongTermKeyInfo.toString
String toString()
Definition: SMPLongTermKeyInfo.java:211
org.direct_bt.HCIStatusCode
BT Core Spec v5.2: Vol 1, Part F Controller Error Codes: 1.3 List of Error Codes.
Definition: HCIStatusCode.java:34
org.direct_bt.PairingMode.value
final byte value
Definition: PairingMode.java:60
org.direct_bt.SMPKeyMask.KeyType.ENC_KEY
ENC_KEY
LE legacy pairing: Indicates device shall distribute LTK using the Encryption Information command,...
Definition: SMPKeyMask.java:63
org.direct_bt.BTDevice.getPairingMode
PairingMode getPairingMode()
Returns the current PairingMode used by the device.
org.direct_bt.SMPKeyBin.setCSRKResp
final void setCSRKResp(final SMPSignatureResolvingKeyInfo v)
Definition: SMPKeyBin.java:341
org.direct_bt.SMPKeyBin.getCSRKResp
final SMPSignatureResolvingKeyInfo getCSRKResp()
Definition: SMPKeyBin.java:335
org.direct_bt.SMPKeyBin.hasCSRKInit
final boolean hasCSRKInit()
Definition: SMPKeyBin.java:318
org.direct_bt.SMPIOCapability.get
static SMPIOCapability get(final String name)
Maps the specified name to a constant of SMPIOCapability.
Definition: SMPIOCapability.java:64
org.direct_bt.SMPKeyBin.getFilename
final static String getFilename(final String path, final BDAddressAndType addrAndType_)
Definition: SMPKeyBin.java:371
org.direct_bt.BTDevice
Provides access to Bluetooth adapters.
Definition: BTDevice.java:42
org.direct_bt.SMPKeyBin.isValid
final boolean isValid()
Definition: SMPKeyBin.java:349
org.direct_bt.SMPKeyBin.hasLTKInit
final boolean hasLTKInit()
Definition: SMPKeyBin.java:317
org
org.direct_bt.SMPKeyBin.read
static SMPKeyBin read(final String fname, final boolean verbose_)
Create a new SMPKeyBin instance based upon stored file denoted by fname.
Definition: SMPKeyBin.java:207
org.direct_bt.SMPKeyBin.getCreationTime
final long getCreationTime()
Returns the creation timestamp in seconds since Unix epoch.
Definition: SMPKeyBin.java:311
org.direct_bt.BTDevice.getAddressAndType
BDAddressAndType getAddressAndType()
Returns the unique device EUI48 address and BDAddressType type.
org.direct_bt.BDAddressType
Bluetooth address type constants.
Definition: BDAddressType.java:53
org.direct_bt.SMPKeyBin.hasLTKResp
final boolean hasLTKResp()
Definition: SMPKeyBin.java:332
org.direct_bt.SMPKeyBin.read
final boolean read(final String fname)
Definition: SMPKeyBin.java:505
org.direct_bt.HCIStatusCode.INVALID_PARAMS
INVALID_PARAMS
Definition: HCIStatusCode.java:115
org.direct_bt.SMPKeyBin.getSecLevel
final BTSecurityLevel getSecLevel()
Definition: SMPKeyBin.java:314
org.direct_bt.BTUtils.wallClockSeconds
static native long wallClockSeconds()
Returns current wall-clock system time of day in seconds since Unix Epoch 00:00:00 UTC on 1 January 1...
org.direct_bt.BTDevice.setLongTermKeyInfo
HCIStatusCode setLongTermKeyInfo(final SMPLongTermKeyInfo ltk)
Sets the long term ket (LTK) info of this device to reuse pre-paired encryption.
org.direct_bt.SMPLongTermKeyInfo.isValid
final boolean isValid()
Definition: SMPLongTermKeyInfo.java:206
org.direct_bt.SMPKeyBin.VERSION
static final short VERSION
Definition: SMPKeyBin.java:61
org.direct_bt.SMPKeyBin.SMPKeyBin
SMPKeyBin()
Definition: SMPKeyBin.java:284
org.direct_bt.BTSecurityLevel.NONE
NONE
No encryption and no authentication.
Definition: BTSecurityLevel.java:42
org.direct_bt.SMPSignatureResolvingKeyInfo
SMP Signature Resolving Key Info, used for platform agnostic persistence.
Definition: SMPSignatureResolvingKeyInfo.java:39
org.direct_bt.BDAddressType.get
static BDAddressType get(final String name)
Maps the specified name to a constant of BDAddressType.
Definition: BDAddressType.java:104
org.direct_bt.SMPKeyBin.getLTKInit
final SMPLongTermKeyInfo getLTKInit()
Definition: SMPKeyBin.java:319
org.direct_bt.BDAddressAndType.address
EUI48 address
Definition: BDAddressAndType.java:46
org.direct_bt.BTDevice.getSignatureResolvingKeyInfo
SMPSignatureResolvingKeyInfo getSignatureResolvingKeyInfo(final boolean responder)
Returns a copy of the Signature Resolving Key (LTK) info, valid after connection and SMP pairing has ...
org.direct_bt.SMPKeyBin.create
static SMPKeyBin create(final BTDevice device)
Create a new SMPKeyBin instance based upon given BTDevice's BTSecurityLevel, SMPPairingState,...
Definition: SMPKeyBin.java:127
org.direct_bt.SMPLongTermKeyInfo.getStream
final void getStream(final byte[] sink, int pos)
Method transfers all bytes representing this instance into the given destination array at the given p...
Definition: SMPLongTermKeyInfo.java:194
org.direct_bt.SMPKeyBin.getLTKResp
final SMPLongTermKeyInfo getLTKResp()
Definition: SMPKeyBin.java:334
org.direct_bt.SMPKeyBin.setLTKInit
final void setLTKInit(final SMPLongTermKeyInfo v)
Definition: SMPKeyBin.java:321
org.direct_bt.SMPKeyBin.getSize
final short getSize()
Definition: SMPKeyBin.java:308