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