/*
* Author: Sven Gothel
* 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
#include
#include
#include
#include "SMPTypes.hpp"
#include "HCITypes.hpp"
namespace direct_bt {
class BTDevice; // forward
class BTAdapter; // forward
/** \addtogroup DBTUserAPI
*
* @{
*/
/**
* Storage for SMP keys including required connection parameter per local adapter and remote device.
*
* File format version 5.
*
* Storage for a device's BDAddressAndType, its security connection setup ::BTSecurityLevel + ::SMPIOCapability
* and optionally the initiator and responder SMPLongTermKeyInfo (LTK), SMPSignatureResolvingKeyInfo (CSRK)
* and SMPLinkKeyInfo (LK) within one file.
*
* Since the SMPLongTermKeyInfo (LTK), SMPSignatureResolvingKeyInfo (CSRK)
* and SMPLinkKeyInfo (LK)
* are optionally set depending on their availability per initiator and responder,
* implementation supports mixed mode for certain devices.
* E.g. LTK responder key only etc.
*
*
* Data is stored in endian::little format, native to Bluetooth.
*
*
* Filename as retrieved by SMPKeyBin::getFileBasename()
* has the following form `bd_010203040506_C026DA01DAB11.key`:
*
* - {@code 'bd_'} prefix
* - {@code '010203040506'} local {@link EUI48} local adapter address
* - {@code '_'} separator
* - {@code 'C026DA01DAB1'} remote {@link EUI48} remote device address
* - {@code '1'} {@link BDAddressType}
* - {@code '.key'} suffix
*
*
* @see BTDevice::setSMPKeyBin()
* @see BTDevice::uploadKeys()
*/
class SMPKeyBin {
public:
constexpr static const uint16_t VERSION = (uint16_t)0b0101010101010101U + (uint16_t)6U; // bitpattern + version
private:
uint16_t version; // 2
uint16_t size; // 2
uint64_t ts_creation_sec; // 8
BTRole localRole; // 1
BDAddressAndType localAddress; // 7
BDAddressAndType remoteAddress; // 7
BTSecurityLevel sec_level; // 1
SMPIOCapability io_cap; // 1
SMPKeyType keys_init; // 1
SMPKeyType keys_resp; // 1 -> 31
SMPLongTermKey ltk_init; // 28 (optional)
SMPIdentityResolvingKey irk_init; // 23 (optional)
SMPSignatureResolvingKey csrk_init; // 17 (optional)
SMPLinkKey lk_init; // 19 (optional)
SMPLongTermKey ltk_resp; // 28 (optional)
SMPIdentityResolvingKey irk_resp; // 23 (optional)
SMPSignatureResolvingKey csrk_resp; // 17 (optional)
SMPLinkKey lk_resp; // 19 (optional)
constexpr static const int byte_size_max = 205;
constexpr static const int byte_size_min = 31;
bool verbose;
constexpr uint16_t calcSize() const {
uint16_t s = 0;
s += sizeof(version);
s += sizeof(size);
s += sizeof(ts_creation_sec);
s += sizeof(localRole);
s += sizeof(localAddress.address);
s += sizeof(localAddress.type);
s += sizeof(remoteAddress.address);
s += sizeof(remoteAddress.type);
s += sizeof(sec_level);
s += sizeof(io_cap);
s += sizeof(keys_init);
s += sizeof(keys_resp);
if( hasLTKInit() ) {
s += sizeof(ltk_init);
}
if( hasIRKInit() ) {
s += sizeof(irk_init);
}
if( hasCSRKInit() ) {
s += sizeof(csrk_init);
}
if( hasLKInit() ) {
s += sizeof(lk_init);
}
if( hasLTKResp() ) {
s += sizeof(ltk_resp);
}
if( hasIRKResp() ) {
s += sizeof(irk_resp);
}
if( hasCSRKResp() ) {
s += sizeof(csrk_resp);
}
if( hasLKResp() ) {
s += sizeof(lk_resp);
}
return s;
}
static bool remove_impl(const std::string& fname);
public:
/**
* Create a new SMPKeyBin instance based upon given BTDevice's
* BTSecurityLevel, SMPPairingState, PairingMode and LTK keys.
*
* Returned SMPKeyBin shall be tested if valid via SMPKeyBin::isValid(),
* whether the retrieved data from BTDevice is consistent and hence
* having BTDevice is a well connected state.
*
* @param device the BTDevice from which all required data is derived
* @return a valid SMPKeyBin instance if properly connected, otherwise an invalid instance.
* @see BTDevice
* @see isValid()
*/
static SMPKeyBin create(const BTDevice& device);
/**
* Create a new SMPKeyBin instance on the fly based upon given BTDevice's
* BTSecurityLevel, SMPPairingState, PairingMode and LTK keys.
* If valid, instance is stored to a file denoted by `path` and `BTDevice::getAddressAndType()`.
*
* If BTDevice::getPairingMode() is PairingMode::PRE_PAIRED, an existing file will not be overwritten.
* Otherwise, a new key is assumed and an existing file shall be overwritten.
*
* Method returns `false` if resulting SMPKeyBin is not SMPKeyBin::isValid().
* Otherwise, method returns the SMPKeyBin::write() result.
*
* @param device the BTDevice from which all required data is derived
* @param path the path for the stored SMPKeyBin file.
* @param verbose_ set to true to have detailed write processing logged to stderr, otherwise false
* @return `true` if file has been successfully written, otherwise `false`.
* @see BTDevice
* @see Create()
* @see write()
* @see isValid()
*/
static bool createAndWrite(const BTDevice& device, const std::string& path, const bool verbose_);
/**
* Create a new SMPKeyBin instance based upon stored file denoted by `fname`.
*
* Returned SMPKeyBin shall be tested if valid via SMPKeyBin::isValid(),
* whether the read() operation was successful and data is consistent.
*
* If file is invalid, it is removed.
*
* @param fname full path of the stored SMPKeyBin file.
* @param verbose_ set to true to have detailed read processing logged to stderr, otherwise false
* @return valid SMPKeyBin instance if file exist and read successfully, otherwise invalid SMPKeyBin instance.
* @see isValid()
* @see read()
*/
static SMPKeyBin read(const std::string& fname, const bool verbose_) {
SMPKeyBin smpKeyBin;
smpKeyBin.setVerbose( verbose_ );
smpKeyBin.read( fname ); // read failure -> !isValid()
return smpKeyBin;
}
/**
* Create a new SMPKeyBin instance based upon the given BTDevice's matching filename,
* see SMPKeyBin API doc for filename naming scheme.
*
* Returned SMPKeyBin shall be tested if valid via SMPKeyBin::isValid(),
* whether the read() operation was successful and data is consistent.
*
* If file is invalid, it is removed.
*
* @param path directory for the stored SMPKeyBin file.
* @param device BTDevice used to derive the filename, see getFilename()
* @param verbose_ set to true to have detailed read processing logged to stderr, otherwise false
* @return valid SMPKeyBin instance if file exist and read successfully, otherwise invalid SMPKeyBin instance.
* @see getFilename()
* @see isValid()
* @see read()
*/
static SMPKeyBin read(const std::string& path, const BTDevice& device, const bool verbose_) {
return read(getFilename(path, device), verbose_);
}
static std::vector readAll(const std::string& dname, const bool verbose_);
static std::vector readAllForLocalAdapter(const BDAddressAndType& localAddress, const std::string& dname, const bool verbose_);
SMPKeyBin(BTRole localRole_, BDAddressAndType localAddress_,
BDAddressAndType remoteAddress_,
const BTSecurityLevel sec_level_, const SMPIOCapability io_cap_)
: version(VERSION), size(0),
ts_creation_sec( jau::getWallClockSeconds() ),
localRole(localRole_),
localAddress(std::move(localAddress_)), remoteAddress(std::move(remoteAddress_)),
sec_level(sec_level_), io_cap(io_cap_),
keys_init(SMPKeyType::NONE), keys_resp(SMPKeyType::NONE),
ltk_init(), irk_init(), csrk_init(), lk_init(),
ltk_resp(), irk_resp(), csrk_resp(), lk_resp(),
verbose(false)
{ size = calcSize(); }
SMPKeyBin()
: version(VERSION), size(0),
ts_creation_sec(0),
localRole(BTRole::None),
localAddress(), remoteAddress(),
sec_level(BTSecurityLevel::UNSET), io_cap(SMPIOCapability::UNSET),
keys_init(SMPKeyType::NONE), keys_resp(SMPKeyType::NONE),
ltk_init(), irk_init(), csrk_init(), lk_init(),
ltk_resp(), irk_resp(), csrk_resp(), lk_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;}
/** Returns the creation timestamp in seconds since Unix epoch */
constexpr uint64_t getCreationTime() const noexcept { return ts_creation_sec; }
/** Return the local adapter BTRole. */
constexpr BTRole getLocalRole() const noexcept { return localRole; }
/** Return the local adapter address. */
constexpr const BDAddressAndType& getLocalAddrAndType() const noexcept { return localAddress; }
/** Return the remote device address. */
constexpr const BDAddressAndType& getRemoteAddrAndType() const noexcept { return remoteAddress; }
/** Return whether Secure Connection (SC) is being used via LTK keys. */
constexpr bool uses_SC() const noexcept {
return ( hasLTKInit() && SMPLongTermKey::Property::NONE != ( getLTKInit().properties & SMPLongTermKey::Property::SC ) ) ||
( hasLTKResp() && SMPLongTermKey::Property::NONE != ( getLTKResp().properties & SMPLongTermKey::Property::SC ) );
}
constexpr BTSecurityLevel getSecLevel() const noexcept { return sec_level; }
constexpr SMPIOCapability getIOCap() const noexcept { return io_cap; }
constexpr bool hasLTKInit() const noexcept { return is_set(keys_init, SMPKeyType::ENC_KEY); }
constexpr bool hasIRKInit() const noexcept { return is_set(keys_init, SMPKeyType::ID_KEY); }
constexpr bool hasCSRKInit() const noexcept { return is_set(keys_init, SMPKeyType::SIGN_KEY); }
constexpr bool hasLKInit() const noexcept { return is_set(keys_init, SMPKeyType::LINK_KEY); }
constexpr const SMPLongTermKey& getLTKInit() const noexcept { return ltk_init; }
constexpr const SMPIdentityResolvingKey& getIRKInit() const noexcept { return irk_init; }
constexpr const SMPSignatureResolvingKey& getCSRKInit() const noexcept { return csrk_init; }
constexpr const SMPLinkKey& getLKInit() const noexcept { return lk_init; }
void setLTKInit(const SMPLongTermKey& v) noexcept {
ltk_init = v;
keys_init |= SMPKeyType::ENC_KEY;
size = calcSize();
}
void setIRKInit(const SMPIdentityResolvingKey& v) noexcept {
irk_init = v;
keys_init |= SMPKeyType::ID_KEY;
size = calcSize();
}
void setCSRKInit(const SMPSignatureResolvingKey& v) noexcept {
csrk_init = v;
keys_init |= SMPKeyType::SIGN_KEY;
size = calcSize();
}
void setLKInit(const SMPLinkKey& v) noexcept {
lk_init = v;
keys_init |= SMPKeyType::LINK_KEY;
size = calcSize();
}
constexpr bool hasLTKResp() const noexcept { return is_set(keys_resp, SMPKeyType::ENC_KEY); }
constexpr bool hasIRKResp() const noexcept { return is_set(keys_resp, SMPKeyType::ID_KEY); }
constexpr bool hasCSRKResp() const noexcept { return is_set(keys_resp, SMPKeyType::SIGN_KEY); }
constexpr bool hasLKResp() const noexcept { return is_set(keys_resp, SMPKeyType::LINK_KEY); }
constexpr const SMPLongTermKey& getLTKResp() const noexcept { return ltk_resp; }
constexpr const SMPIdentityResolvingKey& getIRKResp() const noexcept { return irk_resp; }
constexpr const SMPSignatureResolvingKey& getCSRKResp() const noexcept { return csrk_resp; }
constexpr const SMPLinkKey& getLKResp() const noexcept { return lk_resp; }
void setLTKResp(const SMPLongTermKey& v) noexcept {
ltk_resp = v;
keys_resp |= SMPKeyType::ENC_KEY;
size = calcSize();
}
void setIRKResp(const SMPIdentityResolvingKey& v) noexcept {
irk_resp = v;
keys_resp |= SMPKeyType::ID_KEY;
size = calcSize();
}
void setCSRKResp(const SMPSignatureResolvingKey& v) noexcept {
csrk_resp = v;
keys_resp |= SMPKeyType::SIGN_KEY;
size = calcSize();
}
void setLKResp(const SMPLinkKey& v) noexcept {
lk_resp = v;
keys_resp |= SMPKeyType::LINK_KEY;
size = calcSize();
}
void setVerbose(bool v) noexcept { verbose = v; }
constexpr bool getVerbose() const noexcept { return verbose; }
/**
* Returns `true` if
*
* isVersionValid() && isSizeValid() &&
* not BTSecurityLevel::UNSET &&
* not SMPIOCapability::UNSET &&
* has valid LTK, if at all
*
*/
constexpr bool isValid() const noexcept {
// local_is_responder == true: responder's IRK info (LL slave), else the initiator's (LL master)
const bool local_is_responder = BTRole::Slave == localRole;
const BDAddressAndType& responderAddress = local_is_responder ? localAddress : remoteAddress;
const BDAddressAndType& initiatorAddress = local_is_responder ? remoteAddress : localAddress;
return isVersionValid() && isSizeValid() &&
BTSecurityLevel::UNSET != sec_level &&
SMPIOCapability::UNSET != io_cap &&
( !hasLTKInit() || ltk_init.isValid() ) &&
( !hasLTKResp() || ltk_resp.isValid() ) &&
( !hasLKInit() || lk_init.isValid() ) &&
( !hasLKResp() || lk_resp.isValid() ) &&
( !hasIRKInit() || irk_init.id_address == initiatorAddress.address ) &&
( !hasIRKResp() || irk_resp.id_address == responderAddress.address );
}
std::string toString() const noexcept;
/**
* Returns the base filename, see SMPKeyBin API doc for naming scheme.
*/
std::string getFileBasename() const noexcept;
/**
* Returns the base filename, see SMPKeyBin API doc for naming scheme.
*/
static std::string getFileBasename(const BDAddressAndType& localAddress_, const BDAddressAndType& remoteAddress_) noexcept;
static std::string getFilename(const std::string& path, const BDAddressAndType& localAddress_, const BDAddressAndType& remoteAddress_) noexcept {
return path + "/" + getFileBasename(localAddress_, remoteAddress_);
}
static std::string getFilename(const std::string& path, const BTDevice& remoteDevice) noexcept ;
static bool remove(const std::string& path, const BDAddressAndType& localAddress_, const BDAddressAndType& remoteAddress_) {
return remove_impl( getFilename(path, localAddress_, remoteAddress_) );
}
static bool remove(const std::string& path, const BTDevice& remoteDevice);
std::string getFilename(const std::string& path) const noexcept {
return getFilename(path, localAddress, remoteAddress);
}
bool remove(const std::string& path) {
return remove_impl( getFilename(path) );
}
bool write(const std::string& path, const bool overwrite) const noexcept;
bool read(const std::string& fname);
};
/**@}*/
} // namespace direct_bt
#endif /* SMPKEYBIN_HPP_ */