aboutsummaryrefslogtreecommitdiffstats
path: root/java/org/direct_bt/SMPKeyBin.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/org/direct_bt/SMPKeyBin.java')
-rw-r--r--java/org/direct_bt/SMPKeyBin.java393
1 files changed, 393 insertions, 0 deletions
diff --git a/java/org/direct_bt/SMPKeyBin.java b/java/org/direct_bt/SMPKeyBin.java
new file mode 100644
index 00000000..34193db7
--- /dev/null
+++ b/java/org/direct_bt/SMPKeyBin.java
@@ -0,0 +1,393 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2021 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.direct_bt;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.direct_bt.SMPKeyMask.KeyType;
+
+/**
+ * Storage for a device's {@link BDAddressAndType}, its security connection setup {@link BTSecurityLevel} + {@link SMPIOCapability}
+ * and optionally the initiator and responder {@link SMPLongTermKeyInfo LTK} and {@link SMPSignatureResolvingKeyInfo CSRK} within one file.
+ * <p>
+ * Since the {@link SMPLongTermKeyInfo LTK} and {@link SMPSignatureResolvingKeyInfo CSRK}
+ * can be optionally set due to their availability per initiator and responder,
+ * implementation supports mixed mode for certain devices.
+ * E.g. LTK responder key only etc.
+ * </p>
+ */
+public class SMPKeyBin {
+ public static final short VERSION = (short)0b0101010101010101 + (short)1; // bitpattern + version
+
+ private short version; // 2
+ private short size; // 2
+ private final BDAddressAndType addrAndType; // 7
+ private BTSecurityLevel sec_level;; // 1
+ private SMPIOCapability io_cap; // 1
+
+ private final SMPKeyMask keys_init; // 1
+ private final SMPKeyMask keys_resp; // 1
+
+ private SMPLongTermKeyInfo ltk_init; // 28 (optional)
+ private SMPSignatureResolvingKeyInfo csrk_init; // 17 (optional)
+
+ private SMPLongTermKeyInfo ltk_resp; // 28 (optional)
+ private SMPSignatureResolvingKeyInfo csrk_resp; // 17 (optional)
+
+ private static final int byte_size_max = 105;
+ private static final int byte_size_min = 15;
+ // Min-Max: 15 - 105 bytes
+
+ boolean verbose;
+
+ final private short calcSize() {
+ short s = 0;
+ s += 2; // sizeof(version);
+ s += 2; // sizeof(size);
+ s += 6; // sizeof(addrAndType.address);
+ s += 1; // sizeof(addrAndType.type);
+ s += 1; // sizeof(sec_level);
+ s += 1; // sizeof(io_cap);
+
+ s += 1; // sizeof(keys_init);
+ s += 1; // sizeof(keys_resp);
+
+ if( hasLTKInit() ) {
+ s += 28; // sizeof(ltk_init);
+ }
+ if( hasCSRKInit() ) {
+ s += 17; // sizeof(csrk_init);
+ }
+
+ if( hasLTKResp() ) {
+ s += 28; // sizeof(ltk_resp);
+ }
+ if( hasCSRKResp() ) {
+ s += 17; // sizeof(csrk_resp);
+ }
+ return s;
+ }
+
+ public SMPKeyBin(final BDAddressAndType addrAndType_,
+ final BTSecurityLevel sec_level_, final SMPIOCapability io_cap_)
+ {
+ version = VERSION;
+ this.size = 0;
+ this.addrAndType = addrAndType_;
+ this.sec_level = sec_level_;
+ this.io_cap = io_cap_;
+
+ this.keys_init = new SMPKeyMask();
+ this.keys_resp = new SMPKeyMask();
+
+ this.ltk_init = new SMPLongTermKeyInfo();
+ this.csrk_init = new SMPSignatureResolvingKeyInfo();
+
+ this.ltk_resp = new SMPLongTermKeyInfo();
+ this.csrk_resp = new SMPSignatureResolvingKeyInfo();
+
+ this.size = calcSize();
+ }
+
+ public SMPKeyBin() {
+ version = VERSION;
+ size = 0;
+ addrAndType = new BDAddressAndType();
+ sec_level = BTSecurityLevel.UNSET;
+ io_cap = SMPIOCapability.UNSET;
+
+ keys_init = new SMPKeyMask();
+ keys_resp = new SMPKeyMask();
+
+ ltk_init = new SMPLongTermKeyInfo();
+ csrk_init = new SMPSignatureResolvingKeyInfo();
+
+ ltk_resp = new SMPLongTermKeyInfo();
+ csrk_resp = new SMPSignatureResolvingKeyInfo();
+
+ size = calcSize();
+ }
+
+ final public boolean isVersionValid() { return VERSION==version; }
+ final public short getVersion() { return version;}
+
+ final public boolean isSizeValid() { return calcSize() == size;}
+ final public short getSize() { return size;}
+
+ final public BDAddressAndType getAddrAndType() { return addrAndType; }
+ final public BTSecurityLevel getSecLevel() { return sec_level; }
+ final public SMPIOCapability getIOCap() { return io_cap; }
+
+ final public boolean hasLTKInit() { return keys_init.isSet(KeyType.ENC_KEY); }
+ final public boolean hasCSRKInit() { return keys_init.isSet(KeyType.SIGN_KEY); }
+ final public SMPLongTermKeyInfo getLTKInit() { return ltk_init; }
+ final public SMPSignatureResolvingKeyInfo getCSRKInit() { return csrk_init; }
+ final public void setLTKInit(final SMPLongTermKeyInfo v) {
+ ltk_init = v;
+ keys_init.set(KeyType.ENC_KEY);
+ size = calcSize();
+ }
+ final public void setCSRKInit(final SMPSignatureResolvingKeyInfo v) {
+ csrk_init = v;
+ keys_init.set(KeyType.SIGN_KEY);
+ size = calcSize();
+ }
+
+ final public boolean hasLTKResp() { return keys_resp.isSet(KeyType.ENC_KEY); }
+ final public boolean hasCSRKResp() { return keys_resp.isSet(KeyType.SIGN_KEY); }
+ final public SMPLongTermKeyInfo getLTKResp() { return ltk_resp; }
+ final public SMPSignatureResolvingKeyInfo getCSRKResp() { return csrk_resp; }
+ final public void setLTKResp(final SMPLongTermKeyInfo v) {
+ ltk_resp = v;
+ keys_resp.set(KeyType.ENC_KEY);
+ size = calcSize();
+ }
+ final public void setCSRKResp(final SMPSignatureResolvingKeyInfo v) {
+ csrk_resp = v;
+ keys_resp.set(KeyType.SIGN_KEY);
+ size = calcSize();
+ }
+
+ final public void setVerbose(final boolean v) { verbose = v; }
+
+ final public boolean isValid() {
+ return isVersionValid() && isSizeValid() &&
+ BTSecurityLevel.UNSET != sec_level &&
+ SMPIOCapability.UNSET != io_cap &&
+ ( !hasLTKInit() || ltk_init.isValid() ) &&
+ ( !hasLTKResp() || ltk_resp.isValid() );
+ }
+
+ final public String getFileBasename() {
+ return "bd_"+addrAndType.address.toString()+":"+addrAndType.type.value+".smpkey.bin";
+ }
+ final public static String getFileBasename(final BDAddressAndType addrAndType_) {
+ return "bd_"+addrAndType_.address.toString()+":"+addrAndType_.type.value+".smpkey.bin";
+ }
+
+ @Override
+ final public String toString() {
+ final StringBuilder res = new StringBuilder();
+ res.append("SMPKeyBin[").append(addrAndType.toString()).append(", sec ").append(sec_level).append(", io ").append(io_cap).append(", ");
+ if( isVersionValid() ) {
+ res.append("Init[");
+ if( hasLTKInit() && null != ltk_init ) {
+ res.append(ltk_init.toString());
+ }
+ if( hasCSRKInit() && null != csrk_init ) {
+ if( hasLTKInit() ) {
+ res.append(", ");
+ }
+ res.append(csrk_init.toString());
+ }
+ res.append("], Resp[");
+ if( hasLTKResp() && null != ltk_resp ) {
+ res.append(ltk_resp.toString());
+ }
+ if( hasCSRKResp() && null != csrk_resp ) {
+ if( hasLTKResp() ) {
+ res.append(", ");
+ }
+ res.append(csrk_resp.toString());
+ }
+ res.append("], ");
+ }
+ res.append("ver[0x").append(Integer.toHexString(version)).append(", ok ").append(isVersionValid()).append("], size[").append(size);
+ if( verbose ) {
+ res.append(", calc ").append(calcSize());
+ }
+ res.append(", valid ").append(isSizeValid()).append("], valid ").append(isValid()).append("]");
+
+ return res.toString();
+ }
+
+ final public boolean write(final String path, final String basename) {
+ if( !isValid() ) {
+ System.err.println("****** WRITE SMPKeyBin: Invalid (skipped) "+toString());
+ return false;
+ }
+ final String fname = path+"/"+basename;
+ final File file = new File(fname);
+ OutputStream out = null;
+ try {
+ file.delete(); // alternative to truncate, if existing
+ out = new FileOutputStream(file);
+ out.write( (byte) version );
+ out.write( (byte)( version >> 8 ) );
+ out.write( (byte) size );
+ out.write( (byte)( size >> 8 ) );
+ out.write(addrAndType.address.b);
+ out.write(addrAndType.type.value);
+ out.write(sec_level.value);
+ out.write(io_cap.value);
+
+ out.write(keys_init.mask);
+ out.write(keys_resp.mask);
+
+ if( hasLTKInit() ) {
+ final byte[] ltk_init_b = new byte[SMPLongTermKeyInfo.byte_size];
+ ltk_init.getStream(ltk_init_b, 0);
+ out.write(ltk_init_b);
+ }
+ if( hasCSRKInit() ) {
+ final byte[] csrk_init_b = new byte[SMPSignatureResolvingKeyInfo.byte_size];
+ csrk_init.getStream(csrk_init_b, 0);
+ out.write(csrk_init_b);
+ }
+
+ if( hasLTKResp() ) {
+ final byte[] ltk_resp_b = new byte[SMPLongTermKeyInfo.byte_size];
+ ltk_resp.getStream(ltk_resp_b, 0);
+ out.write(ltk_resp_b);
+ }
+ if( hasCSRKResp() ) {
+ final byte[] csrk_resp_b = new byte[SMPSignatureResolvingKeyInfo.byte_size];
+ csrk_resp.getStream(csrk_resp_b, 0);
+ out.write(csrk_resp_b);
+ }
+ if( verbose ) {
+ System.err.println("****** WRITE SMPKeyBin: "+fname+": "+toString());
+ }
+ return true;
+ } catch (final Exception ex) {
+ System.err.println("****** WRITE SMPKeyBin: Failed "+fname+": "+toString()+": "+ex.getMessage());
+ ex.printStackTrace();
+ } finally {
+ try {
+ if( null != out ) {
+ out.close();
+ }
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return false;
+ }
+ final public boolean write(final String path) {
+ return write( path, getFileBasename() );
+ }
+
+ final public boolean read(final String path, final String basename) {
+ final String fname = path+"/"+basename;
+ final File file = new File(fname);
+ InputStream in = null;
+ try {
+ if( !file.canRead() ) {
+ if( verbose ) {
+ System.err.println("****** READ SMPKeyBin: Failed "+fname+": Not existing or readable: "+toString());
+ }
+ return false;
+ }
+ final byte[] buffer = new byte[byte_size_max];
+ in = new FileInputStream(file);
+ read(in, buffer, byte_size_min, fname);
+
+ int i=0;
+ version = (short) ( buffer[i++] | ( buffer[i++] << 8 ) );
+ size = (short) ( buffer[i++] | ( buffer[i++] << 8 ) );
+ addrAndType.address.putStream(buffer, i); i+=6;
+ addrAndType.type = BDAddressType.get(buffer[i++]);
+ sec_level = BTSecurityLevel.get(buffer[i++]);
+ io_cap = SMPIOCapability.get(buffer[i++]);
+
+ keys_init.mask = buffer[i++];
+ keys_resp.mask = buffer[i++];
+
+ int remaining = size - i; // i == byte_size_min == 15
+ boolean err = false;
+
+ if( !err && hasLTKInit() ) {
+ if( SMPLongTermKeyInfo.byte_size <= remaining ) {
+ read(in, buffer, SMPLongTermKeyInfo.byte_size, fname);
+ ltk_init.putStream(buffer, 0);
+ remaining -= SMPLongTermKeyInfo.byte_size;
+ } else {
+ err = true;
+ }
+ }
+ if( !err && hasCSRKInit() ) {
+ if( SMPSignatureResolvingKeyInfo.byte_size <= remaining ) {
+ read(in, buffer, SMPSignatureResolvingKeyInfo.byte_size, fname);
+ csrk_init.putStream(buffer, 0);
+ remaining -= SMPSignatureResolvingKeyInfo.byte_size;
+ } else {
+ err = true;
+ }
+ }
+
+ if( !err && hasLTKResp() ) {
+ if( SMPLongTermKeyInfo.byte_size <= remaining ) {
+ read(in, buffer, SMPLongTermKeyInfo.byte_size, fname);
+ ltk_resp.putStream(buffer, 0);
+ remaining -= SMPLongTermKeyInfo.byte_size;
+ } else {
+ err = true;
+ }
+ }
+ if( !err && hasCSRKResp() ) {
+ if( SMPSignatureResolvingKeyInfo.byte_size <= remaining ) {
+ read(in, buffer, SMPSignatureResolvingKeyInfo.byte_size, fname);
+ csrk_resp.putStream(buffer, 0);
+ remaining -= SMPSignatureResolvingKeyInfo.byte_size;
+ } else {
+ err = true;
+ }
+ }
+
+ if( verbose ) {
+ System.err.println("****** READ SMPKeyBin: "+fname+": "+toString()+", remaining "+remaining);
+ }
+ return isValid();
+ } catch (final Exception ex) {
+ System.err.println("****** READ SMPKeyBin: Failed "+fname+": "+toString()+": "+ex.getMessage());
+ ex.printStackTrace();
+ } finally {
+ try {
+ if( null != in ) {
+ in.close();
+ }
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return false;
+ }
+ final public boolean read(final String path, final BDAddressAndType addrAndType_) {
+ return read(path, getFileBasename(addrAndType_));
+ }
+
+ final static private int read(final InputStream in, final byte[] buffer, final int rsize, final String fname) throws IOException {
+ final int read_count = in.read(buffer, 0, rsize);
+ if( read_count != rsize ) {
+ throw new IOException("Couldn't read "+rsize+" bytes, only "+read_count+" from "+fname);
+ }
+ return read_count;
+ }
+};