diff options
author | Sven Gothel <[email protected]> | 2021-02-02 07:19:15 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2021-02-02 07:19:15 +0100 |
commit | 32a8e1a7622f4cede05c7038d3e7a4213b546a35 (patch) | |
tree | 0669e80d6bf2ecb220319ad0be45e2bc1fb1cb90 | |
parent | 26091fd771661bb8dcf4364f9a088590d98454fc (diff) |
Promote persistent SMP Key Storage to API: SMPKeyBinv2.2.3
SMPKeyBin stores device's BDAddressAndType, its security connection setup BTSecurityLevel + SMPIOCapability
and optionally the initiator and responder LTK and CSRK within one file.
Since the LTK and 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.
This was the final motivation to promote the storage demo code to the API.
-rw-r--r-- | api/direct_bt/DirectBT.hpp | 1 | ||||
-rw-r--r-- | api/direct_bt/SMPKeyBin.hpp | 195 | ||||
-rw-r--r-- | examples/direct_bt_scanner10/dbt_scanner10.cpp | 188 | ||||
-rw-r--r-- | examples/java/DBTScanner10.java | 269 | ||||
-rw-r--r-- | java/org/direct_bt/SMPKeyBin.java | 393 | ||||
-rw-r--r-- | src/direct_bt/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/direct_bt/SMPKeyBin.cpp | 200 |
7 files changed, 849 insertions, 398 deletions
diff --git a/api/direct_bt/DirectBT.hpp b/api/direct_bt/DirectBT.hpp index 98755c49..8e7a4bba 100644 --- a/api/direct_bt/DirectBT.hpp +++ b/api/direct_bt/DirectBT.hpp @@ -48,5 +48,6 @@ #include "BTManager.hpp" +#include "SMPKeyBin.hpp" #endif /* DIRECTBT_HPP_ */ diff --git a/api/direct_bt/SMPKeyBin.hpp b/api/direct_bt/SMPKeyBin.hpp new file mode 100644 index 00000000..c86e4653 --- /dev/null +++ b/api/direct_bt/SMPKeyBin.hpp @@ -0,0 +1,195 @@ +/* + * 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. + */ + +#ifndef SMPKEYBIN_HPP_ +#define SMPKEYBIN_HPP_ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <fstream> +#include <iostream> + +#include "SMPTypes.hpp" + +namespace direct_bt { + +/** + * Storage for SMP keys including the required connection parameter. + * + * Storage for a device's BDAddressAndType, its security connection setup BTSecurityLevel + SMPIOCapability + * and optionally the initiator and responder SMPLongTermKeyInfo (LTK) and SMPSignatureResolvingKeyInfo (CSRK) within one file. + * <p> + * Since the SMPLongTermKeyInfo (LTK) and 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> + */ +class SMPKeyBin { + public: + constexpr static const uint16_t VERSION = (uint16_t)0b0101010101010101 + (uint16_t)1; // bitpattern + version + + private: + uint16_t version; // 2 + uint16_t size; // 2 + BDAddressAndType addrAndType; // 7 + BTSecurityLevel sec_level; // 1 + SMPIOCapability io_cap; // 1 + + SMPKeyType keys_init; // 1 + SMPKeyType keys_resp; // 1 + + SMPLongTermKeyInfo ltk_init; // 28 (optional) + SMPSignatureResolvingKeyInfo csrk_init; // 17 (optional) + + SMPLongTermKeyInfo ltk_resp; // 28 (optional) + SMPSignatureResolvingKeyInfo csrk_resp; // 17 (optional) + + // Min-Max: 15 - 105 bytes + + bool verbose; + + constexpr uint16_t calcSize() const { + uint16_t s = 0; + s += sizeof(version); + s += sizeof(size); + s += sizeof(addrAndType.address); + s += sizeof(addrAndType.type); + s += sizeof(sec_level); + s += sizeof(io_cap); + + s += sizeof(keys_init); + s += sizeof(keys_resp); + + if( hasLTKInit() ) { + s += sizeof(ltk_init); + } + if( hasCSRKInit() ) { + s += sizeof(csrk_init); + } + + if( hasLTKResp() ) { + s += sizeof(ltk_resp); + } + if( hasCSRKResp() ) { + s += sizeof(csrk_resp); + } + return s; + } + + public: + SMPKeyBin(const BDAddressAndType& addrAndType_, + const BTSecurityLevel sec_level_, const SMPIOCapability io_cap_) + : version(VERSION), size(0), + addrAndType(addrAndType_), sec_level(sec_level_), io_cap(io_cap_), + keys_init(SMPKeyType::NONE), keys_resp(SMPKeyType::NONE), + ltk_init(), csrk_init(), ltk_resp(), csrk_resp(), + verbose(false) + { size = calcSize(); } + + SMPKeyBin() + : version(VERSION), size(0), + addrAndType(), sec_level(BTSecurityLevel::UNSET), io_cap(SMPIOCapability::UNSET), + keys_init(SMPKeyType::NONE), keys_resp(SMPKeyType::NONE), + ltk_init(), csrk_init(), ltk_resp(), csrk_resp(), + verbose(false) + { size = calcSize(); } + + constexpr bool isVersionValid() const noexcept { return VERSION==version; } + constexpr uint16_t getVersion() const noexcept { return version;} + + constexpr bool isSizeValid() const noexcept { return calcSize() == size;} + constexpr uint16_t getSize() const noexcept { return size;} + + constexpr const BDAddressAndType& getAddrAndType() const noexcept { return addrAndType; } + constexpr BTSecurityLevel getSecLevel() const noexcept { return sec_level; } + constexpr SMPIOCapability getIOCap() const noexcept { return io_cap; } + + constexpr bool hasLTKInit() const noexcept { return ( SMPKeyType::ENC_KEY & keys_init ) != SMPKeyType::NONE; } + constexpr bool hasCSRKInit() const noexcept { return ( SMPKeyType::SIGN_KEY & keys_init ) != SMPKeyType::NONE; } + constexpr const SMPLongTermKeyInfo& getLTKInit() const noexcept { return ltk_init; } + constexpr const SMPSignatureResolvingKeyInfo& getCSRKInit() const noexcept { return csrk_init; } + void setLTKInit(const SMPLongTermKeyInfo& v) noexcept { + ltk_init = v; + keys_init |= SMPKeyType::ENC_KEY; + size = calcSize(); + } + void setCSRKInit(const SMPSignatureResolvingKeyInfo& v) noexcept { + csrk_init = v; + keys_init |= SMPKeyType::SIGN_KEY; + size = calcSize(); + } + + constexpr bool hasLTKResp() const noexcept { return ( SMPKeyType::ENC_KEY & keys_resp ) != SMPKeyType::NONE; } + constexpr bool hasCSRKResp() const noexcept { return ( SMPKeyType::SIGN_KEY & keys_resp ) != SMPKeyType::NONE; } + constexpr const SMPLongTermKeyInfo& getLTKResp() const noexcept { return ltk_resp; } + constexpr const SMPSignatureResolvingKeyInfo& getCSRKResp() const noexcept { return csrk_resp; } + void setLTKResp(const SMPLongTermKeyInfo& v) noexcept { + ltk_resp = v; + keys_resp |= SMPKeyType::ENC_KEY; + size = calcSize(); + } + void setCSRKResp(const SMPSignatureResolvingKeyInfo& v) noexcept { + csrk_resp = v; + keys_resp |= SMPKeyType::SIGN_KEY; + size = calcSize(); + } + + void setVerbose(bool v) noexcept { verbose = v; } + + constexpr bool isValid() const noexcept { + return isVersionValid() && isSizeValid() && + BTSecurityLevel::UNSET != sec_level && + SMPIOCapability::UNSET != io_cap && + ( !hasLTKInit() || ltk_init.isValid() ) && + ( !hasLTKResp() || ltk_resp.isValid() ); + } + + std::string toString() const noexcept; + + constexpr_cxx20 std::string getFileBasename() const noexcept { + return "bd_"+addrAndType.address.toString()+":"+std::to_string(number(addrAndType.type))+".smpkey.bin"; + } + static std::string getFileBasename(const BDAddressAndType& addrAndType_) { + return "bd_"+addrAndType_.address.toString()+":"+std::to_string(number(addrAndType_.type))+".smpkey.bin"; + } + + bool write(const std::string path, const std::string basename) const noexcept; + + bool write(const std::string path) const noexcept { + return write( path, getFileBasename() ); + } + + bool read(const std::string path, const std::string basename); + + bool read(const std::string path, const BDAddressAndType& addrAndType_) { + return read(path, getFileBasename(addrAndType_)); + } +}; + +} // namespace direct_bt + +#endif /* SMPKEYBIN_HPP_ */ diff --git a/examples/direct_bt_scanner10/dbt_scanner10.cpp b/examples/direct_bt_scanner10/dbt_scanner10.cpp index bc5a4ff2..40f437df 100644 --- a/examples/direct_bt_scanner10/dbt_scanner10.cpp +++ b/examples/direct_bt_scanner10/dbt_scanner10.cpp @@ -244,138 +244,6 @@ struct MyBTSecurityDetail { }; std::unordered_map<BDAddressAndType, MyBTSecurityDetail> MyBTSecurityDetail::devicesSecDetail; -struct MyLongTermKeyInfo { - constexpr static const uint16_t VERSION = (uint16_t)0b0101010101010101 + (uint16_t)0; // bitpattern + version - uint16_t version; // 2 - BDAddressAndType addrAndType; // 7 - BTSecurityLevel sec_level; // 1 - SMPIOCapability io_cap; // 1 - SMPLongTermKeyInfo smp_ltk; // 28 - - // 39 bytes - - MyLongTermKeyInfo(const BDAddressAndType& addrAndType_, - const BTSecurityLevel sec_level_, const SMPIOCapability io_cap_, - const SMPLongTermKeyInfo& smp_ltk_) - : version(VERSION), - addrAndType(addrAndType_), sec_level(sec_level_), io_cap(io_cap_), - smp_ltk(smp_ltk_) - {} - - MyLongTermKeyInfo() - : version(VERSION), - addrAndType(), sec_level(BTSecurityLevel::UNSET), io_cap(SMPIOCapability::UNSET), - smp_ltk() - { } - - constexpr_cxx20 std::string toString() const noexcept { - return "LTKInfo["+addrAndType.toString()+", sec "+getBTSecurityLevelString(sec_level)+ - ", io "+getSMPIOCapabilityString(io_cap)+ - ", "+smp_ltk.toString()+", ver["+jau::uint16HexString(version)+", ok "+std::to_string(VERSION==version)+"]]"; - } - - constexpr bool isValid() const noexcept { - return VERSION == version && BTSecurityLevel::ENC_ONLY <= sec_level && smp_ltk.isValid(); - } - - bool isResponder() const noexcept { return smp_ltk.isResponder(); } - - constexpr_cxx20 std::string getFilename() const noexcept { - const std::string role = isResponder() ? "resp" : "init"; - return "bt_sec."+addrAndType.address.toString()+":"+std::to_string(number(addrAndType.type))+".ltk."+role+".bin"; - } - static std::string getFilename(const BDAddressAndType& addrAndType_, const bool isResponder_) { - const std::string role = isResponder_ ? "resp" : "init"; - return "bt_sec."+addrAndType_.address.toString()+":"+std::to_string(number(addrAndType_.type))+".ltk."+role+".bin"; - } - - bool write(const std::string path) { - if( !isValid() ) { - fprintf(stderr, "****** WRITE LTK: Invalid (skipped) %s\n", toString().c_str()); - return false; - } - std::ofstream file(path+"/"+getFilename(), std::ios::binary | std::ios::trunc); - uint8_t buffer[2]; - jau::put_uint16(buffer, 0, version, true /* littleEndian */); - file.write((char*)buffer, sizeof(version)); - file.write((char*)&addrAndType.address, sizeof(addrAndType.address)); - file.write((char*)&addrAndType.type, sizeof(addrAndType.type)); - file.write((char*)&sec_level, sizeof(sec_level)); - file.write((char*)&io_cap, sizeof(io_cap)); - file.write((char*)&smp_ltk, sizeof(smp_ltk)); - file.close(); - fprintf(stderr, "****** WRITE LTK: Stored %s\n", toString().c_str()); - return true; - } - - bool read(const std::string path, const BDAddressAndType& addrAndType_, const bool isResponder_) { - const std::string filename = path+"/"+getFilename(addrAndType_, isResponder_); - std::ifstream file(filename, std::ios::binary); - if (!file.is_open() ) { - fprintf(stderr, "****** READ LTK failed: %s\n", filename.c_str()); - return false; - } - uint8_t buffer[2]; - file.read((char*)buffer, sizeof(version)); - version = jau::get_uint16(buffer, 0, true /* littleEndian */); - file.read((char*)&addrAndType.address, sizeof(addrAndType.address)); - file.read((char*)&addrAndType.type, sizeof(addrAndType.type)); - file.read((char*)&sec_level, sizeof(sec_level)); - file.read((char*)&io_cap, sizeof(io_cap)); - file.read((char*)&smp_ltk, sizeof(smp_ltk)); - file.close(); - addrAndType.clearHash(); - fprintf(stderr, "****** READ LTK: %s\n", toString().c_str()); - return isValid(); - } -}; - -struct MySignatureResolvingKeyInfo { - BDAddressAndType addrAndType; - SMPSignatureResolvingKeyInfo smp_csrk; - - bool isResponder() const noexcept { return smp_csrk.isResponder(); } - - constexpr_cxx20 std::string getFilename() const noexcept { - const std::string role = isResponder() ? "resp" : "init"; - return "bt_sec."+addrAndType.address.toString()+":"+std::to_string(number(addrAndType.type))+".csrk."+role+".bin"; - } - static std::string getFilename(const BDAddressAndType& addrAndType_, const bool isResponder_) { - const std::string role = isResponder_ ? "resp" : "init"; - return "bt_sec."+addrAndType_.address.toString()+":"+std::to_string(number(addrAndType_.type))+".csrk."+role+".bin"; - } - - bool write(const std::string path) { - std::ofstream file(path+"/"+getFilename(), std::ios::binary | std::ios::trunc); - file.write((char*)&addrAndType.address, sizeof(addrAndType.address)); - file.write((char*)&addrAndType.type, sizeof(addrAndType.type)); - file.write((char*)&smp_csrk, sizeof(smp_csrk)); - file.close(); - fprintf(stderr, "****** WRITE CSRK [%s, written]: %s\n", - addrAndType.toString().c_str(), - smp_csrk.toString().c_str()); - return true; - } - - bool read(const std::string path, const BDAddressAndType& addrAndType_, const bool isResponder_) { - const std::string filename = path+"/"+getFilename(addrAndType_, isResponder_); - std::ifstream file(filename, std::ios::binary); - if (!file.is_open() ) { - fprintf(stderr, "****** READ CSRK [%s] failed\n", filename.c_str()); - return false; - } - file.read((char*)&addrAndType.address, sizeof(addrAndType.address)); - file.read((char*)&addrAndType.type, sizeof(addrAndType.type)); - file.read((char*)&smp_csrk, sizeof(smp_csrk)); - file.close(); - addrAndType.clearHash(); - fprintf(stderr, "****** READ CSRK %s: %s\n", - addrAndType.toString().c_str(), - smp_csrk.toString().c_str()); - return true; - } -}; - class MyAdapterStatusListener : public AdapterStatusListener { void adapterSettingsChanged(BTAdapter &a, const AdapterSetting oldmask, const AdapterSetting newmask, @@ -598,23 +466,28 @@ static void connectDiscoveredDevice(std::shared_ptr<BTDevice> device) { device->getAdapter().stopDiscovery(); - bool useStoredLTKInfo = false; + bool useSMPKeyBin = false; { - MyLongTermKeyInfo my_ltk_resp; - MyLongTermKeyInfo my_ltk_init; - if( my_ltk_init.read(KEY_PATH, device->getAddressAndType(), false /* responder */) && - my_ltk_resp.read(KEY_PATH, device->getAddressAndType(), true /* responder */) && - device->setConnSecurity(my_ltk_init.sec_level, my_ltk_init.io_cap) && - HCIStatusCode::SUCCESS == device->setLongTermKeyInfo(my_ltk_init.smp_ltk) && - HCIStatusCode::SUCCESS == device->setLongTermKeyInfo(my_ltk_resp.smp_ltk) ) + SMPKeyBin smpKeyBin; + smpKeyBin.setVerbose( true ); + if( smpKeyBin.read(KEY_PATH, device->getAddressAndType()) && + device->setConnSecurity(smpKeyBin.getSecLevel(), smpKeyBin.getIOCap()) ) { - fprintf(stderr, "****** Connecting Device: Loaded LTKs from file successfully\n"); - fprintf(stderr, "- init %s\n", my_ltk_init.toString().c_str()); - fprintf(stderr, "- resp %s\n", my_ltk_resp.toString().c_str()); - useStoredLTKInfo = true; + useSMPKeyBin = true; + + if( smpKeyBin.hasLTKInit() && + HCIStatusCode::SUCCESS != device->setLongTermKeyInfo(smpKeyBin.getLTKInit()) ) + { + useSMPKeyBin = false; // error setting LTK init + } + if( smpKeyBin.hasLTKResp() && + HCIStatusCode::SUCCESS != device->setLongTermKeyInfo(smpKeyBin.getLTKResp()) ) + { + useSMPKeyBin = false; // error setting LTK resp + } } } - if( !useStoredLTKInfo ) { + if( !useSMPKeyBin ) { const MyBTSecurityDetail* sec = MyBTSecurityDetail::get(device->getAddressAndType()); if( nullptr != sec ) { if( sec->isSecurityAutoEnabled() ) { @@ -626,6 +499,8 @@ static void connectDiscoveredDevice(std::shared_ptr<BTDevice> device) { } } else { fprintf(stderr, "****** Connecting Device: No SecurityDetail for %s\n", device->getAddressAndType().toString().c_str()); + bool res = device->setConnSecurityAuto( SMPIOCapability::KEYBOARD_ONLY ); + fprintf(stderr, "****** Connecting Device: Setting SEC AUTO security detail w/ KEYBOARD_ONLY -> set OK %d\n", res); } } @@ -656,29 +531,24 @@ static void processReadyDevice(std::shared_ptr<BTDevice> device) { const SMPKeyType keys_resp = device->getAvailableSMPKeys(true /* responder */); const SMPKeyType keys_init = device->getAvailableSMPKeys(false /* responder */); + SMPKeyBin smpKeyBin(device->getAddressAndType(), + device->getConnSecurityLevel(), device->getConnIOCapability()); + smpKeyBin.setVerbose( true ); + if( ( SMPKeyType::ENC_KEY & keys_init ) != SMPKeyType::NONE ) { - MyLongTermKeyInfo my_ltk (device->getAddressAndType(), - device->getConnSecurityLevel(), device->getConnIOCapability(), - device->getLongTermKeyInfo(false /* responder */)); - my_ltk.write(KEY_PATH); + smpKeyBin.setLTKInit( device->getLongTermKeyInfo(false /* responder */) ); } if( ( SMPKeyType::ENC_KEY & keys_resp ) != SMPKeyType::NONE ) { - MyLongTermKeyInfo my_ltk (device->getAddressAndType(), - device->getConnSecurityLevel(), device->getConnIOCapability(), - device->getLongTermKeyInfo(true /* responder */)); - my_ltk.write(KEY_PATH); + smpKeyBin.setLTKResp( device->getLongTermKeyInfo(true /* responder */) ); } if( ( SMPKeyType::SIGN_KEY & keys_init ) != SMPKeyType::NONE ) { - MySignatureResolvingKeyInfo my_csrk { device->getAddressAndType(), - device->getSignatureResolvingKeyInfo(false /* responder */) }; - my_csrk.write(KEY_PATH); + smpKeyBin.setCSRKInit( device->getSignatureResolvingKeyInfo(false /* responder */) ); } if( ( SMPKeyType::SIGN_KEY & keys_resp ) != SMPKeyType::NONE ) { - MySignatureResolvingKeyInfo my_csrk { device->getAddressAndType(), - device->getSignatureResolvingKeyInfo(true /* responder */) }; - my_csrk.write(KEY_PATH); + smpKeyBin.setCSRKResp( device->getSignatureResolvingKeyInfo(true /* responder */) ); } + smpKeyBin.write(KEY_PATH); } } diff --git a/examples/java/DBTScanner10.java b/examples/java/DBTScanner10.java index d02b74ad..039cc6cc 100644 --- a/examples/java/DBTScanner10.java +++ b/examples/java/DBTScanner10.java @@ -23,12 +23,6 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -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 java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; @@ -66,10 +60,9 @@ import org.direct_bt.HCIStatusCode; import org.direct_bt.HCIWhitelistConnectType; import org.direct_bt.PairingMode; import org.direct_bt.SMPIOCapability; +import org.direct_bt.SMPKeyBin; import org.direct_bt.SMPKeyMask; -import org.direct_bt.SMPLongTermKeyInfo; import org.direct_bt.SMPPairingState; -import org.direct_bt.SMPSignatureResolvingKeyInfo; import org.direct_bt.ScanType; import jau.direct_bt.DBTManager; @@ -191,210 +184,6 @@ public class DBTScanner10 { static public String allToString() { return Arrays.toString( devicesSecDetail.values().toArray() ); } } - static public class MyLongTermKeyInfo { - static final short VERSION = (short)0b0101010101010101 + (short)0; // bitpattern + version - short version; // 2 - BDAddressAndType addrAndType; // 7 - BTSecurityLevel sec_level;; // 1 - SMPIOCapability io_cap; // 1 - SMPLongTermKeyInfo smp_ltk; // 28 - - // 39 bytes - - MyLongTermKeyInfo(final BDAddressAndType addrAndType_, - final BTSecurityLevel sec_level_, final SMPIOCapability io_cap_, - final SMPLongTermKeyInfo smp_ltk_) { - version = VERSION; - this.addrAndType = addrAndType_; - this.sec_level = sec_level_; - this.io_cap = io_cap_; - this.smp_ltk = smp_ltk_; - } - - MyLongTermKeyInfo() { - version = VERSION; - addrAndType = new BDAddressAndType(); - sec_level = BTSecurityLevel.UNSET; - io_cap = SMPIOCapability.UNSET; - smp_ltk = new SMPLongTermKeyInfo(); - } - - @Override - final public String toString() { - return "LTKInfo["+addrAndType+", sec "+sec_level+", io "+io_cap+ - ", "+smp_ltk+", ver[0x"+Integer.toHexString(version)+", ok "+(VERSION==version)+"]]"; - } - - final boolean isValid() { - return VERSION == version && BTSecurityLevel.ENC_ONLY.value <= sec_level.value && smp_ltk.isValid(); - } - - final public boolean isResponder() { return smp_ltk.isResponder(); } - - final public String getFilename() { - final String role = isResponder() ? "resp" : "init"; - return "bt_sec."+addrAndType.address.toString()+":"+addrAndType.type.value+".ltk."+role+".bin"; - } - static final public String getFilename(final BDAddressAndType addrAndType_, final boolean isResponder_) { - final String role = isResponder_ ? "resp" : "init"; - return "bt_sec."+addrAndType_.address.toString()+":"+addrAndType_.type.value+".ltk."+role+".bin"; - } - - final boolean write(final String path) { - if( !isValid() ) { - println("****** WRITE LTK: Invalid (skipped) "+toString()); - return false; - } - final File file = new File(path+"/"+getFilename()); - 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(addrAndType.address.b); - out.write(addrAndType.type.value); - out.write(sec_level.value); - out.write(io_cap.value); - final byte[] smp_ltk_b = new byte[SMPLongTermKeyInfo.byte_size]; - smp_ltk.getStream(smp_ltk_b, 0); - out.write(smp_ltk_b); - println("****** WRITE LTK: Stored "+toString()); - return true; - } catch (final Exception ex) { - println("****** WRITE LTK: Failed "+toString()+": "+ex.getMessage()); - ex.printStackTrace(); - } finally { - try { - if( null != out ) { - out.close(); - } - } catch (final IOException e) { - e.printStackTrace(); - } - } - return false; - } - - final boolean read(final String path, final BDAddressAndType addrAndType_, final boolean isResponder_) { - final String filename = path+"/"+getFilename(addrAndType_, isResponder_); - final File file = new File(filename); - InputStream in = null; - try { - final byte[] buffer = new byte[2 + 6 + 1 + 1 + 1 + SMPLongTermKeyInfo.byte_size]; - in = new FileInputStream(file); - final int read_count = in.read(buffer, 0, buffer.length); - if( read_count != buffer.length ) { - throw new IOException("Couldn't read "+buffer.length+" bytes, only "+read_count+" from "+filename); - } - int i=0; - version = (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++]); - smp_ltk.putStream(buffer, i++); - println("****** READ LTK: "+toString()); - return isValid(); - } catch (final Exception ex) { - println("****** READ LTK failed: "+filename+": "+ex.getMessage()); - } finally { - try { - if( null != in ) { - in.close(); - } - } catch (final IOException e) { - e.printStackTrace(); - } - } - return false; - } - } - - static public class MySignatureResolvingKeyInfo { - BDAddressAndType addrAndType; - SMPSignatureResolvingKeyInfo smp_csrk; - - MySignatureResolvingKeyInfo(final BDAddressAndType address_and_type, final SMPSignatureResolvingKeyInfo smp_csrk) { - this.addrAndType = address_and_type; - this.smp_csrk = smp_csrk; - } - - MySignatureResolvingKeyInfo() { - addrAndType = new BDAddressAndType(); - smp_csrk = new SMPSignatureResolvingKeyInfo(); - } - - final public boolean isResponder() { return smp_csrk.isResponder(); } - - final public String getFilename() { - final String role = isResponder() ? "resp" : "init"; - return "bt_sec."+addrAndType.address.toString()+":"+addrAndType.type.value+".csrk."+role+".bin"; - } - static final public String getFilename(final BDAddressAndType addrAndType_, final boolean isResponder_) { - final String role = isResponder_ ? "resp" : "init"; - return "bt_sec."+addrAndType_.address.toString()+":"+addrAndType_.type.value+".csrk."+role+".bin"; - } - - final boolean write(final String path) { - final File file = new File(path+"/"+getFilename()); - OutputStream out = null; - try { - file.delete(); // alternative to truncate, if existing - out = new FileOutputStream(file); - out.write(addrAndType.address.b); - out.write(addrAndType.type.value); - final byte[] smp_ltk_b = new byte[SMPSignatureResolvingKeyInfo.byte_size]; - smp_csrk.getStream(smp_ltk_b, 0); - out.write(smp_ltk_b); - println("****** WRITE CSRK ["+addrAndType+", written]: "+smp_csrk); - return true; - } catch (final Exception ex) { - println("****** WRITE CSRK "+addrAndType+" failed: "+ex.getMessage()); - ex.printStackTrace(); - } finally { - try { - if( null != out ) { - out.close(); - } - } catch (final IOException e) { - e.printStackTrace(); - } - } - return false; - } - - final boolean read(final String path, final BDAddressAndType addrAndType_, final boolean isResponder_) { - final String filename = path+"/"+getFilename(addrAndType_, isResponder_); - final File file = new File(filename); - InputStream in = null; - try { - final byte[] buffer = new byte[6 + 1 + SMPSignatureResolvingKeyInfo.byte_size]; - in = new FileInputStream(file); - final int read_count = in.read(buffer, 0, buffer.length); - if( read_count != buffer.length ) { - throw new IOException("Couldn't read "+buffer.length+" bytes, only "+read_count+" from "+filename); - } - addrAndType.address.putStream(buffer, 0); - addrAndType.type = BDAddressType.get(buffer[6]); - smp_csrk.putStream(buffer, 6+1); - println("****** READ CSRK "+addrAndType+": "+smp_csrk); - return true; - } catch (final Exception ex) { - println("****** READ CSRK ["+filename+"] failed: "+ex.getMessage()); - } finally { - try { - if( null != in ) { - in.close(); - } - } catch (final IOException e) { - e.printStackTrace(); - } - } - return false; - } - } - Collection<BDAddressAndType> devicesInProcessing = Collections.synchronizedCollection(new HashSet<>()); Collection<BDAddressAndType> devicesProcessed = Collections.synchronizedCollection(new HashSet<>()); @@ -574,23 +363,28 @@ public class DBTScanner10 { println("****** Connecting Device: stopDiscovery result "+r); } - boolean useStoredLTKInfo = false; + boolean useSMPKeyBin = false; { - final MyLongTermKeyInfo my_ltk_resp = new MyLongTermKeyInfo(); - final MyLongTermKeyInfo my_ltk_init = new MyLongTermKeyInfo(); - if( my_ltk_init.read(KEY_PATH, device.getAddressAndType(), false /* responder */) && - my_ltk_resp.read(KEY_PATH, device.getAddressAndType(), true /* responder */) && - device.setConnSecurity(my_ltk_init.sec_level, my_ltk_init.io_cap) && - HCIStatusCode.SUCCESS == device.setLongTermKeyInfo(my_ltk_init.smp_ltk) && - HCIStatusCode.SUCCESS == device.setLongTermKeyInfo(my_ltk_resp.smp_ltk) ) + final SMPKeyBin smpKeyBin = new SMPKeyBin(); + smpKeyBin.setVerbose( true ); + if( smpKeyBin.read(KEY_PATH, device.getAddressAndType()) && + device.setConnSecurity(smpKeyBin.getSecLevel(), smpKeyBin.getIOCap()) ) { - println("****** Connecting Device: Loaded LTKs from file successfully"); - println("- init "+my_ltk_init.toString()); - println("- resp "+my_ltk_resp.toString()); - useStoredLTKInfo = true; + useSMPKeyBin = true; + + if( smpKeyBin.hasLTKInit() && + HCIStatusCode.SUCCESS != device.setLongTermKeyInfo(smpKeyBin.getLTKInit()) ) + { + useSMPKeyBin = false; // error setting LTK init + } + if( smpKeyBin.hasLTKResp() && + HCIStatusCode.SUCCESS != device.setLongTermKeyInfo(smpKeyBin.getLTKResp()) ) + { + useSMPKeyBin = false; // error setting LTK resp + } } } - if( !useStoredLTKInfo ) { + if( !useSMPKeyBin ) { // Always reuse same sec setting if reusing LTK final MyBTSecurityDetail sec = MyBTSecurityDetail.get(device.getAddressAndType()); if( null != sec ) { @@ -603,6 +397,8 @@ public class DBTScanner10 { } } else { println("****** Connecting Device: No SecurityDetail for "+device.getAddressAndType()); + final boolean res = device.setConnSecurityAuto( SMPIOCapability.KEYBOARD_ONLY ); + println("****** Connecting Device: Setting SEC AUTO security detail w/ KEYBOARD_ONLY -> set OK "+res); } } @@ -660,29 +456,24 @@ public class DBTScanner10 { final SMPKeyMask keys_resp = device.getAvailableSMPKeys(true /* responder */); final SMPKeyMask keys_init = device.getAvailableSMPKeys(false /* responder */); + final SMPKeyBin smpKeyBin = new SMPKeyBin(device.getAddressAndType(), + device.getConnSecurityLevel(), device.getConnIOCapability()); + smpKeyBin.setVerbose( true ); + if( keys_init.isSet(SMPKeyMask.KeyType.ENC_KEY) ) { - final MyLongTermKeyInfo my_ltk = new MyLongTermKeyInfo(device.getAddressAndType(), - device.getConnSecurityLevel(), device.getConnIOCapability(), - device.getLongTermKeyInfo(false /* responder */)); - my_ltk.write(KEY_PATH); + smpKeyBin.setLTKInit( device.getLongTermKeyInfo(false /* responder */) ); } if( keys_resp.isSet(SMPKeyMask.KeyType.ENC_KEY) ) { - final MyLongTermKeyInfo my_ltk = new MyLongTermKeyInfo(device.getAddressAndType(), - device.getConnSecurityLevel(), device.getConnIOCapability(), - device.getLongTermKeyInfo(true /* responder */)); - my_ltk.write(KEY_PATH); + smpKeyBin.setLTKResp( device.getLongTermKeyInfo(true /* responder */) ); } if( keys_init.isSet(SMPKeyMask.KeyType.SIGN_KEY) ) { - final MySignatureResolvingKeyInfo my_csrk = new MySignatureResolvingKeyInfo(device.getAddressAndType(), - device.getSignatureResolvingKeyInfo(false /* responder */)); - my_csrk.write(KEY_PATH); + smpKeyBin.setCSRKInit( device.getSignatureResolvingKeyInfo(false /* responder */) ); } if( keys_resp.isSet(SMPKeyMask.KeyType.SIGN_KEY) ) { - final MySignatureResolvingKeyInfo my_csrk = new MySignatureResolvingKeyInfo(device.getAddressAndType(), - device.getSignatureResolvingKeyInfo(true /* responder */)); - my_csrk.write(KEY_PATH); + smpKeyBin.setCSRKResp( device.getSignatureResolvingKeyInfo(true /* responder */) ); } + smpKeyBin.write(KEY_PATH); } } 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; + } +}; diff --git a/src/direct_bt/CMakeLists.txt b/src/direct_bt/CMakeLists.txt index 89ff0588..dea50003 100644 --- a/src/direct_bt/CMakeLists.txt +++ b/src/direct_bt/CMakeLists.txt @@ -34,6 +34,7 @@ set (direct_bt_LIB_SRCS ${PROJECT_SOURCE_DIR}/src/direct_bt/MgmtTypes.cpp ${PROJECT_SOURCE_DIR}/src/direct_bt/SMPHandler.cpp ${PROJECT_SOURCE_DIR}/src/direct_bt/SMPTypes.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/SMPKeyBin.cpp ${PROJECT_SOURCE_DIR}/src/direct_bt/UUID.cpp # autogenerated files ${CMAKE_CURRENT_BINARY_DIR}/../version.c diff --git a/src/direct_bt/SMPKeyBin.cpp b/src/direct_bt/SMPKeyBin.cpp new file mode 100644 index 00000000..a726e118 --- /dev/null +++ b/src/direct_bt/SMPKeyBin.cpp @@ -0,0 +1,200 @@ +/* + * 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. + */ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <cstdio> + +#include "SMPKeyBin.hpp" + +using namespace direct_bt; + +std::string SMPKeyBin::toString() const noexcept { + std::string res = "SMPKeyBin["+addrAndType.toString()+", sec "+getBTSecurityLevelString(sec_level)+ + ", io "+getSMPIOCapabilityString(io_cap)+ + ", "; + if( isVersionValid() ) { + res += "Init["; + if( hasLTKInit() ) { + res += ltk_init.toString(); + } + if( hasCSRKInit() ) { + if( hasLTKInit() ) { + res += ", "; + } + res += csrk_init.toString(); + } + res += "], Resp["; + if( hasLTKResp() ) { + res += ltk_resp.toString(); + } + if( hasCSRKResp() ) { + if( hasLTKResp() ) { + res += ", "; + } + res += csrk_resp.toString(); + } + res += "], "; + } + res += "ver["+jau::uint16HexString(version)+", ok "+std::to_string( isVersionValid() )+ + "], size["+std::to_string(size); + if( verbose ) { + res += ", calc "+std::to_string( calcSize() ); + } + res += ", valid "+std::to_string( isSizeValid() )+ + "], valid "+std::to_string( isValid() )+"]"; + return res; +} + +bool SMPKeyBin::write(const std::string path, const std::string basename) const noexcept { + if( !isValid() ) { + if( verbose ) { + fprintf(stderr, "****** WRITE SMPKeyBin: Invalid (skipped) %s\n", toString().c_str()); + } + return false; + } + const std::string fname = path+"/"+basename; + std::ofstream file(fname, std::ios::binary | std::ios::trunc); + uint8_t buffer[2]; + + jau::put_uint16(buffer, 0, version, true /* littleEndian */); + file.write((char*)buffer, sizeof(version)); + + jau::put_uint16(buffer, 0, size, true /* littleEndian */); + file.write((char*)buffer, sizeof(size)); + + file.write((char*)&addrAndType.address, sizeof(addrAndType.address)); + file.write((char*)&addrAndType.type, sizeof(addrAndType.type)); + file.write((char*)&sec_level, sizeof(sec_level)); + file.write((char*)&io_cap, sizeof(io_cap)); + + file.write((char*)&keys_init, sizeof(keys_init)); + file.write((char*)&keys_resp, sizeof(keys_resp)); + + if( hasLTKInit() ) { + file.write((char*)<k_init, sizeof(ltk_init)); + } + if( hasCSRKInit() ) { + file.write((char*)&csrk_init, sizeof(csrk_init)); + } + + if( hasLTKResp() ) { + file.write((char*)<k_resp, sizeof(ltk_resp)); + } + if( hasCSRKResp() ) { + file.write((char*)&csrk_resp, sizeof(csrk_resp)); + } + + file.close(); + if( verbose ) { + fprintf(stderr, "****** WRITE SMPKeyBin: %s: %s\n", fname.c_str(), toString().c_str()); + } + return true; +} + +bool SMPKeyBin::read(const std::string path, const std::string basename) { + const std::string fname = path+"/"+basename; + std::ifstream file(fname, std::ios::binary); + if (!file.is_open() ) { + if( verbose ) { + fprintf(stderr, "****** READ SMPKeyBin failed: %s\n", fname.c_str()); + } + return false; + } + bool err = false; + uint8_t buffer[2]; + + file.read((char*)buffer, sizeof(version)); + version = jau::get_uint16(buffer, 0, true /* littleEndian */); + err = file.fail(); + + if( !err ) { + file.read((char*)buffer, sizeof(size)); + size = jau::get_uint16(buffer, 0, true /* littleEndian */); + err = file.fail(); + } + uint16_t remaining = size - sizeof(version) - sizeof(size); + + if( !err && 11 <= remaining ) { + file.read((char*)&addrAndType.address, sizeof(addrAndType.address)); + file.read((char*)&addrAndType.type, sizeof(addrAndType.type)); + file.read((char*)&sec_level, sizeof(sec_level)); + file.read((char*)&io_cap, sizeof(io_cap)); + + file.read((char*)&keys_init, sizeof(keys_init)); + file.read((char*)&keys_resp, sizeof(keys_resp)); + remaining -= 11; + err = file.fail(); + } else { + err = true; + } + addrAndType.clearHash(); + + if( !err && hasLTKInit() ) { + if( sizeof(ltk_init) <= remaining ) { + file.read((char*)<k_init, sizeof(ltk_init)); + remaining -= sizeof(ltk_init); + err = file.fail(); + } else { + err = true; + } + } + if( !err && hasCSRKInit() ) { + if( sizeof(csrk_init) <= remaining ) { + file.read((char*)&csrk_init, sizeof(csrk_init)); + remaining -= sizeof(csrk_init); + err = file.fail(); + } else { + err = true; + } + } + + if( !err && hasLTKResp() ) { + if( sizeof(ltk_resp) <= remaining ) { + file.read((char*)<k_resp, sizeof(ltk_resp)); + remaining -= sizeof(ltk_resp); + err = file.fail(); + } else { + err = true; + } + } + if( !err && hasCSRKResp() ) { + if( sizeof(csrk_resp) <= remaining ) { + file.read((char*)&csrk_resp, sizeof(csrk_resp)); + remaining -= sizeof(csrk_resp); + err = file.fail(); + } else { + err = true; + } + } + + file.close(); + if( verbose ) { + fprintf(stderr, "****** READ SMPKeyBin: %s: %s, remaining %u\n", + fname.c_str(), toString().c_str(), remaining); + } + return isValid(); +} |