Search in sources :

Example 1 with Nonce

use of org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveSecureNonceTracker.Nonce 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)

Example 2 with Nonce

use of org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveSecureNonceTracker.Nonce in project openhab1-addons by openhab.

the class ZWaveSecurityCommandClass method decryptMessage.

/**
     * Decrypts a security encapsulated message from the Z-Wave network. Ideally this would return
     * a {@link SerialMessage} but we don't have enough data to do so. So we just return the
     * decrypted payload bytes
     *
     * @param offset the offset at which the command byte exists
     * @param endpoint
     * @param messagePayload
     * @return the decrypted payload bytes. 0=command class, 1=command, 2+=payload
     */
public byte[] decryptMessage(byte[] data, int offset) {
    if (!checkRealNetworkKeyLoaded()) {
        return null;
    }
    traceHex("in decryptMessage starting at offset, buffer is", data, offset);
    ByteArrayInputStream bais = new ByteArrayInputStream(data);
    // check for minimum size here so we can ignore the return value of bais.read() below
    int minimumSize = offset + ENCAPSULATED_HEADER_LENGTH + ENCAPSULATED_FOOTER_LENGTH;
    if (data.length < minimumSize) {
        logger.error("NODE {}: Dropping security encapsulated packet which is too small:  min={}, actual={}", this.getNode().getNodeId(), minimumSize, data.length);
        return null;
    }
    try {
        // advance to the command byte
        bais.read(new byte[offset]);
        byte command = (byte) bais.read();
        byte[] initializationVector = new byte[IV_LENGTH];
        // the next 8 bytes of packet are the nonce generated by the device for the IV
        bais.read(initializationVector, 0, HALF_OF_IV);
        traceHex("device nonce", initializationVector, 0, HALF_OF_IV);
        int ciphertextSize = data.length - offset - ENCAPSULATED_HEADER_LENGTH - ENCAPSULATED_FOOTER_LENGTH + 1;
        // Next are the ciphertext bytes
        byte[] ciphertextBytes = new byte[ciphertextSize];
        bais.read(ciphertextBytes);
        logger.trace("NODE {}: Encrypted Packet Sizes: total={}, encrypted={}", this.getNode().getNodeId(), data.length, ciphertextSize);
        traceHex("ciphertextBytes", ciphertextBytes);
        // We stored the nonce that we sent to the device, retrieve it by the id so we can use it in the IV
        byte nonceId = (byte) bais.read();
        Nonce nonceWeSentToDevice = nonceGeneration.getNonceWeGeneratedById(nonceId);
        if (nonceWeSentToDevice == null) {
            // Error message logged in ZWaveSecureNonceTracker, just return
            return null;
        }
        System.arraycopy(nonceWeSentToDevice.getNonceBytes(), 0, initializationVector, HALF_OF_IV, HALF_OF_IV);
        traceHex("IV", initializationVector);
        byte[] macFromPacket = new byte[MAC_LENGTH];
        bais.read(macFromPacket);
        Cipher cipher = Cipher.getInstance("AES/OFB/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, encryptKey, new IvParameterSpec(initializationVector));
        byte[] plaintextBytes = cipher.doFinal(ciphertextBytes);
        traceHex("plaintextBytes", plaintextBytes);
        byte driverNodeId = (byte) this.getController().getOwnNodeId();
        byte[] mac = generateMAC(command, ciphertextBytes, (byte) this.getNode().getNodeId(), driverNodeId, initializationVector);
        if (Arrays.equals(mac, macFromPacket)) {
            logger.trace("NODE {}: MAC Authentication of packet verified OK", this.getNode().getNodeId());
        } else {
            logger.error("NODE {}: MAC Authentication of packet failed. dropping", this.getNode().getNodeId());
            traceHex("full packet", data);
            traceHex("package mac", macFromPacket);
            traceHex("our mac", mac);
            if (DROP_PACKETS_ON_MAC_FAILURE) {
                return null;
            } else {
                logger.error("NODE {}: Just kidding, ignored failed MAC Authentication of packet", this.getNode().getNodeId());
            }
        }
        byte sequenceDataByte = plaintextBytes[0];
        if (sequenceDataByte != ZWaveSecurityPayloadFrame.SEQUENCE_BYTE_FOR_SINGLE_FRAME_MESSAGE) {
            // This is a multi frame message which is not yet supported
            logger.error("NODE {}: Received multi frmae message which is not supported.  Please post this to the OpenHab" + "mailing list so it can be fixed!  bytes=", this.getNode().getNodeId(), SerialMessage.bb2hex(plaintextBytes));
            return null;
        }
        // so we know if we got something that's not supported
        logger.debug("NODE {}: decrypted bytes {}", getNode().getNodeId(), SerialMessage.bb2hex(plaintextBytes));
        if (lastEncapsulatedRequstMessage != null) {
            lastEncapsulatedRequstMessage.securityReponseReceived(plaintextBytes);
        }
        notifyEncapsulationThread();
        return plaintextBytes;
    } catch (Exception e) {
        logger.error("NODE {}: Error decrypting packet", getNode().getNodeId(), e);
        return null;
    }
}
Also used : Nonce(org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveSecureNonceTracker.Nonce) ByteArrayInputStream(java.io.ByteArrayInputStream) IvParameterSpec(javax.crypto.spec.IvParameterSpec) Cipher(javax.crypto.Cipher) ZWaveEndpoint(org.openhab.binding.zwave.internal.protocol.ZWaveEndpoint) GeneralSecurityException(java.security.GeneralSecurityException) IOException(java.io.IOException)

Aggregations

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