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);
}
}
Aggregations