Search in sources :

Example 1 with ExtendedInvalidCipherTextException

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());
    }
}
Also used : InvalidCipherTextException(org.bouncycastle.crypto.InvalidCipherTextException) ExtendedInvalidCipherTextException(org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException) ExtendedInvalidCipherTextException(org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException) ByteBuffer(java.nio.ByteBuffer)

Example 2 with ExtendedInvalidCipherTextException

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());
    }
}
Also used : ProvisionedMeshNode(org.openremote.agent.protocol.bluetooth.mesh.transport.ProvisionedMeshNode) Calendar(java.util.Calendar) ArrayList(java.util.ArrayList) ExtendedInvalidCipherTextException(org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException) Map(java.util.Map)

Example 3 with ExtendedInvalidCipherTextException

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());
    }
}
Also used : InvalidCipherTextException(org.bouncycastle.crypto.InvalidCipherTextException) ExtendedInvalidCipherTextException(org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException) HashMap(java.util.HashMap) ExtendedInvalidCipherTextException(org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException)

Example 4 with ExtendedInvalidCipherTextException

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());
    }
}
Also used : InvalidCipherTextException(org.bouncycastle.crypto.InvalidCipherTextException) ExtendedInvalidCipherTextException(org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException) ExtendedInvalidCipherTextException(org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException)

Example 5 with ExtendedInvalidCipherTextException

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++;
    }
}
Also used : NetworkKey(org.openremote.agent.protocol.bluetooth.mesh.NetworkKey) InvalidCipherTextException(org.bouncycastle.crypto.InvalidCipherTextException) ExtendedInvalidCipherTextException(org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException) ExtendedInvalidCipherTextException(org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException) SecureUtils(org.openremote.agent.protocol.bluetooth.mesh.utils.SecureUtils)

Aggregations

ExtendedInvalidCipherTextException (org.openremote.agent.protocol.bluetooth.mesh.utils.ExtendedInvalidCipherTextException)5 InvalidCipherTextException (org.bouncycastle.crypto.InvalidCipherTextException)4 ByteBuffer (java.nio.ByteBuffer)1 ArrayList (java.util.ArrayList)1 Calendar (java.util.Calendar)1 HashMap (java.util.HashMap)1 Map (java.util.Map)1 NetworkKey (org.openremote.agent.protocol.bluetooth.mesh.NetworkKey)1 ProvisionedMeshNode (org.openremote.agent.protocol.bluetooth.mesh.transport.ProvisionedMeshNode)1 SecureUtils (org.openremote.agent.protocol.bluetooth.mesh.utils.SecureUtils)1