Search in sources :

Example 1 with SecurityEncapsulatedSerialMessage

use of org.openhab.binding.zwave.internal.protocol.SecurityEncapsulatedSerialMessage in project openhab1-addons by openhab.

the class ZWaveSecurityCommandClass method sendNextMessageUsingDeviceNonce.

/**
     * Gets the next message from {@link #payloadEncapsulationQueue}, encapsulates (encrypts and MACs) it, then
     * transmits
     * Invoked by {@link ZWaveSecurityEncapsulationThread}. This method must only be called by
     * {@link ZWaveSecurityEncapsulationThread}
     */
protected void sendNextMessageUsingDeviceNonce() {
    checkInit();
    if (!checkRealNetworkKeyLoaded()) {
        return;
    }
    if (encryptKey == null) {
        // when loaded from xml, encrypt key will be null so we load it here
        setupNetworkKey(false);
    }
    if (payloadEncapsulationQueue.isEmpty()) {
        logger.warn("NODE {}: payloadQueue was empty, returning", this.getNode().getNodeId());
        return;
    }
    Nonce deviceNonce = nonceGeneration.getUseableDeviceNonce();
    if (deviceNonce == null) {
        SerialMessage nonceGetMessage = nonceGeneration.buildNonceGetIfNeeded();
        if (nonceGetMessage == null) {
        // Nothing to do, we are already waiting for a nonce from the device
        } else {
            transmitMessage(nonceGetMessage);
        }
        return;
    }
    // Fetch the next payload from the queue and encapsulate it
    ZWaveSecurityPayloadFrame securityPayload = payloadEncapsulationQueue.poll();
    if (securityPayload == null) {
        logger.warn("NODE {}: payloadQueue was empty, returning", this.getNode().getNodeId());
        return;
    }
    // Encapsulate the message fragment
    traceHex("SecurityPayloadBytes", securityPayload.getMessageBytes());
    // Note that we set the expected reply to that of the original message, as it can vary
    SecurityEncapsulatedSerialMessage message = new SecurityEncapsulatedSerialMessage(SerialMessageClass.SendData, SerialMessageType.Request, securityPayload.getOriginalMessage());
    message.setDeviceNonceId(deviceNonce.getNonceBytes()[0]);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    baos.write((byte) this.getNode().getNodeId());
    baos.write(securityPayload.getLength() + 20);
    baos.write(this.getCommandClass().getKey());
    byte commandByte = SECURITY_MESSAGE_ENCAP;
    if (USE_SECURITY_MESSAGE_ENCAP_NONCE_GET && !disableEncapNonceGet) {
        boolean useNonceGetMessage = false;
        if (payloadEncapsulationQueue.size() > 0) {
            useNonceGetMessage = true;
            logger.debug("NODE {}: using SECURITY_MESSAGE_ENCAP_NONCE_GET with queue size of {}", this.getNode().getNodeId(), payloadEncapsulationQueue.size());
        } else if (false) {
            // Check for messages that we know will have a follow-up request that is secure TODO: DB
            // change flag to AGGRESSIVE, etc? or just remove..
            useNonceGetMessage = bytesAreEqual(securityPayload.getMessageBytes()[0], ZWaveCommandClass.CommandClass.DOOR_LOCK.getKey()) && bytesAreEqual(securityPayload.getMessageBytes()[1], ZWaveDoorLockCommandClass.DOORLOCK_SET);
            if (useNonceGetMessage) {
                logger.debug("NODE {}: using SECURITY_MESSAGE_ENCAP_NONCE_GET since there will be a followup command", this.getNode().getNodeId());
            }
        }
        if (useNonceGetMessage) {
            commandByte = SECURITY_MESSAGE_ENCAP_NONCE_GET;
            nonceGeneration.sendingEncapNonceGet(message);
        }
    }
    logger.trace("NODE {}: Used nonce to form {} ({}).", this.getNode().getNodeId(), commandToString(commandByte), securityPayload.getLogMessage());
    baos.write(commandByte);
    // create the iv
    byte[] initializationVector = new byte[16];
    // Generate a new nonce and fill the first half of the IV buffer with it
    byte[] nonceBytes = nonceGeneration.generateNonceForEncapsulationMessage();
    System.arraycopy(nonceBytes, 0, initializationVector, 0, HALF_OF_IV);
    // the 2nd half of the IV is the nonce provided by the device
    System.arraycopy(deviceNonce.getNonceBytes(), 0, initializationVector, HALF_OF_IV, HALF_OF_IV);
    try {
        // Append the first 8 bytes of the IV (our nonce) to the message
        baos.write(initializationVector, 0, HALF_OF_IV);
        int totalParts = securityPayload.getTotalParts();
        if (totalParts < 1 || totalParts > 2) {
            logger.error("NODE {}: securityPayload had invalid number of parts: {}   Send aborted.", this.getNode().getNodeId(), totalParts);
            return;
        }
        // at most, the payload will be securityPayload length + 1 byte for the sequence byte
        byte[] plaintextMessageBytes = new byte[1 + securityPayload.getLength()];
        plaintextMessageBytes[0] = securityPayload.getSequenceByte();
        System.arraycopy(securityPayload.getMessageBytes(), 0, plaintextMessageBytes, 1, securityPayload.getLength());
        // Append the message payload after encrypting it with AES-OFB
        traceHex("Input frame for encryption:", plaintextMessageBytes);
        traceHex("IV:", initializationVector);
        // This will use hardware AES acceleration when possible (default in JDK 8)
        Cipher encryptCipher = Cipher.getInstance("AES/OFB/NoPadding");
        encryptCipher.init(Cipher.ENCRYPT_MODE, encryptKey, new IvParameterSpec(initializationVector));
        byte[] ciphertextBytes = encryptCipher.doFinal(plaintextMessageBytes);
        traceHex("Encrypted Output", ciphertextBytes);
        baos.write(ciphertextBytes);
        // Append the nonce identifier which is the first byte of the device nonce
        baos.write(deviceNonce.getNonceBytes()[0]);
        int commandClassByteOffset = 2;
        // Start at command class byte
        int toMacLength = baos.toByteArray().length - commandClassByteOffset;
        byte[] toMac = new byte[toMacLength];
        System.arraycopy(baos.toByteArray(), commandClassByteOffset, toMac, 0, toMacLength);
        // Generate the MAC
        byte sendingNode = (byte) this.getController().getOwnNodeId();
        byte[] mac = generateMAC(commandByte, ciphertextBytes, sendingNode, (byte) getNode().getNodeId(), initializationVector);
        traceHex("Auth mac", mac);
        baos.write(mac);
        byte[] payload = baos.toByteArray();
        debugHex(String.format("Outgoing encrypted message (device nonce=%02X): ", initializationVector[HALF_OF_IV]), payload);
        message.setMessagePayload(payload);
        message.setSecurityPayload(securityPayload);
        lastEncapsulatedRequstMessage = message;
        transmitMessage(message);
    } catch (GeneralSecurityException e) {
        logger.error("NODE {}: Error in sendNextMessageWithNonce, message not sent", e);
    } catch (IOException e) {
        logger.error("NODE {}: Error in sendNextMessageWithNonce, message not sent", e);
    }
}
Also used : SecurityEncapsulatedSerialMessage(org.openhab.binding.zwave.internal.protocol.SecurityEncapsulatedSerialMessage) GeneralSecurityException(java.security.GeneralSecurityException) SerialMessage(org.openhab.binding.zwave.internal.protocol.SerialMessage) SecurityEncapsulatedSerialMessage(org.openhab.binding.zwave.internal.protocol.SecurityEncapsulatedSerialMessage) ByteArrayOutputStream(java.io.ByteArrayOutputStream) IOException(java.io.IOException) ZWaveEndpoint(org.openhab.binding.zwave.internal.protocol.ZWaveEndpoint) Nonce(org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveSecureNonceTracker.Nonce) IvParameterSpec(javax.crypto.spec.IvParameterSpec) Cipher(javax.crypto.Cipher)

Aggregations

ByteArrayOutputStream (java.io.ByteArrayOutputStream)1 IOException (java.io.IOException)1 GeneralSecurityException (java.security.GeneralSecurityException)1 Cipher (javax.crypto.Cipher)1 IvParameterSpec (javax.crypto.spec.IvParameterSpec)1 SecurityEncapsulatedSerialMessage (org.openhab.binding.zwave.internal.protocol.SecurityEncapsulatedSerialMessage)1 SerialMessage (org.openhab.binding.zwave.internal.protocol.SerialMessage)1 ZWaveEndpoint (org.openhab.binding.zwave.internal.protocol.ZWaveEndpoint)1 Nonce (org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveSecureNonceTracker.Nonce)1