logger.debug("Doorbird returned json response: {}", infoResponse);
doorbirdInfo = new DoorbirdInfo(infoResponse);
} catch (IOException e) {
- logger.info("Unable to communicate with Doorbird: {}", e.getMessage());
+ logger.warn("Unable to communicate with Doorbird: {}", e.getMessage());
} catch (JsonSyntaxException e) {
- logger.info("Unable to parse Doorbird response: {}", e.getMessage());
+ logger.warn("Unable to parse Doorbird response: {}", e.getMessage());
} catch (DoorbirdUnauthorizedException e) {
logAuthorizationError("getDoorbirdName");
}
return doorbirdInfo;
}
+ public @Nullable DoorbirdSession getSession() {
+ DoorbirdSession doorbirdSession = null;
+ try {
+ String sessionResponse = executeGetRequest("/bha-api/getsession.cgi");
+ doorbirdSession = new DoorbirdSession(sessionResponse);
+ } catch (IOException e) {
+ logger.warn("Unable to communicate with Doorbird: {}", e.getMessage());
+ } catch (JsonSyntaxException e) {
+ logger.warn("Unable to parse Doorbird response: {}", e.getMessage());
+ } catch (DoorbirdUnauthorizedException e) {
+ logAuthorizationError("getDoorbirdName");
+ }
+ return doorbirdSession;
+ }
+
public @Nullable SipStatus getSipStatus() {
SipStatus sipStatus = null;
try {
logger.debug("Doorbird returned json response: {}", statusResponse);
sipStatus = new SipStatus(statusResponse);
} catch (IOException e) {
- logger.info("Unable to communicate with Doorbird: {}", e.getMessage());
+ logger.warn("Unable to communicate with Doorbird: {}", e.getMessage());
} catch (JsonSyntaxException e) {
- logger.info("Unable to parse Doorbird response: {}", e.getMessage());
+ logger.warn("Unable to parse Doorbird response: {}", e.getMessage());
} catch (DoorbirdUnauthorizedException e) {
logAuthorizationError("getSipStatus");
}
String response = executeGetRequest("/bha-api/light-on.cgi");
logger.debug("Response={}", response);
} catch (IOException e) {
- logger.debug("IOException turning on light: {}", e.getMessage());
+ logger.warn("IOException turning on light: {}", e.getMessage());
} catch (DoorbirdUnauthorizedException e) {
logAuthorizationError("lightOn");
}
String response = executeGetRequest("/bha-api/restart.cgi");
logger.debug("Response={}", response);
} catch (IOException e) {
- logger.debug("IOException restarting device: {}", e.getMessage());
+ logger.warn("IOException restarting device: {}", e.getMessage());
} catch (DoorbirdUnauthorizedException e) {
logAuthorizationError("restart");
}
String response = executeGetRequest("/bha-api/sip.cgi?action=hangup");
logger.debug("Response={}", response);
} catch (IOException e) {
- logger.debug("IOException hanging up SIP call: {}", e.getMessage());
+ logger.warn("IOException hanging up SIP call: {}", e.getMessage());
} catch (DoorbirdUnauthorizedException e) {
logAuthorizationError("sipHangup");
}
}
private void logAuthorizationError(String operation) {
- logger.info("Authorization info is not set or is incorrect on call to '{}' API", operation);
+ logger.warn("Authorization info is not set or is incorrect on call to '{}' API", operation);
}
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.doorbird.internal.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.doorbird.internal.model.GetsessionDTO;
+import org.openhab.binding.doorbird.internal.model.GetsessionDTO.GetsessionBha;
+
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link DoorbirdSession} holds information about the Doorbird session,
+ * including the v2 decryption key for notification events.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public class DoorbirdSession {
+ private @Nullable String returnCode;
+ private @Nullable String sessionId;
+ private @Nullable String decryptionKey;
+
+ public DoorbirdSession(String infoJson) throws JsonSyntaxException {
+ GetsessionDTO session = DoorbirdAPI.fromJson(infoJson, GetsessionDTO.class);
+ if (session != null) {
+ GetsessionBha bha = session.bha;
+ returnCode = bha.returnCode;
+ sessionId = bha.sessionId;
+ decryptionKey = bha.decryptionKey;
+ }
+ }
+
+ public @Nullable String getReturnCode() {
+ return returnCode;
+ }
+
+ public @Nullable String getSessionId() {
+ return sessionId;
+ }
+
+ public @Nullable String getDecryptionKey() {
+ return decryptionKey;
+ }
+}
import org.openhab.binding.doorbird.internal.action.DoorbirdActions;
import org.openhab.binding.doorbird.internal.api.DoorbirdAPI;
import org.openhab.binding.doorbird.internal.api.DoorbirdImage;
+import org.openhab.binding.doorbird.internal.api.DoorbirdSession;
import org.openhab.binding.doorbird.internal.api.SipStatus;
import org.openhab.binding.doorbird.internal.audio.DoorbirdAudioSink;
import org.openhab.binding.doorbird.internal.config.DoorbellConfiguration;
private DoorbirdAPI api = new DoorbirdAPI();
+ private @Nullable DoorbirdSession session;
+
private BundleContext bundleContext;
private @Nullable ServiceRegistration<AudioSink> audioSinkRegistration;
}
api.setAuthorization(host, user, password);
api.setHttpClient(httpClient);
+ session = api.getSession();
startImageRefreshJob();
startUDPListenerJob();
startAudioSink();
updateMotionMontage();
}
+ // Callback used by listener to get session object
+ public @Nullable DoorbirdSession getSession() {
+ return session;
+ }
+
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Got command {} for channel {} of thing {}", command, channelUID, getThing().getUID());
* - if both of these attempts fail, the binding will be functional, except for
* its ability to decrypt the UDP events.
*/
- @NonNullByDefault
private static class LazySodiumJavaHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(LazySodiumJavaHolder.class);
* The following functions support the decryption of the doorbell event
* using the LazySodium wrapper for the libsodium crypto library
*/
- public void decrypt(DatagramPacket p, String password) {
+ public void decrypt(DatagramPacket p, String password, @Nullable String v2DecryptionKey) {
isDoorbellEvent = false;
int length = p.getLength();
if (version == 1) {
// Decrypt using version 1 decryption scheme
decryptV1(bb, passwordFirstFive);
+ } else if (version == 2) {
+ // Decrypt using version 2 decryption scheme
+ decryptV2(bb, v2DecryptionKey);
} else {
logger.info("Don't know how to decrypt version {} doorbell event", version);
}
private void decryptV1(ByteBuffer bb, String password5) throws IndexOutOfBoundsException, BufferUnderflowException {
LazySodiumJava sodium = getLazySodiumJavaInstance();
if (sodium == null) {
- logger.debug("Unable to decrypt event because libsodium is not loaded");
+ logger.debug("Unable to decrypt v1 event because libsodium is not loaded");
return;
}
if (bb.capacity() != 70) {
logger.info("Received malformed version 1 doorbell event, length not 70 bytes");
return;
}
+
+ logger.debug("Decrypting v1 event, size of buffer: {}", bb.capacity());
+
// opslimit and memlimit are 4 bytes each
opslimit = bb.getInt();
memlimit = bb.getInt();
logger.trace("Decryption FAILED");
return;
}
+ getFieldsFromDecryptedText(m, mLen);
+ }
+
+ private void decryptV2(ByteBuffer bb, @Nullable String v2DecryptionKey)
+ throws IndexOutOfBoundsException, BufferUnderflowException {
+ LazySodiumJava sodium = getLazySodiumJavaInstance();
+ if (sodium == null) {
+ logger.debug("Unable to decrypt v2 event because libsodium is not loaded");
+ return;
+ }
+
+ if (v2DecryptionKey == null) {
+ logger.debug("Unable to decrypt v2 event because decryption key is null");
+ return;
+ }
+
+ logger.debug("Decrypting v2 event, size of buffer: {}", bb.capacity());
+
+ // Get nonce and ciphertext arrays
+ bb.get(nonce, 0, nonce.length);
+ bb.get(ciphertext, 0, ciphertext.length);
+
+ // Set up the variables for the decryption algorithm
+ byte[] m = new byte[30];
+ long[] mLen = new long[30];
+ byte[] nSec = null;
+ byte[] c = ciphertext;
+ long cLen = ciphertext.length;
+ byte[] ad = null;
+ long adLen = 0;
+ byte[] nPub = nonce;
+ byte[] k = v2DecryptionKey.getBytes();
+
+ // Decrypt the ciphertext
+ logger.trace("Call cryptoAeadChaCha20Poly1305Decrypt with ciphertext='{}', nonce='{}', key='{}'",
+ HexUtils.bytesToHex(ciphertext, " "), HexUtils.bytesToHex(nonce, " "), HexUtils.bytesToHex(k, " "));
+ boolean success = sodium.cryptoAeadChaCha20Poly1305Decrypt(m, mLen, nSec, c, cLen, ad, adLen, nPub, k);
+ if (!success) {
+ /*
+ * Don't log at debug level since the decryption will fail for events encrypted with
+ * passwords other than the password contained in the thing configuration (reference API
+ * documentation for details)
+ */
+ logger.trace("Decryption FAILED");
+ return;
+ }
+ getFieldsFromDecryptedText(m, mLen);
+ }
+
+ private void getFieldsFromDecryptedText(byte[] m, long[] mLen) {
int decryptedTextLength = (int) mLen[0];
if (decryptedTextLength != 18L) {
logger.info("Length of decrypted text is invalid, must be 18 bytes");
return;
}
+
// Get event fields from decrypted text
logger.debug("Received and successfully decrypted a Doorbird event!!");
ByteBuffer b = ByteBuffer.allocate(decryptedTextLength);
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.doorbird.internal.api.DoorbirdSession;
import org.openhab.binding.doorbird.internal.handler.DoorbellHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
return;
}
+ DoorbirdSession session = thingHandler.getSession();
+ String v2DecryptionKey = (session != null ? session.getDecryptionKey() : null);
String userId = thingHandler.getUserId();
String userPassword = thingHandler.getUserPassword();
if (userId == null || userPassword == null) {
return;
}
try {
- event.decrypt(packet, userPassword);
+ event.decrypt(packet, userPassword, v2DecryptionKey);
} catch (RuntimeException e) {
// The libsodium library might generate a runtime exception if the packet is malformed
logger.info("DoorbirdEvent got unhandled exception: {}", e.getMessage(), e);
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.doorbird.internal.model;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link GetsessionDTO} models the JSON response returned by the Doorbird in response
+ * to calling the getsession.cgi API.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+public class GetsessionDTO {
+ /**
+ * Top level container of information about the Doorbird session
+ */
+ @SerializedName("BHA")
+ public GetsessionBha bha;
+
+ public class GetsessionBha {
+ /**
+ * Return code from the Doorbird
+ */
+ @SerializedName("RETURNCODE")
+ public String returnCode;
+
+ /**
+ * Contains information about the Doorbird session
+ */
+ @SerializedName("SESSIONID")
+ public String sessionId;
+
+ /**
+ * Contains the v2 decryption key for events
+ */
+ @SerializedName("NOTIFICATION_ENCRYPTION_KEY")
+ public String decryptionKey;
+ }
+}