mirror of
https://github.com/thelsing/knx.git
synced 2025-09-23 17:51:00 +02:00
1278 lines
47 KiB
C++
1278 lines
47 KiB
C++
#include "config.h"
|
|
#ifdef USE_DATASECURE
|
|
|
|
#include "secure_application_layer.h"
|
|
#include "transport_layer.h"
|
|
#include "cemi_frame.h"
|
|
#include "association_table_object.h"
|
|
#include "address_table_object.h"
|
|
#include "security_interface_object.h"
|
|
#include "device_object.h"
|
|
#include "apdu.h"
|
|
#include "bau.h"
|
|
#include "string.h"
|
|
#include "bits.h"
|
|
|
|
// Select what cipher modes to include. We need AES128-CBC and AES128-CTR modes.
|
|
#define CBC 1
|
|
#define CTR 1
|
|
#define ECB 0
|
|
#include "aes.hpp"
|
|
|
|
static constexpr uint8_t kSecureDataPdu = 0;
|
|
static constexpr uint8_t kSecureSyncRequest = 2;
|
|
static constexpr uint8_t kSecureSyncResponse = 3;
|
|
|
|
SecureApplicationLayer::SecureApplicationLayer(DeviceObject &deviceObj, SecurityInterfaceObject &secIfObj, BusAccessUnit& bau):
|
|
ApplicationLayer(bau),
|
|
_secIfObj(secIfObj),
|
|
_deviceObj(deviceObj)
|
|
{
|
|
}
|
|
|
|
void SecureApplicationLayer::groupAddressTable(AddressTableObject &addrTable)
|
|
{
|
|
_addrTab = &addrTable;
|
|
}
|
|
|
|
/* from transport layer */
|
|
|
|
void SecureApplicationLayer::dataGroupIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu)
|
|
{
|
|
if (_addrTab == nullptr)
|
|
return;
|
|
|
|
println("dataGroupIndication");
|
|
|
|
if (apdu.type() == SecureService)
|
|
{
|
|
// Will be filled in by decodeSecureApdu()
|
|
SecurityControl secCtrl;
|
|
|
|
// Decrypt secure APDU
|
|
// Somehow ugly that we need to know the size in advance here at this point
|
|
uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
|
|
CemiFrame plainFrame(plainApduLength);
|
|
// Decrypt secure APDU
|
|
if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
|
|
{
|
|
// Process decrypted inner APDU
|
|
ApplicationLayer::dataGroupIndication(hopType, priority, tsap, plainFrame.apdu(), secCtrl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataGroupIndication(hopType, priority, tsap, apdu);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataGroupConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status)
|
|
{
|
|
println("dataGroupConfirm");
|
|
|
|
if (apdu.type() == SecureService)
|
|
{
|
|
// We do not care about confirmations of our sync communication
|
|
if (isSyncService(apdu))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Will be filled in by decodeSecureApdu()
|
|
SecurityControl secCtrl;
|
|
|
|
// Decrypt secure APDU
|
|
// Somehow ugly that we need to know the size in advance here at this point
|
|
uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
|
|
CemiFrame plainFrame(plainApduLength);
|
|
// Decrypt secure APDU
|
|
if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
|
|
{
|
|
// Process decrypted inner APDU
|
|
ApplicationLayer::dataGroupConfirm(ack, hopType, priority, tsap, plainFrame.apdu(), secCtrl, status);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataGroupConfirm(ack, hopType, priority, tsap, apdu, status);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu)
|
|
{
|
|
println("dataBroadcastIndication");
|
|
|
|
if (apdu.type() == SecureService)
|
|
{
|
|
// Will be filled in by decodeSecureApdu()
|
|
SecurityControl secCtrl;
|
|
|
|
// Decrypt secure APDU
|
|
// Somehow ugly that we need to know the size in advance here at this point
|
|
uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
|
|
CemiFrame plainFrame(plainApduLength);
|
|
// Decrypt secure APDU
|
|
if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
|
|
{
|
|
// Process decrypted inner APDU
|
|
ApplicationLayer::dataBroadcastIndication(hopType, priority, source, plainFrame.apdu(), secCtrl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataBroadcastIndication(hopType, priority, source, apdu);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataBroadcastConfirm(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, bool status)
|
|
{
|
|
println("dataBroadcastConfirm");
|
|
|
|
if (apdu.type() == SecureService)
|
|
{
|
|
// We do not care about confirmations of our sync communication
|
|
if (isSyncService(apdu))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Will be filled in by decodeSecureApdu()
|
|
SecurityControl secCtrl;
|
|
|
|
// Decrypt secure APDU
|
|
// Somehow ugly that we need to know the size in advance here at this point
|
|
uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
|
|
CemiFrame plainFrame(plainApduLength);
|
|
// Decrypt secure APDU
|
|
if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
|
|
{
|
|
// Process decrypted inner APDU
|
|
ApplicationLayer::dataBroadcastConfirm(ack, hopType, priority, plainFrame.apdu(), secCtrl, status);
|
|
}
|
|
return;
|
|
}
|
|
ApplicationLayer::dataBroadcastConfirm(ack, hopType, priority, apdu, status);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu)
|
|
{
|
|
println("dataSystemBroadcastIndication");
|
|
|
|
if (apdu.type() == SecureService)
|
|
{
|
|
// Will be filled in by decodeSecureApdu()
|
|
SecurityControl secCtrl;
|
|
|
|
// Decrypt secure APDU
|
|
// Somehow ugly that we need to know the size in advance here at this point
|
|
uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
|
|
CemiFrame plainFrame(plainApduLength);
|
|
// Decrypt secure APDU
|
|
if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
|
|
{
|
|
// Process decrypted inner APDU
|
|
ApplicationLayer::dataSystemBroadcastIndication(hopType, priority, source, plainFrame.apdu(), secCtrl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataSystemBroadcastIndication(hopType, priority, source, apdu);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, bool status)
|
|
{
|
|
println("dataSystemBroadcastConfirm");
|
|
|
|
if (apdu.type() == SecureService)
|
|
{
|
|
// We do not care about confirmations of our sync communication
|
|
if (isSyncService(apdu))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Will be filled in by decodeSecureApdu()
|
|
SecurityControl secCtrl;
|
|
|
|
// Decrypt secure APDU
|
|
// Somehow ugly that we need to know the size in advance here at this point
|
|
uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
|
|
CemiFrame plainFrame(plainApduLength);
|
|
// Decrypt secure APDU
|
|
if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
|
|
{
|
|
// Process decrypted inner APDU
|
|
ApplicationLayer::dataSystemBroadcastConfirm(hopType, priority, plainFrame.apdu(), secCtrl, status);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataSystemBroadcastConfirm(hopType, priority, apdu, status);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataIndividualIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu)
|
|
{
|
|
println("dataIndividualIndication");
|
|
|
|
if (apdu.type() == SecureService)
|
|
{
|
|
// Will be filled in by decodeSecureApdu()
|
|
SecurityControl secCtrl;
|
|
|
|
// Decrypt secure APDU
|
|
// Somehow ugly that we need to know the size in advance here at this point
|
|
uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
|
|
CemiFrame plainFrame(plainApduLength);
|
|
// Decrypt secure APDU
|
|
if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
|
|
{
|
|
// Process decrypted inner APDU
|
|
ApplicationLayer::dataIndividualIndication(hopType, priority, source, plainFrame.apdu(), secCtrl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataIndividualIndication(hopType, priority, source, apdu);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataIndividualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t source, APDU& apdu, bool status)
|
|
{
|
|
println("dataIndividualConfirm");
|
|
|
|
if (apdu.type() == SecureService)
|
|
{
|
|
// We do not care about confirmations of our sync communication
|
|
if (isSyncService(apdu))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Will be filled in by decodeSecureApdu()
|
|
SecurityControl secCtrl;
|
|
|
|
// Decrypt secure APDU
|
|
// Somehow ugly that we need to know the size in advance here at this point
|
|
uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
|
|
CemiFrame plainFrame(plainApduLength);
|
|
// Decrypt secure APDU
|
|
if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
|
|
{
|
|
// Process decrypted inner APDU
|
|
ApplicationLayer::dataIndividualConfirm(ack, hopType, priority, source, apdu, secCtrl, status);
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ApplicationLayer::dataIndividualConfirm(ack, hopType, priority, source, apdu, status);
|
|
}
|
|
}
|
|
|
|
void SecureApplicationLayer::dataConnectedIndication(Priority priority, uint16_t tsap, APDU& apdu)
|
|
{
|
|
println("dataConnectedIndication");
|
|
|
|
if (apdu.type() == SecureService)
|
|
{
|
|
// Will be filled in by decodeSecureApdu()
|
|
SecurityControl secCtrl;
|
|
|
|
// Decrypt secure APDU
|
|
// Somehow ugly that we need to know the size in advance here at this point
|
|
uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
|
|
CemiFrame plainFrame(plainApduLength);
|
|
// Decrypt secure APDU
|
|
if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
|
|
{
|
|
// Process decrypted inner APDU
|
|
ApplicationLayer::dataConnectedIndication(priority, tsap, plainFrame.apdu(), secCtrl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataConnectedIndication(priority, tsap, apdu);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataConnectedConfirm(uint16_t tsap)
|
|
{
|
|
// Just the confirmation issued by the transport layer in case of T_DATA_CONNECTED
|
|
ApplicationLayer::dataConnectedConfirm(tsap);
|
|
}
|
|
|
|
/* to transport layer */
|
|
|
|
void SecureApplicationLayer::dataGroupRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl)
|
|
{
|
|
if (_addrTab == nullptr)
|
|
return;
|
|
|
|
println("dataGroupRequest");
|
|
|
|
if (secCtrl.dataSecurity != DataSecurity::None)
|
|
{
|
|
apdu.frame().sourceAddress(_deviceObj.individualAddress());
|
|
apdu.frame().destinationAddress(_addrTab->getGroupAddress(tsap));
|
|
apdu.frame().addressType(GroupAddress);
|
|
|
|
uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4
|
|
CemiFrame secureFrame(secureApduLength);
|
|
// create secure APDU
|
|
if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl))
|
|
{
|
|
ApplicationLayer::dataGroupRequest(ack, hopType, priority, tsap, secureFrame.apdu(), secCtrl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataGroupRequest(ack, hopType, priority, tsap, apdu, secCtrl);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl)
|
|
{
|
|
println("dataBroadcastRequest");
|
|
|
|
if (secCtrl.dataSecurity != DataSecurity::None)
|
|
{
|
|
apdu.frame().sourceAddress(_deviceObj.individualAddress());
|
|
apdu.frame().destinationAddress(0x0000);
|
|
apdu.frame().addressType(GroupAddress);
|
|
apdu.frame().systemBroadcast(Broadcast);
|
|
|
|
uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4
|
|
CemiFrame secureFrame(secureApduLength);
|
|
// create secure APDU
|
|
if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl))
|
|
{
|
|
ApplicationLayer::dataBroadcastRequest(ack, hopType, SystemPriority, secureFrame.apdu(), secCtrl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl)
|
|
{
|
|
println("dataSystemBroadcastRequest");
|
|
|
|
if (secCtrl.dataSecurity != DataSecurity::None)
|
|
{
|
|
apdu.frame().sourceAddress(_deviceObj.individualAddress());
|
|
apdu.frame().destinationAddress(0x0000);
|
|
apdu.frame().addressType(GroupAddress);
|
|
apdu.frame().systemBroadcast(SysBroadcast);
|
|
|
|
uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4
|
|
CemiFrame secureFrame(secureApduLength);
|
|
// create secure APDU
|
|
if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl))
|
|
{
|
|
ApplicationLayer::dataSystemBroadcastRequest(ack, hopType, SystemPriority, secureFrame.apdu(), secCtrl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataSystemBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataIndividualRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t destination, APDU& apdu, const SecurityControl& secCtrl)
|
|
{
|
|
println("dataIndividualRequest");
|
|
|
|
if (secCtrl.dataSecurity != DataSecurity::None)
|
|
{
|
|
apdu.frame().sourceAddress(_deviceObj.individualAddress());
|
|
apdu.frame().destinationAddress(destination);
|
|
apdu.frame().addressType(IndividualAddress);
|
|
|
|
uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4
|
|
CemiFrame secureFrame(secureApduLength);
|
|
// create secure APDU
|
|
if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl))
|
|
{
|
|
ApplicationLayer::dataIndividualRequest(ack, hopType, priority, destination, secureFrame.apdu(), secCtrl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ApplicationLayer::dataIndividualRequest(ack, hopType, priority, destination, apdu, secCtrl);
|
|
}
|
|
|
|
void SecureApplicationLayer::dataConnectedRequest(uint16_t tsap, Priority priority, APDU& apdu, const SecurityControl &secCtrl)
|
|
{
|
|
println("dataConnectedRequest");
|
|
|
|
if (secCtrl.dataSecurity != DataSecurity::None)
|
|
{
|
|
apdu.frame().sourceAddress(_deviceObj.individualAddress());
|
|
apdu.frame().destinationAddress(_transportLayer->getConnectionAddress());
|
|
apdu.frame().addressType(IndividualAddress);
|
|
|
|
uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4
|
|
CemiFrame secureFrame(secureApduLength);
|
|
// create secure APDU
|
|
if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl))
|
|
{
|
|
ApplicationLayer::dataConnectedRequest(tsap, priority, secureFrame.apdu(), secCtrl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// apdu must be valid until it was confirmed
|
|
ApplicationLayer::dataConnectedRequest(tsap, priority, apdu, secCtrl);
|
|
}
|
|
|
|
void SecureApplicationLayer::encryptAesCbc(uint8_t* buffer, uint16_t bufLen, const uint8_t* iv, const uint8_t* key)
|
|
{
|
|
// Use zeroes as IV for first round
|
|
uint8_t zeroIv[16] = {0x00};
|
|
|
|
struct AES_ctx ctx;
|
|
AES_init_ctx_iv(&ctx, key, zeroIv);
|
|
|
|
// Now encrypt first block B0.
|
|
AES_CBC_encrypt_buffer(&ctx, (uint8_t*) iv, 16);
|
|
|
|
// Encrypt remaining buffer
|
|
AES_CBC_encrypt_buffer(&ctx, buffer, bufLen);
|
|
}
|
|
|
|
void SecureApplicationLayer::xcryptAesCtr(uint8_t* buffer, uint16_t bufLen, const uint8_t* iv, const uint8_t* key)
|
|
{
|
|
struct AES_ctx ctx;
|
|
|
|
AES_init_ctx_iv(&ctx, key, iv);
|
|
|
|
AES_CTR_xcrypt_buffer(&ctx, buffer, bufLen);
|
|
}
|
|
|
|
uint32_t SecureApplicationLayer::calcAuthOnlyMac(uint8_t* apdu, uint8_t apduLength, const uint8_t* key, uint8_t* iv, uint8_t* ctr0)
|
|
{
|
|
uint16_t bufLen = 2 + apduLength; // 2 bytes for the length field (uint16_t)
|
|
// AES-128 operates on blocks of 16 bytes, add padding
|
|
uint16_t bufLenPadded = (bufLen + 15) / 16 * 16;
|
|
uint8_t buffer[bufLenPadded];
|
|
// Make sure to have zeroes everywhere, because of the padding
|
|
memset(buffer, 0x00, bufLenPadded);
|
|
|
|
uint8_t* pBuf = buffer;
|
|
|
|
pBuf = pushWord(apduLength, pBuf);
|
|
pBuf = pushByteArray(apdu, apduLength, pBuf);
|
|
|
|
encryptAesCbc(buffer, bufLenPadded, iv, key);
|
|
xcryptAesCtr(buffer, 4, ctr0, key); // 4 bytes only for the MAC
|
|
|
|
uint32_t mac;
|
|
popInt(mac, &buffer[0]);
|
|
|
|
return mac;
|
|
}
|
|
|
|
uint32_t SecureApplicationLayer::calcConfAuthMac(uint8_t* associatedData, uint16_t associatedDataLength,
|
|
uint8_t* apdu, uint8_t apduLength,
|
|
const uint8_t* key, uint8_t* iv)
|
|
{
|
|
uint16_t bufLen = 2 + associatedDataLength + apduLength; // 2 bytes for the length field (uint16_t)
|
|
// AES-128 operates on blocks of 16 bytes, add padding
|
|
uint16_t bufLenPadded = (bufLen + 15) / 16 * 16;
|
|
uint8_t buffer[bufLenPadded];
|
|
// Make sure to have zeroes everywhere, because of the padding
|
|
memset(buffer, 0x00, bufLenPadded);
|
|
|
|
uint8_t* pBuf = buffer;
|
|
|
|
pBuf = pushWord(associatedDataLength, pBuf);
|
|
pBuf = pushByteArray(associatedData, associatedDataLength, pBuf);
|
|
pBuf = pushByteArray(apdu, apduLength, pBuf);
|
|
|
|
encryptAesCbc(buffer, bufLenPadded, iv, key);
|
|
|
|
uint32_t mac;
|
|
popInt(mac, &buffer[bufLenPadded - 16]); // bufLenPadded has a guaranteed minimum size of 16 bytes
|
|
|
|
return mac;
|
|
}
|
|
|
|
void SecureApplicationLayer::block0(uint8_t* buffer, uint8_t* seqNum, uint16_t indSrcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t extFrameFormat, uint8_t tpci, uint8_t apci, uint8_t payloadLength)
|
|
{
|
|
uint8_t* pBuf = buffer;
|
|
pBuf = pushByteArray(seqNum, 6, pBuf);
|
|
pBuf = pushWord(indSrcAddr, pBuf);
|
|
pBuf = pushWord(dstAddr, pBuf);
|
|
pBuf = pushByte(0x00, pBuf); // FT: frametype
|
|
pBuf = pushByte( (dstAddrIsGroupAddr ? 0x80 : 0x00) | (extFrameFormat & 0xf), pBuf); // AT: address type
|
|
pBuf = pushByte(tpci, pBuf); // TPCI
|
|
pBuf = pushByte(apci, pBuf); // APCI // draft spec shows something different!
|
|
pBuf = pushByte(0x00, pBuf); // Reserved: fixed 0x00 (really?)
|
|
pBuf = pushByte(payloadLength, pBuf); // Payload length
|
|
}
|
|
|
|
void SecureApplicationLayer::blockCtr0(uint8_t* buffer, uint8_t* seqNum, uint16_t indSrcAddr, uint16_t dstAddr)
|
|
{
|
|
uint8_t* pBuf = buffer;
|
|
pBuf = pushByteArray(seqNum, 6, pBuf);
|
|
pBuf = pushWord(indSrcAddr, pBuf);
|
|
pBuf = pushWord(dstAddr, pBuf);
|
|
pBuf = pushInt(0x00000000, pBuf);
|
|
pBuf = pushByte(0x01, pBuf);
|
|
}
|
|
|
|
uint16_t SecureApplicationLayer::groupAddressIndex(uint16_t groupAddr)
|
|
{
|
|
// Just for safety reasons, we should never get here, because the dataGroupIndication will return already return early without doing anything
|
|
if (_addrTab == nullptr)
|
|
return 0;
|
|
return _addrTab->getTsap(groupAddr);
|
|
}
|
|
|
|
const uint8_t* SecureApplicationLayer::securityKey(uint16_t addr, bool isGroupAddress)
|
|
{
|
|
if (isGroupAddress)
|
|
{
|
|
uint16_t gaIndex = groupAddressIndex(addr);
|
|
if (gaIndex > 0)
|
|
return _secIfObj.groupKey(gaIndex);
|
|
}
|
|
else
|
|
{
|
|
uint16_t iaIndex = _secIfObj.indAddressIndex(addr);
|
|
if (iaIndex > 0)
|
|
return _secIfObj.p2pKey(iaIndex);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// returns next outgoing sequence number for secure communication
|
|
uint64_t SecureApplicationLayer::nextSequenceNumber(bool toolAccess)
|
|
{
|
|
return toolAccess ? _sequenceNumberToolAccess : _sequenceNumber;
|
|
}
|
|
|
|
// stores next outgoing sequence number for secure communication
|
|
void SecureApplicationLayer::updateSequenceNumber(bool toolAccess, uint64_t seqNum)
|
|
{
|
|
if (toolAccess)
|
|
{
|
|
_sequenceNumberToolAccess = seqNum;
|
|
}
|
|
else
|
|
{
|
|
_sequenceNumber = seqNum;
|
|
}
|
|
|
|
// Also update the properties accordingly
|
|
_secIfObj.setSequenceNumber(toolAccess, seqNum);
|
|
}
|
|
|
|
uint64_t SecureApplicationLayer::lastValidSequenceNumber(bool toolAccess, uint16_t srcAddr)
|
|
{
|
|
if (toolAccess)
|
|
{
|
|
// TODO: check if we really have to support multiple tools at the same time
|
|
if (srcAddr == _deviceObj.individualAddress())
|
|
return _sequenceNumberToolAccess;
|
|
return _lastValidSequenceNumberTool;
|
|
}
|
|
else
|
|
{
|
|
return _secIfObj.getLastValidSequenceNumber(srcAddr);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SecureApplicationLayer::updateLastValidSequence(bool toolAccess, uint16_t remoteAddr, uint64_t seqNo)
|
|
{
|
|
if (toolAccess)
|
|
// TODO: check if we really have to support multiple tools at the same time
|
|
_lastValidSequenceNumberTool = seqNo;
|
|
else
|
|
{
|
|
_secIfObj.setLastValidSequenceNumber(remoteAddr, seqNo);
|
|
}
|
|
}
|
|
|
|
void SecureApplicationLayer::sendSyncRequest(uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl &secCtrl, bool systemBcast)
|
|
{
|
|
if (secCtrl.dataSecurity != DataSecurity::AuthConf)
|
|
{
|
|
println("sync.req is always sent with auth+conf!");
|
|
return;
|
|
}
|
|
|
|
_syncReqBroadcastOutgoing = (dstAddr == 0x0000) && dstAddrIsGroupAddr;
|
|
|
|
// use random number in SyncResponse
|
|
uint64_t challenge = getRandomNumber();
|
|
|
|
uint8_t asdu[6];
|
|
sixBytesFromUInt64(challenge, &asdu[0]);
|
|
|
|
CemiFrame request(2 + 6 + sizeof(asdu) + 4); // 2 bytes (APCI, SCF) + 6 bytes (SeqNum) + 6 bytes (challenge) + 4 bytes (MAC)
|
|
// Note: additional TPCI byte is already handled internally!
|
|
|
|
uint8_t tpci = 0;
|
|
if (!_syncReqBroadcastOutgoing)
|
|
{
|
|
if (isConnected())
|
|
{
|
|
tpci |= 0x40 | _transportLayer->getTpciSeqNum(); // get next TPCI sequence number for MAC calculation from TL (T_DATA_CONNECTED)
|
|
}
|
|
}
|
|
print("sendSyncRequest: TPCI: ");
|
|
println(tpci, HEX);
|
|
|
|
if(secure(request.data() + APDU_LPDU_DIFF, kSecureSyncRequest, _deviceObj.individualAddress(), dstAddr, dstAddrIsGroupAddr, tpci, asdu, sizeof(asdu), secCtrl, systemBcast))
|
|
{
|
|
println("SyncRequest: ");
|
|
request.apdu().printPDU();
|
|
|
|
if (_syncReqBroadcastOutgoing)
|
|
{
|
|
_transportLayer->dataBroadcastRequest(AckType::AckDontCare, HopCountType::NetworkLayerParameter, Priority::SystemPriority, request.apdu());
|
|
}
|
|
else
|
|
{
|
|
// either send on T_DATA_INDIVIDUAL or T_DATA_CONNECTED
|
|
if (isConnected())
|
|
{
|
|
_transportLayer->dataConnectedRequest(getConnectedTsasp(), SystemPriority, request.apdu());
|
|
}
|
|
else
|
|
{
|
|
// Send encrypted SyncResponse message using T_DATA_INDIVIDUAL
|
|
_transportLayer->dataIndividualRequest(AckType::AckDontCare, NetworkLayerParameter, SystemPriority, dstAddr, request.apdu());
|
|
}
|
|
}
|
|
|
|
Addr toAddr = _syncReqBroadcastOutgoing ? (Addr)GrpAddr(0) : (Addr)IndAddr(dstAddr);
|
|
_pendingOutgoingSyncRequests.insertOrAssign(toAddr, challenge);
|
|
}
|
|
else
|
|
{
|
|
println("SyncRequest: failure during encryption");
|
|
}
|
|
}
|
|
|
|
void SecureApplicationLayer::sendSyncResponse(uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl &secCtrl, uint64_t remoteNextSeqNum, bool systemBcast)
|
|
{
|
|
if (secCtrl.dataSecurity != DataSecurity::AuthConf)
|
|
{
|
|
println("sync.res is always sent with auth+conf!");
|
|
return;
|
|
}
|
|
|
|
uint64_t ourNextSeqNum = nextSequenceNumber(secCtrl.toolAccess);
|
|
|
|
uint8_t asdu[12];
|
|
sixBytesFromUInt64(ourNextSeqNum, &asdu[0]);
|
|
sixBytesFromUInt64(remoteNextSeqNum, &asdu[6]);
|
|
|
|
CemiFrame response(2 + 6 + sizeof(asdu) + 4); // 2 bytes (APCI, SCF) + 6 bytes (SeqNum) + 12 bytes + 4 bytes (MAC)
|
|
// Note: additional TPCI byte is already handled internally!
|
|
|
|
uint8_t tpci = 0;
|
|
if (!_syncReqBroadcastIncoming)
|
|
{
|
|
if (isConnected())
|
|
{
|
|
tpci |= 0x40 | _transportLayer->getTpciSeqNum(); // get next TPCI sequence number for MAC calculation from TL (T_DATA_CONNECTED)
|
|
}
|
|
}
|
|
print("sendSyncResponse: TPCI: ");
|
|
println(tpci, HEX);
|
|
|
|
if(secure(response.data() + APDU_LPDU_DIFF, kSecureSyncResponse, _deviceObj.individualAddress(), dstAddr, dstAddrIsGroupAddr, tpci, asdu, sizeof(asdu), secCtrl, systemBcast))
|
|
{
|
|
_lastSyncRes = millis();
|
|
|
|
println("SyncResponse: ");
|
|
response.apdu().printPDU();
|
|
|
|
if (_syncReqBroadcastIncoming)
|
|
{
|
|
_transportLayer->dataBroadcastRequest(AckType::AckDontCare, HopCountType::NetworkLayerParameter, Priority::SystemPriority, response.apdu());
|
|
}
|
|
else
|
|
{
|
|
// either send on T_DATA_INDIVIDUAL or T_DATA_CONNECTED
|
|
if (isConnected())
|
|
{
|
|
_transportLayer->dataConnectedRequest(getConnectedTsasp(), SystemPriority, response.apdu());
|
|
}
|
|
else
|
|
{
|
|
// Send encrypted SyncResponse message using T_DATA_INDIVIDUAL
|
|
_transportLayer->dataIndividualRequest(AckType::AckDontCare, NetworkLayerParameter, SystemPriority, dstAddr, response.apdu());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
println("SyncResponse: failure during encryption");
|
|
}
|
|
}
|
|
|
|
void SecureApplicationLayer::receivedSyncRequest(uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl &secCtrl, uint8_t* seqNum, uint64_t challenge, bool systemBcast)
|
|
{
|
|
println("Received SyncRequest:");
|
|
|
|
uint64_t nextRemoteSeqNum = sixBytesToUInt64(seqNum);
|
|
uint64_t nextSeqNum = 1 + lastValidSequenceNumber(secCtrl.toolAccess, srcAddr);
|
|
|
|
if (nextRemoteSeqNum > nextSeqNum)
|
|
{
|
|
updateLastValidSequence(secCtrl.toolAccess, srcAddr, nextRemoteSeqNum - 1);
|
|
nextSeqNum = nextRemoteSeqNum;
|
|
}
|
|
|
|
_syncReqBroadcastIncoming = (dstAddr == 0x0000) && dstAddrIsGroupAddr;
|
|
|
|
// Remember challenge for securing the sync.res later
|
|
_pendingIncomingSyncRequests.insertOrAssign(_syncReqBroadcastIncoming ? (Addr) GrpAddr(0) : (Addr) IndAddr(srcAddr), challenge);
|
|
|
|
uint16_t toAddr = _syncReqBroadcastIncoming ? dstAddr : srcAddr;
|
|
bool toIsGroupAddress = _syncReqBroadcastIncoming;
|
|
sendSyncResponse(toAddr, toIsGroupAddress, secCtrl, nextSeqNum, systemBcast);
|
|
}
|
|
|
|
void SecureApplicationLayer::receivedSyncResponse(uint16_t remote, const SecurityControl &secCtrl, uint8_t* plainApdu)
|
|
{
|
|
println("Received SyncResponse:");
|
|
|
|
if (_syncReqBroadcastOutgoing)
|
|
{
|
|
if (_pendingOutgoingSyncRequests.get(GrpAddr(0)) == nullptr)
|
|
{
|
|
println("Cannot handle sync.res without pending sync.req! (broadcast/systembroadcast)");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_pendingOutgoingSyncRequests.get(IndAddr(remote)) == nullptr)
|
|
{
|
|
println("Cannot handle sync.res without pending sync.req!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Bytes 0-5 in the "APDU" buffer contain the remote sequence number
|
|
// Bytes 6-11 in the "APDU" buffer contain the local sequence number
|
|
uint64_t remoteSeq = sixBytesToUInt64(plainApdu + 0);
|
|
uint64_t localSeq = sixBytesToUInt64(plainApdu + 6);
|
|
|
|
uint64_t last = lastValidSequenceNumber(secCtrl.toolAccess, remote);
|
|
if (remoteSeq - 1 > last)
|
|
{
|
|
//logger.debug("sync.res update {} last valid {} seq -> {}", remote, toolAccess ? "tool access" : "p2p", remoteSeq -1);
|
|
updateLastValidSequence(secCtrl.toolAccess, remote, remoteSeq - 1);
|
|
}
|
|
|
|
uint64_t next = nextSequenceNumber(secCtrl.toolAccess);
|
|
if (localSeq > next) {
|
|
//logger.debug("sync.res update local next {} seq -> {}", toolAccess ? "tool access" : "p2p", localSeq);
|
|
updateSequenceNumber(secCtrl.toolAccess, localSeq);
|
|
}
|
|
|
|
Addr remoteAddr = _syncReqBroadcastOutgoing ? (Addr)GrpAddr(0) : (Addr)IndAddr(remote);
|
|
_pendingOutgoingSyncRequests.erase(remoteAddr);
|
|
}
|
|
|
|
bool SecureApplicationLayer::decrypt(uint8_t* plainApdu, uint16_t plainApduLength, uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t tpci, uint8_t* secureAsdu, SecurityControl& secCtrl, bool systemBcast)
|
|
{
|
|
const uint8_t* pBuf;
|
|
uint8_t scf;
|
|
|
|
pBuf = popByte(scf, secureAsdu);
|
|
|
|
bool toolAccess = ((scf & 0x80) == 0x80);
|
|
bool systemBroadcast = ((scf & 0x08) == 0x08);
|
|
uint8_t sai = (scf >> 4) & 0x07; // sai can only be 0x0 (CCM auth only) or 0x1 (CCM with auth+conf), other values are reserved
|
|
bool authOnly = ( sai == 0);
|
|
uint8_t service = (scf & 0x07); // only 0x0 (S-A_Data-PDU), 0x2 (S-A_Sync_Req-PDU) or 0x3 (S-A_Sync_Rsp-PDU) are valid values
|
|
|
|
if (systemBroadcast != systemBcast)
|
|
{
|
|
println("SBC flag in SCF does not match actual communication mode!");
|
|
}
|
|
|
|
secCtrl.toolAccess = toolAccess;
|
|
secCtrl.dataSecurity = authOnly ? DataSecurity::Auth : DataSecurity::AuthConf;
|
|
|
|
bool syncReq = service == kSecureSyncRequest;
|
|
bool syncRes = service == kSecureSyncResponse;
|
|
|
|
//const uint8_t* key = dstAddrIsGroupAddr ? securityKey(dstAddr, dstAddrIsGroupAddr) : toolAccess ? toolKey() : securityKey(srcAddr, false);
|
|
const uint8_t* key = dstAddrIsGroupAddr && (dstAddr != 0) ? securityKey(dstAddr, dstAddrIsGroupAddr) : toolAccess ? _secIfObj.toolKey() : securityKey(srcAddr, false);
|
|
if (key == nullptr)
|
|
{
|
|
print("Error: No key found. toolAccess: ");
|
|
println(toolAccess ? "true" : "false");
|
|
return false;
|
|
}
|
|
|
|
uint8_t seqNum[6];
|
|
pBuf = popByteArray(seqNum, 6, pBuf);
|
|
uint64_t receivedSeqNumber = sixBytesToUInt64(seqNum);
|
|
|
|
// Provide array for KNX serial number if it is a SyncRequest
|
|
// DataService and SyncResponse do not use this variable.
|
|
uint8_t knxSerialNumber[6];
|
|
|
|
uint16_t remainingPlainApduLength = plainApduLength;
|
|
|
|
if (service == kSecureDataPdu)
|
|
{
|
|
if (srcAddr != _deviceObj.individualAddress())
|
|
{
|
|
uint64_t expectedSeqNumber = lastValidSequenceNumber(toolAccess, srcAddr) + 1;
|
|
|
|
if (receivedSeqNumber < expectedSeqNumber)
|
|
{
|
|
// security failure
|
|
print("security failure: received seqNum: ");
|
|
print(receivedSeqNumber, HEX);
|
|
print(" < expected seqNum: ");
|
|
print(expectedSeqNumber, HEX);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if(syncReq)
|
|
{
|
|
pBuf = popByteArray(knxSerialNumber, 6, pBuf);
|
|
remainingPlainApduLength -= 6;
|
|
|
|
// ignore sync.reqs not addressed to us
|
|
if (!memcmp(knxSerialNumber, _deviceObj.propertyData(PID_SERIAL_NUMBER), 6))
|
|
{
|
|
uint8_t emptySerialNumber[6] = {0};
|
|
if (systemBroadcast || dstAddr != _deviceObj.individualAddress() || !memcmp(knxSerialNumber, emptySerialNumber, 6))
|
|
return false;
|
|
}
|
|
|
|
// if responded to another request within the last 1 second, ignore
|
|
if ((millis() - _lastSyncRes) < 1000)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (syncRes)
|
|
{
|
|
// fetch challenge depending on srcAddr to handle multiple requests
|
|
uint64_t *challenge = _pendingOutgoingSyncRequests.get(IndAddr(srcAddr));
|
|
if (challenge == nullptr)
|
|
{
|
|
println("Cannot find matching challenge for source address!");
|
|
return false;
|
|
}
|
|
|
|
uint8_t _challengeSixBytes[6];
|
|
sixBytesFromUInt64(*challenge, _challengeSixBytes);
|
|
// in a sync.res, seq actually contains our challenge from sync.req xored with a random value
|
|
// extract the random value and store it in seqNum to use it for block0 and ctr0
|
|
for (uint8_t i = 0; i < sizeof(seqNum); i++)
|
|
{
|
|
seqNum[i] ^= _challengeSixBytes[i];
|
|
}
|
|
}
|
|
|
|
pBuf = popByteArray(plainApdu, remainingPlainApduLength, pBuf);
|
|
|
|
// No LTE-HEE for now
|
|
// Data Secure always uses extended frame format with APDU length > 15 octets (long messages), no standard frames
|
|
uint8_t extendedFrameFormat = 0;
|
|
// Clear IV buffer
|
|
uint8_t iv[16] = {0x00};
|
|
// Create first block B0 for AES CBC MAC calculation, used as IV later
|
|
/*
|
|
printHex("seq: ", seqNum, 6);
|
|
printHex("src: ", (uint8_t*) &srcAddr, 2);
|
|
printHex("dst: ", (uint8_t*) &dstAddr, 2);
|
|
print("dstAddrisGroup: ");println(dstAddrIsGroupAddr ? "true" : "false");
|
|
*/
|
|
block0(iv, seqNum, srcAddr, dstAddr, dstAddrIsGroupAddr, extendedFrameFormat, tpci | (SecureService >> 8), SecureService & 0x00FF, remainingPlainApduLength);
|
|
|
|
// Clear block counter0 buffer
|
|
uint8_t ctr0[16] = {0x00};
|
|
// Create first block for block counter 0
|
|
blockCtr0(ctr0, seqNum, srcAddr, dstAddr);
|
|
|
|
uint32_t mac;
|
|
pBuf = popInt(mac, pBuf);
|
|
|
|
if (authOnly)
|
|
{
|
|
// APDU is already plain, no decryption needed
|
|
|
|
// Only check the MAC
|
|
uint32_t calculatedMac = calcAuthOnlyMac(plainApdu, remainingPlainApduLength, key, iv, ctr0);
|
|
if (calculatedMac != mac)
|
|
{
|
|
// security failure
|
|
print("security failure(auth): calculated MAC: ");
|
|
print(calculatedMac, HEX);
|
|
print(" != received MAC: ");
|
|
print(mac, HEX);
|
|
println("\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
memcpy(plainApdu, secureAsdu, remainingPlainApduLength);
|
|
}
|
|
else
|
|
{
|
|
// APDU is encrypted and needs decryption
|
|
|
|
uint16_t bufLen = 4 + remainingPlainApduLength;
|
|
// AES-128 operates on blocks of 16 bytes, add padding
|
|
uint16_t bufLenPadded = (bufLen + 15) / 16 * 16;
|
|
uint8_t buffer[bufLenPadded];
|
|
//uint8_t buffer[bufLen];
|
|
// Make sure to have zeroes everywhere, because of the padding
|
|
memset(buffer, 0x00, bufLenPadded);
|
|
|
|
pushInt(mac, &buffer[0]);
|
|
pushByteArray(plainApdu, remainingPlainApduLength, &buffer[4]); // apdu is still encrypted
|
|
|
|
xcryptAesCtr(buffer, bufLenPadded, ctr0, key);
|
|
//xcryptAesCtr(buffer, bufLen, ctr0, key);
|
|
|
|
uint32_t decryptedMac;
|
|
popInt(decryptedMac, &buffer[0]);
|
|
popByteArray(plainApdu, remainingPlainApduLength, &buffer[4]); // apdu is now decrypted (overwritten)
|
|
|
|
// Do calculations for Auth+Conf
|
|
uint8_t associatedData[syncReq ? 7 : 1];
|
|
associatedData[0] = scf;
|
|
if (syncReq)
|
|
{
|
|
memcpy(&associatedData[1], knxSerialNumber, 6);
|
|
}
|
|
/*
|
|
printHex("APDU--------->", plainApdu, remainingPlainApduLength);
|
|
printHex("Key---------->", key, 16);
|
|
printHex("ASSOC-------->", associatedData, sizeof(associatedData));
|
|
*/
|
|
uint32_t calculatedMac = calcConfAuthMac(associatedData, sizeof(associatedData), plainApdu, remainingPlainApduLength, key, iv);
|
|
if (calculatedMac != decryptedMac)
|
|
{
|
|
// security failure
|
|
print("security failure(conf+auth): calculated MAC: ");
|
|
print(calculatedMac, HEX);
|
|
print(" != decrypted MAC: ");
|
|
print(decryptedMac, HEX);
|
|
println("\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
// prevent a sync.req sent by us to trigger sync notification, this happens if we provide our own tool key
|
|
// for decryption above
|
|
if (syncReq && (srcAddr == _deviceObj.individualAddress()))
|
|
return false;
|
|
|
|
if (syncReq)
|
|
{
|
|
uint64_t challenge = sixBytesToUInt64(&plainApdu[0]);
|
|
receivedSyncRequest(srcAddr, dstAddr, dstAddrIsGroupAddr, secCtrl, seqNum, challenge, systemBroadcast);
|
|
return false;
|
|
}
|
|
else if (syncRes)
|
|
{
|
|
receivedSyncResponse(srcAddr, secCtrl, plainApdu);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (srcAddr == _deviceObj.individualAddress())
|
|
{
|
|
print("Update our next ");
|
|
print(toolAccess ? "tool access" : "");
|
|
print(" seq from ");
|
|
print(srcAddr, HEX);
|
|
print(" -> (+1) ");
|
|
println(receivedSeqNumber,HEX);
|
|
updateSequenceNumber(toolAccess, receivedSeqNumber + 1);
|
|
}
|
|
else
|
|
{
|
|
print("Update last valid ");
|
|
print(toolAccess ? "tool access" : "");
|
|
print(" seq from ");
|
|
print(srcAddr, HEX);
|
|
print(" -> ");
|
|
println(receivedSeqNumber, HEX);
|
|
updateLastValidSequence(toolAccess, srcAddr, receivedSeqNumber);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SecureApplicationLayer::decodeSecureApdu(APDU& secureApdu, APDU& plainApdu, SecurityControl& secCtrl)
|
|
{
|
|
// Decode secure APDU
|
|
|
|
println("decodeSecureApdu: Secure APDU: ");
|
|
secureApdu.printPDU();
|
|
|
|
uint16_t srcAddress = secureApdu.frame().sourceAddress();
|
|
uint16_t dstAddress = secureApdu.frame().destinationAddress();
|
|
bool isDstAddrGroupAddr = secureApdu.frame().addressType() == GroupAddress;
|
|
bool isSystemBroadcast = secureApdu.frame().systemBroadcast();
|
|
uint8_t tpci = secureApdu.frame().data()[TPDU_LPDU_DIFF]; // FIXME: when cEMI class is refactored, there might be additional info fields in cEMI [fixed TPDU_LPDU_DIFF]
|
|
print("decodeSecureApdu: TPCI: ");
|
|
println(tpci, HEX);
|
|
// Note:
|
|
// The TPCI is also included in the MAC calculation to provide authenticity for this field.
|
|
// However, a secure APDU (with a MAC) is only included in transport layer PDUs T_DATA_GROUP, T_DATA_TAG_GROUP, T_DATA_INDIVIDUAL, T_DATA_CONNECTED
|
|
// and not in T_CONNECT, T_DISCONNECT, T_ACK, T_NACK.
|
|
// This means the DATA/CONTROL flag is always 0(=DATA). The flag "NUMBERED" differentiates between T_DATA_INDIVIDUAL and T_DATA_CONNECTED.
|
|
// The seqNumber is only used in T_DATA_CONNECTED and 0 in case of T_DATA_GROUP and T_DATA_GROUP (leaving out T_DATA_TAG_GROUP).
|
|
// Summary: effectively only the "NUMBERED" flag (bit6) and the SeqNumber (bit5-2) are used from transport layer.
|
|
// In T_DATA_* services the bits1-0 of TPCI octet are used as bits9-8 for APCI type which is fixed to 0x03. SecureService APCI is 0x03F1.
|
|
|
|
// FIXME: when cEMI class is refactored, there might be additional info fields in cEMI (fixed APDU_LPDU_DIFF)
|
|
// We are starting from TPCI octet (including): plainApdu.frame().data()+APDU_LPDU_DIFF
|
|
if (decrypt(plainApdu.frame().data()+APDU_LPDU_DIFF, plainApdu.length()+1, srcAddress, dstAddress, isDstAddrGroupAddr, tpci, secureApdu.data()+1, secCtrl, isSystemBroadcast))
|
|
{
|
|
println("decodeSecureApdu: Plain APDU: ");
|
|
plainApdu.frame().apdu().printPDU();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SecureApplicationLayer::secure(uint8_t* buffer, uint16_t service, uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t tpci,
|
|
uint8_t* apdu, uint16_t apduLength, const SecurityControl& secCtrl, bool systemBcast)
|
|
{
|
|
bool toolAccess = secCtrl.toolAccess;
|
|
bool confidentiality = secCtrl.dataSecurity == DataSecurity::AuthConf;
|
|
|
|
if (toolAccess)
|
|
{
|
|
if (!confidentiality)
|
|
{
|
|
println("Error: tool access requires auth+conf security");
|
|
return false;
|
|
}
|
|
if (dstAddrIsGroupAddr && dstAddr != 0)
|
|
{
|
|
println("Error: tool access requires individual address");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const uint8_t* key = toolAccess ? _secIfObj.toolKey() : securityKey(dstAddr, dstAddrIsGroupAddr);
|
|
if (key == nullptr)
|
|
{
|
|
print("Error: No key found. toolAccess: ");
|
|
println(toolAccess ? "true" : "false");
|
|
return false;
|
|
}
|
|
|
|
bool syncReq = service == kSecureSyncRequest;
|
|
bool syncRes = service == kSecureSyncResponse;
|
|
|
|
tpci |= SecureService >> 8; // OR'ing upper two APCI bits
|
|
uint8_t apci = SecureService & 0x00FF;
|
|
uint8_t* pBuf = buffer;
|
|
pBuf = pushByte(tpci, pBuf); // TPCI
|
|
pBuf = pushByte(apci, pBuf); // APCI
|
|
|
|
uint8_t scf;
|
|
scf = service;
|
|
scf |= toolAccess ? 0x80 : 0;
|
|
scf |= confidentiality ? 0x10 : 0;
|
|
scf |= systemBcast ? 0x8 : 0;
|
|
|
|
pBuf = pushByte(scf, pBuf); // SCF
|
|
|
|
uint64_t seqSend = nextSequenceNumber(toolAccess);
|
|
if (seqSend == 0)
|
|
println("0 is not a valid sequence number");
|
|
|
|
uint8_t seq[6];
|
|
sixBytesFromUInt64(seqSend, seq);
|
|
if (!syncRes)
|
|
pBuf = pushByteArray(seq, 6, pBuf); // Sequence Number
|
|
|
|
// Prepare associated data depending on service (SyncRequest, SyncResponse or just DataService)
|
|
uint8_t associatedData[syncReq ? 7 : 1];
|
|
associatedData[0] = scf;
|
|
if (syncReq)
|
|
{
|
|
// TODO: NYI lookup serial number of target device for SBC sync.req
|
|
uint8_t remoteSerialNo[6] = {0};
|
|
|
|
uint8_t emptySerialNo[6] = {0};
|
|
pBuf = pushByteArray(systemBcast ? remoteSerialNo : emptySerialNo, 6, pBuf);
|
|
pushByteArray(_deviceObj.propertyData(PID_SERIAL_NUMBER), 6, &associatedData[1]);
|
|
}
|
|
else if (syncRes)
|
|
{
|
|
// use random number in SyncResponse
|
|
uint64_t randomNumber = getRandomNumber();
|
|
sixBytesFromUInt64(randomNumber, seq);
|
|
|
|
Addr remote = _syncReqBroadcastIncoming ? (Addr)GrpAddr(0) : (Addr)IndAddr(dstAddr);
|
|
|
|
// Get challenge from sync.req
|
|
uint64_t *challenge = _pendingIncomingSyncRequests.get(remote);
|
|
if (challenge == nullptr)
|
|
{
|
|
println("Cannot send sync.res without corresponding sync.req");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
_pendingIncomingSyncRequests.erase(remote);
|
|
}
|
|
uint8_t challengeSixBytes[6];
|
|
sixBytesFromUInt64(*challenge, challengeSixBytes);
|
|
//printHex("Decrypted challenge: ", challengeSixBytes, 6);
|
|
|
|
// Now XOR the new random SeqNum with the challenge from the SyncRequest
|
|
uint8_t rndXorChallenge[6];
|
|
pushByteArray(seq, 6, rndXorChallenge);
|
|
for (uint8_t i = 0; i < sizeof(rndXorChallenge); i++)
|
|
{
|
|
rndXorChallenge[i] ^= challengeSixBytes[i];
|
|
}
|
|
pBuf = pushByteArray(rndXorChallenge, 6, pBuf);
|
|
}
|
|
|
|
// No LTE-HEE for now
|
|
// Data Secure always uses extended frame format with APDU length > 15 octets (long messages), no standard frames
|
|
uint8_t extendedFrameFormat = 0;
|
|
// Clear IV buffer
|
|
uint8_t iv[16] = {0x00};
|
|
// Create first block B0 for AES CBC MAC calculation, used as IV later
|
|
/*
|
|
printHex("seq: ", seq, 6);
|
|
printHex("src: ", (uint8_t*) &srcAddr, 2);
|
|
printHex("dst: ", (uint8_t*) &dstAddr, 2);
|
|
print("dstAddrisGroup: ");println(dstAddrIsGroupAddr ? "true" : "false");
|
|
*/
|
|
block0(iv, seq, srcAddr, dstAddr, dstAddrIsGroupAddr, extendedFrameFormat, tpci, apci, apduLength);
|
|
|
|
// Clear block counter0 buffer
|
|
uint8_t ctr0[16] = {0x00};
|
|
// Create first block for block counter 0
|
|
blockCtr0(ctr0, seq, srcAddr, dstAddr);
|
|
|
|
if (confidentiality)
|
|
{
|
|
// Do calculations for Auth+Conf
|
|
/*
|
|
printHex("APDU--------->", apdu, apduLength);
|
|
printHex("Key---------->", key, 16);
|
|
printHex("ASSOC-------->", associatedData, sizeof(associatedData));
|
|
*/
|
|
uint32_t mac = calcConfAuthMac(associatedData, sizeof(associatedData), apdu, apduLength, key, iv);
|
|
|
|
uint8_t tmpBuffer[4 + apduLength];
|
|
pushInt(mac, tmpBuffer);
|
|
pushByteArray(apdu, apduLength, &tmpBuffer[4]);
|
|
|
|
xcryptAesCtr(tmpBuffer, apduLength + 4, ctr0, key); // APDU + MAC (4 bytes)
|
|
|
|
pBuf = pushByteArray(tmpBuffer + 4, apduLength, pBuf); // Encrypted APDU
|
|
pBuf = pushByteArray(tmpBuffer + 0, 4, pBuf); // Encrypted MAC
|
|
|
|
//print("MAC(encrypted): ");
|
|
//println(*((uint32_t*)(tmpBuffer + 0)),HEX);
|
|
}
|
|
else
|
|
{
|
|
// Do calculations for AuthOnly
|
|
uint32_t tmpMac = calcAuthOnlyMac(apdu, apduLength, key, iv, ctr0);
|
|
|
|
pBuf = pushByteArray(apdu, apduLength, pBuf); // Plain APDU
|
|
pBuf = pushInt(tmpMac, pBuf); // MAC
|
|
|
|
print("MAC: ");
|
|
println(tmpMac, HEX);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SecureApplicationLayer::createSecureApdu(APDU& plainApdu, APDU& secureApdu, const SecurityControl& secCtrl)
|
|
{
|
|
// Create secure APDU
|
|
|
|
println("createSecureApdu: Plain APDU: ");
|
|
plainApdu.printPDU();
|
|
|
|
uint16_t srcAddress = plainApdu.frame().sourceAddress();
|
|
uint16_t dstAddress = plainApdu.frame().destinationAddress();
|
|
bool isDstAddrGroupAddr = plainApdu.frame().addressType() == GroupAddress;
|
|
bool isSystemBroadcast = plainApdu.frame().systemBroadcast();
|
|
uint8_t tpci = 0x00;
|
|
if (isConnected())
|
|
{
|
|
tpci |= 0x40 | _transportLayer->getTpciSeqNum(); // get next TPCI sequence number for MAC calculation from TL (T_DATA_CONNECTED)
|
|
}
|
|
print("createSecureApdu: TPCI: ");
|
|
println(tpci, HEX);
|
|
// Note:
|
|
// The TPCI is also included in the MAC calculation to provide authenticity for this field.
|
|
// However, a secure APDU (with a MAC) is only included in transport layer PDUs T_DATA_GROUP, T_DATA_TAG_GROUP, T_DATA_INDIVIDUAL, T_DATA_CONNECTED
|
|
// and not in T_CONNECT, T_DISCONNECT, T_ACK, T_NACK.
|
|
// This means the DATA/CONTROL flag is always 0(=DATA). The flag "NUMBERED" differentiates between T_DATA_INDIVIDUAL and T_DATA_CONNECTED.
|
|
// The seqNumber is only used in T_DATA_CONNECTED and 0 in case of T_DATA_GROUP and T_DATA_GROUP (leaving out T_DATA_TAG_GROUP).
|
|
// Summary: effectively only the "NUMBERED" flag (bit6) and the SeqNumber (bit5-2) are used from transport layer.
|
|
// In T_DATA_* services the bits1-0 of TPCI octet are used as bits9-8 for APCI type which is fixed to 0x03. SecureService APCI is 0x03F1.
|
|
|
|
// FIXME: when cEMI class is refactored, there might be additional info fields in cEMI (fixed APDU_LPDU_DIFF)
|
|
// We are starting from TPCI octet (including): plainApdu.frame().data()+APDU_LPDU_DIFF
|
|
if(secure(secureApdu.frame().data()+APDU_LPDU_DIFF, kSecureDataPdu, srcAddress, dstAddress, isDstAddrGroupAddr, tpci, plainApdu.frame().data()+APDU_LPDU_DIFF, plainApdu.length()+1, secCtrl, isSystemBroadcast))
|
|
{
|
|
print("Update our next ");
|
|
print(secCtrl.toolAccess ? "tool access" : "");
|
|
print(" seq from ");
|
|
print(srcAddress, HEX);
|
|
print(" -> (+1) ");
|
|
println(nextSequenceNumber(secCtrl.toolAccess),HEX);
|
|
updateSequenceNumber(secCtrl.toolAccess, nextSequenceNumber(secCtrl.toolAccess) + 1);
|
|
|
|
println("createSecureApdu: Secure APDU: ");
|
|
secureApdu.frame().apdu().printPDU();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint64_t SecureApplicationLayer::getRandomNumber()
|
|
{
|
|
return 0x000102030405; // TODO: generate random number
|
|
}
|
|
|
|
void SecureApplicationLayer::loop()
|
|
{
|
|
// TODO: handle timeout of outgoing sync requests
|
|
//_pendingOutgoingSyncRequests
|
|
}
|
|
|
|
bool SecureApplicationLayer::isSyncService(APDU& secureApdu)
|
|
{
|
|
uint8_t scf = *(secureApdu.data()+1);
|
|
uint8_t service = (scf & 0x07); // only 0x0 (S-A_Data-PDU), 0x2 (S-A_Sync_Req-PDU) or 0x3 (S-A_Sync_Rsp-PDU) are valid values
|
|
|
|
if ((service == kSecureSyncRequest) || (service == kSecureSyncResponse))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|