use of org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException in project openremote by openremote.
the class UpperTransportLayer method parseUpperTransportPDU.
/**
* Parse upper transport pdu
*
* @param message access message containing the upper transport pdu
*/
final synchronized void parseUpperTransportPDU(final Message message) throws ExtendedInvalidCipherTextException {
try {
switch(message.getPduType()) {
case MeshManagerApi.PDU_TYPE_NETWORK:
if (message instanceof AccessMessage) {
// Access message
final AccessMessage accessMessage = (AccessMessage) message;
reassembleLowerTransportAccessPDU(accessMessage);
final byte[] decryptedUpperTransportControlPdu = decryptUpperTransportPDU(accessMessage);
accessMessage.setAccessPdu(decryptedUpperTransportControlPdu);
} else {
// TODO
// this where control messages such as heartbeat and friendship messages are to be implemented
}
break;
case MeshManagerApi.PDU_TYPE_PROXY_CONFIGURATION:
final ControlMessage controlMessage = (ControlMessage) message;
if (controlMessage.getLowerTransportControlPdu().size() == 1) {
final byte[] lowerTransportControlPdu = controlMessage.getLowerTransportControlPdu().get(0);
final ByteBuffer buffer = ByteBuffer.wrap(lowerTransportControlPdu).order(ByteOrder.BIG_ENDIAN);
message.setOpCode(buffer.get());
final byte[] parameters = new byte[buffer.capacity() - 1];
buffer.get(parameters);
message.setParameters(parameters);
}
break;
}
} catch (InvalidCipherTextException ex) {
throw new ExtendedInvalidCipherTextException(ex.getMessage(), ex.getCause(), UpperTransportLayer.class.getSimpleName());
}
}
use of org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException in project openremote by openremote.
the class MeshManagerApi method parseNotifications.
/**
* Parses notifications received by the client.
*
* @param unsegmentedPdu pdu received by the client.
*/
private void parseNotifications(final byte[] unsegmentedPdu) {
try {
switch(unsegmentedPdu[0]) {
case PDU_TYPE_NETWORK:
// MeshNetwork PDU
LOG.info("Received network pdu: " + MeshParserUtils.bytesToHex(unsegmentedPdu, true));
mMeshMessageHandler.parseMeshPduNotifications(unsegmentedPdu, mMeshNetwork);
break;
case PDU_TYPE_MESH_BEACON:
// Validate SNBs against all network keys
NetworkKey networkKey;
for (int i = 0; i < mMeshNetwork.getNetKeys().size(); i++) {
networkKey = mMeshNetwork.getNetKeys().get(i);
final byte[] receivedBeaconData = new byte[unsegmentedPdu.length - 1];
System.arraycopy(unsegmentedPdu, 1, receivedBeaconData, 0, receivedBeaconData.length);
final SecureNetworkBeacon receivedBeacon = new SecureNetworkBeacon(receivedBeaconData);
final byte[] n = networkKey.getTxNetworkKey();
final int flags = receivedBeacon.getFlags();
final byte[] networkId = SecureUtils.calculateK3(n);
final int ivIndex = receivedBeacon.getIvIndex().getIvIndex();
LOG.info("Received mesh beacon: " + receivedBeacon.toString());
final SecureNetworkBeacon localSecureNetworkBeacon = SecureUtils.createSecureNetworkBeacon(n, flags, networkId, ivIndex);
// Check the the beacon received is a valid by matching the authentication values
if (Arrays.equals(receivedBeacon.getAuthenticationValue(), localSecureNetworkBeacon.getAuthenticationValue())) {
LOG.info("Secure Network Beacon beacon authenticated.");
// beacon on a secondary subnet, it will disregard it.
if (mMeshNetwork.getPrimaryNetworkKey() != null && networkKey.keyIndex != 0) {
LOG.info("Discarding beacon for secondary subnet with network key index: " + networkKey.keyIndex);
return;
}
// Get the last IV Index.
// / The last used IV Index for this mesh network.
final IvIndex lastIvIndex = mMeshNetwork.getIvIndex();
LOG.info("Last IV Index: " + lastIvIndex.getIvIndex());
// / The date of the last change of IV Index or IV Update Flag.
final Calendar lastTransitionDate = lastIvIndex.getTransitionDate();
// / A flag whether the IV has recently been updated using IV Recovery procedure.
// / The at-least-96h requirement for the duration of the current state will not apply.
// / The node shall not execute more than one IV Index Recovery within a period of 192 hours.
final boolean isIvRecoveryActive = lastIvIndex.getIvRecoveryFlag();
// / The test mode disables the 96h rule, leaving all other behavior unchanged.
final boolean isIvTestModeActive = ivUpdateTestModeActive;
final boolean flag = allowIvIndexRecoveryOver42;
if (!receivedBeacon.canOverwrite(lastIvIndex, lastTransitionDate, isIvRecoveryActive, isIvTestModeActive, flag)) {
String numberOfHoursSinceDate = ((Calendar.getInstance().getTimeInMillis() - lastTransitionDate.getTimeInMillis()) / (3600 * 1000)) + "h";
LOG.info("Discarding beacon " + receivedBeacon.getIvIndex() + ", last " + lastIvIndex.getIvIndex() + ", changed: " + numberOfHoursSinceDate + "ago, test mode: " + ivUpdateTestModeActive);
return;
}
final IvIndex receivedIvIndex = receivedBeacon.getIvIndex();
mMeshNetwork.ivIndex = new IvIndex(receivedIvIndex.getIvIndex(), receivedIvIndex.isIvUpdateActive(), lastTransitionDate);
if (mMeshNetwork.ivIndex.getIvIndex() > lastIvIndex.getIvIndex()) {
LOG.info("Applying: " + mMeshNetwork.ivIndex.getIvIndex());
}
// the Node shall reset the sequence number to 0x000000.
if (mMeshNetwork.ivIndex.getTransmitIvIndex() > lastIvIndex.getTransmitIvIndex()) {
LOG.info("Resetting local sequence numbers to 0");
final Provisioner provisioner = mMeshNetwork.getSelectedProvisioner();
final ProvisionedMeshNode node = mMeshNetwork.getNode(provisioner.getProvisionerUuid());
node.setSequenceNumber(0);
}
// Updating the iv recovery flag
if (lastIvIndex != mMeshNetwork.ivIndex) {
final boolean ivRecovery = mMeshNetwork.getIvIndex().getIvIndex() > lastIvIndex.getIvIndex() + 1 && !receivedBeacon.getIvIndex().isIvUpdateActive();
mMeshNetwork.getIvIndex().setIvRecoveryFlag(ivRecovery);
}
if (!mMeshNetwork.ivIndex.getIvRecoveryFlag()) {
final Iterator<Map.Entry<Integer, ArrayList<Integer>>> iterator = mMeshNetwork.networkExclusions.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<Integer, ArrayList<Integer>> exclusions = iterator.next();
final int expectedIncrement = exclusions.getKey() + 2;
if (mMeshNetwork.ivIndex.getIvIndex() >= expectedIncrement) {
// Clear the last known sequence number of addresses that are to be removed from the exclusion list.
// Decided to retain the last known sequence number as the IV Indexes increment the sequence number
// will be greater than the last known anyways
// for (Integer address : mMeshNetwork.networkExclusions.get(expectedIncrement)) {
// mMeshNetwork.sequenceNumbers.removeAt(address);
// }
iterator.remove();
}
}
}
}
}
break;
case PDU_TYPE_PROXY_CONFIGURATION:
// Proxy configuration
LOG.info("Received proxy configuration message: " + MeshParserUtils.bytesToHex(unsegmentedPdu, true));
mMeshMessageHandler.parseMeshPduNotifications(unsegmentedPdu, mMeshNetwork);
break;
case PDU_TYPE_PROVISIONING:
// Provisioning PDU
LOG.info("Received provisioning message: " + MeshParserUtils.bytesToHex(unsegmentedPdu, true));
mMeshProvisioningHandler.parseProvisioningNotifications(unsegmentedPdu);
break;
}
} catch (ExtendedInvalidCipherTextException ex) {
// TODO handle decryption failure
} catch (IllegalArgumentException ex) {
LOG.severe("Parsing notification failed: " + MeshParserUtils.bytesToHex(unsegmentedPdu, true) + " - " + ex.getMessage());
}
}
use of org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException in project openremote by openremote.
the class NetworkLayer method parseAccessMessage.
/**
* Parses access message
*
* @param key Network Key used to decrypt
* @param data Received from the node.
* @param networkHeader De-obfuscated network header.
* @param decryptedNetworkPayload Decrypted network payload.
* @param src Source address.
* @param sequenceNumber Sequence number of the received message.
* @param ivIndex IV Index used for decryption.
* @return access message
*/
private AccessMessage parseAccessMessage(final NetworkKey key, final byte[] data, final byte[] networkHeader, final byte[] decryptedNetworkPayload, final int src, final byte[] sequenceNumber, int ivIndex) throws ExtendedInvalidCipherTextException {
try {
int receivedTtl = networkHeader[0] & 0x7F;
final int dst = MeshParserUtils.unsignedBytesToInt(decryptedNetworkPayload[1], decryptedNetworkPayload[0]);
LOG.info("Dst: " + MeshAddress.formatAddress(dst, true));
if (isSegmentedMessage(decryptedNetworkPayload[2])) {
LOG.info("Received a segmented access message from: " + MeshAddress.formatAddress(src, false));
// Ideal case this check is not needed but let's leave it for now.
if (!mMeshNode.hasUnicastAddress(src)) {
LOG.info("Segment received is from a different src than the one we are processing, let's drop it");
return null;
}
if (segmentedAccessMessagesMessages == null) {
segmentedAccessMessagesMessages = new HashMap<>();
segmentedAccessMessagesMessages.put(0, data);
} else {
final int k = segmentedAccessMessagesMessages.size();
segmentedAccessMessagesMessages.put(k, data);
}
// Removing the mDst here
final byte[] pdu = ByteBuffer.allocate(2 + networkHeader.length + decryptedNetworkPayload.length).order(ByteOrder.BIG_ENDIAN).put(data, 0, 2).put(networkHeader).put(decryptedNetworkPayload).array();
// Spec states, section 3.5.2.4 page 77
// If the received segments were sent with TTL set to 0, it is recommended that the
// corresponding Segment Acknowledgment message is sent with TTL set to 0.
final int ttl = receivedTtl == 0 ? receivedTtl : mNetworkLayerCallbacks.getProvisioner().getGlobalTtl();
final AccessMessage message = parseSegmentedAccessLowerTransportPDU(ttl, pdu, ivIndex, sequenceNumber);
if (message != null) {
// final SparseArray<byte[]> segmentedMessages = segmentedAccessMessagesMessages.clone();
Map<Integer, byte[]> segmentedMessages = new HashMap<>();
for (Integer index : segmentedAccessMessagesMessages.keySet()) {
segmentedMessages.put(index, segmentedAccessMessagesMessages.get(index).clone());
}
segmentedAccessMessagesMessages = null;
message.setNetworkKey(key);
message.setIvIndex(MeshParserUtils.intToBytes(ivIndex));
message.setNetworkLayerPdu(segmentedMessages);
message.setTtl(receivedTtl);
message.setSrc(src);
message.setDst(dst);
parseUpperTransportPDU(message);
parseAccessLayerPDU(message);
}
return message;
} else {
// Removing the mDst here
final byte[] pdu = ByteBuffer.allocate(2 + networkHeader.length + decryptedNetworkPayload.length).order(ByteOrder.BIG_ENDIAN).put(data, 0, 2).put(networkHeader).put(decryptedNetworkPayload).array();
final AccessMessage message = parseUnsegmentedAccessLowerTransportPDU(pdu, ivIndex, sequenceNumber);
if (message == null)
return null;
message.setNetworkKey(key);
message.setIvIndex(MeshParserUtils.intToBytes(ivIndex));
final Map<Integer, byte[]> pduArray = new HashMap<>();
pduArray.put(0, data);
message.setNetworkLayerPdu(pduArray);
message.setTtl(receivedTtl);
message.setSrc(src);
message.setDst(dst);
message.setSequenceNumber(sequenceNumber);
parseUpperTransportPDU(message);
parseAccessLayerPDU(message);
return message;
}
} catch (InvalidCipherTextException ex) {
throw new ExtendedInvalidCipherTextException(ex.getMessage(), ex.getCause(), NetworkLayer.class.getSimpleName());
}
}
use of org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException in project openremote by openremote.
the class NetworkLayer method parseControlMessage.
/**
* Parses control message
*
* @param key Network Key used to decrypt
* @param provisionerAddress Provisioner address.
* @param data Data received from the node.
* @param networkHeader De-obfuscated network header.
* @param decryptedNetworkPayload Decrypted network payload.
* @param src Source address where the pdu originated from.
* @param sequenceNumber Sequence number of the received message.
* @return a complete {@link ControlMessage} or null if the message was unable to parsed
*/
private ControlMessage parseControlMessage(final NetworkKey key, /* @Nullable */
final Integer provisionerAddress, final byte[] data, final byte[] networkHeader, final byte[] decryptedNetworkPayload, final int src, final byte[] sequenceNumber) throws ExtendedInvalidCipherTextException {
try {
final int ttl = networkHeader[0] & 0x7F;
final int dst = MeshParserUtils.unsignedBytesToInt(decryptedNetworkPayload[1], decryptedNetworkPayload[0]);
// Removing the mDst here
final byte[] decryptedProxyPdu = ByteBuffer.allocate(2 + networkHeader.length + decryptedNetworkPayload.length).order(ByteOrder.BIG_ENDIAN).put(data, 0, 2).put(networkHeader).put(decryptedNetworkPayload).array();
// We check the pdu type
final int pduType = data[0];
switch(pduType) {
case MeshManagerApi.PDU_TYPE_NETWORK:
// This is not possible however let's return null
if (provisionerAddress == null) {
return null;
}
// Check if the message is directed to us, if its not ignore the message
if (provisionerAddress != dst) {
LOG.info("Received a control message that was not directed to us, so we drop it");
return null;
}
if (isSegmentedMessage(decryptedNetworkPayload[2])) {
return parseSegmentedControlMessage(key, data, decryptedProxyPdu, ttl, src, dst);
} else {
return parseUnsegmentedControlMessage(key, data, decryptedProxyPdu, ttl, src, dst, sequenceNumber);
}
case MeshManagerApi.PDU_TYPE_PROXY_CONFIGURATION:
// Proxy configuration messages are segmented only at the gatt level
return parseUnsegmentedControlMessage(key, data, decryptedProxyPdu, ttl, src, dst, sequenceNumber);
default:
return null;
}
} catch (InvalidCipherTextException ex) {
throw new ExtendedInvalidCipherTextException(ex.getMessage(), ex.getCause(), NetworkLayer.class.getSimpleName());
}
}
use of org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException in project openremote by openremote.
the class BaseMeshMessageHandler method parseMeshPduNotifications.
/**
* Parse the mesh network/proxy pdus
* <p>
* This method will try to network layer de-obfuscation and decryption using the available network keys
* </p>
*
* @param pdu mesh pdu that was sent
* @param network {@link MeshNetwork}
*/
protected synchronized void parseMeshPduNotifications(final byte[] pdu, final MeshNetwork network) throws ExtendedInvalidCipherTextException {
final List<NetworkKey> networkKeys = network.getNetKeys();
final int ivi = ((pdu[1] & 0xFF) >>> 7) & 0x01;
final int nid = pdu[1] & 0x7F;
final int acceptedIvIndex = network.getIvIndex().getIvIndex();
int ivIndex = acceptedIvIndex == 0 ? 0 : acceptedIvIndex - 1;
int tempIvIndex = ivIndex;
NetworkKey networkKey = null;
SecureUtils.K2Output k2Output = null;
byte[] networkHeader = null;
int ctlTtl = 0;
int ctl = 0;
int src = 0;
ProvisionedMeshNode node = null;
while (tempIvIndex <= ivIndex + 1) {
// Here we go through all the network keys and filter out network keys based on the nid.
for (int i = 0; i < networkKeys.size(); i++) {
networkKey = networkKeys.get(i);
k2Output = getMatchingK2Output(networkKey, nid);
if (k2Output != null) {
networkHeader = deObfuscateNetworkHeader(pdu, MeshParserUtils.intToBytes(tempIvIndex), k2Output.getPrivacyKey());
ctlTtl = networkHeader[0];
ctl = (ctlTtl >> 7) & 0x01;
src = MeshParserUtils.unsignedBytesToInt(networkHeader[5], networkHeader[4]);
// Check if the src is known to the network and if found let's break
// Note a node may not be found if there are two provisioners are operating independently without syncing the network.
node = network.getNode(src);
if (node != null) {
break;
}
}
}
// IF the node was found we can safely try to decrypt message with the network key which we found src of the message.
if (node != null && k2Output != null) {
final byte[] sequenceNumber = ByteBuffer.allocate(3).order(ByteOrder.BIG_ENDIAN).put(networkHeader, 1, 3).array();
LOG.info("Sequence number of received Network PDU: " + MeshParserUtils.convert24BitsToInt(sequenceNumber));
// TODO validate ivi
byte[] nonce;
try {
final int networkPayloadLength = pdu.length - (2 + networkHeader.length);
final byte[] transportPdu = new byte[networkPayloadLength];
System.arraycopy(pdu, 8, transportPdu, 0, networkPayloadLength);
final byte[] decryptedPayload;
final MeshMessageState state;
if (pdu[0] == MeshManagerApi.PDU_TYPE_NETWORK) {
nonce = createNetworkNonce((byte) ctlTtl, sequenceNumber, src, MeshParserUtils.intToBytes(tempIvIndex));
decryptedPayload = SecureUtils.decryptCCM(transportPdu, k2Output.getEncryptionKey(), nonce, SecureUtils.getNetMicLength(ctl));
state = getState(src);
} else {
nonce = createProxyNonce(sequenceNumber, src, MeshParserUtils.intToBytes(tempIvIndex));
decryptedPayload = SecureUtils.decryptCCM(transportPdu, k2Output.getEncryptionKey(), nonce, SecureUtils.getNetMicLength(ctl));
state = getState(MeshAddress.UNASSIGNED_ADDRESS);
}
if (state != null) {
// TODO look in to proxy filter messages
((DefaultNoOperationMessageState) state).parseMeshPdu(networkKey, node, pdu, networkHeader, decryptedPayload, tempIvIndex, sequenceNumber);
return;
}
} catch (InvalidCipherTextException ex) {
throw new ExtendedInvalidCipherTextException(ex.getMessage(), ex.getCause(), BaseMeshMessageHandler.class.getName());
}
}
tempIvIndex++;
}
}
Aggregations