2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.loxone.internal.security;
15 import java.security.InvalidKeyException;
16 import java.security.NoSuchAlgorithmException;
17 import java.util.concurrent.locks.Lock;
18 import java.util.concurrent.locks.ReentrantLock;
19 import java.util.function.BiConsumer;
21 import javax.crypto.Mac;
22 import javax.crypto.spec.SecretKeySpec;
24 import org.openhab.binding.loxone.internal.LxServerHandler;
25 import org.openhab.binding.loxone.internal.LxServerHandlerApi;
26 import org.openhab.binding.loxone.internal.LxWebSocket;
27 import org.openhab.binding.loxone.internal.types.LxErrorCode;
28 import org.openhab.binding.loxone.internal.types.LxResponse;
29 import org.openhab.binding.loxone.internal.types.LxWsSecurityType;
30 import org.openhab.core.util.HexUtils;
33 * Security abstract class providing authentication and encryption services.
34 * Used by the {@link LxServerHandler} during connection establishment to authenticate user and during message exchange
35 * for encryption and decryption or the messages.
37 * @author Pawel Pieczul - initial contribution
40 public abstract class LxWsSecurity {
43 final String password;
44 final LxWebSocket socket;
45 final LxServerHandlerApi thingHandler;
49 private String details;
50 private boolean cancel = false;
51 private final Lock authenticationLock = new ReentrantLock();
54 * Create an authentication instance.
56 * @param debugId instance of the client used for debugging purposes only
57 * @param thingHandler API to the thing handler
58 * @param socket websocket to perform communication with Miniserver
59 * @param user user to authenticate
60 * @param password password to authenticate
62 LxWsSecurity(int debugId, LxServerHandlerApi thingHandler, LxWebSocket socket, String user, String password) {
63 this.debugId = debugId;
64 this.thingHandler = thingHandler;
67 this.password = password;
71 * Initiate user authentication. This method will return immediately and authentication will be done in a separate
72 * thread asynchronously. On successful or unsuccessful completion, a provided callback will be called with
73 * information about failed reason and details of failure. In case of success, the reason value will be
74 * {@link LxErrorCode#OK}
75 * Only one authentication can run in parallel and must be performed sequentially (create no more threads).
77 * @param doneCallback callback to execute when authentication is finished or failed
79 public void authenticate(BiConsumer<LxErrorCode, String> doneCallback) {
80 Runnable init = () -> {
81 authenticationLock.lock();
84 doneCallback.accept(reason, details);
86 authenticationLock.unlock();
89 new Thread(init).start();
93 * Perform user authentication using a specific authentication algorithm.
94 * This method will be executed in a dedicated thread to allow sending synchronous messages to the Miniserver.
96 * @return true when authentication granted
98 abstract boolean execute();
101 * Cancel authentication procedure and any pending activities.
102 * It is supposed to be overridden by implementing classes.
104 public void cancel() {
109 * Check a response received from the Miniserver for errors, interpret it and store the results in class fields.
111 * @param response response received from the Miniserver
112 * @return {@link LxErrorCode#OK} when response is correct or a specific {@link LxErrorCode}
114 boolean checkResponse(LxResponse response) {
115 if (response == null || response.subResponse == null || cancel) {
116 reason = LxErrorCode.COMMUNICATION_ERROR;
119 reason = response.getResponseCode();
120 return (reason == LxErrorCode.OK);
124 * Hash string (e.g. containing user name and password or token) according to the algorithm required by the
127 * @param string string to be hashed
128 * @param hashKeyHex hash key received from the Miniserver in hex format
129 * @param sha256 if SHA-256 algorithm should be used (SHA-1 otherwise)
130 * @return hashed string or null if failed
132 String hashString(String string, String hashKeyHex, boolean sha256) {
133 if (string == null || hashKeyHex == null) {
137 String alg = sha256 ? "HmacSHA256" : "HmacSHA1";
138 byte[] hashKeyBytes = HexUtils.hexToBytes(hashKeyHex);
139 SecretKeySpec signKey = new SecretKeySpec(hashKeyBytes, alg);
140 Mac mac = Mac.getInstance(alg);
142 byte[] rawData = mac.doFinal(string.getBytes());
143 return HexUtils.bytesToHex(rawData);
144 } catch (NoSuchAlgorithmException | InvalidKeyException e) {
150 * Encrypt string using current encryption algorithm.
152 * @param string input string to encrypt
153 * @return encrypted string
155 public String encrypt(String string) {
156 // by default no encryption
161 * Check if control is encrypted and decrypt it using current decryption algorithm.
162 * If control is not encrypted or decryption is not available or not ready, the control should be returned in its
165 * @param control control to be decrypted
166 * @return decrypted control or original control in case decryption is unavailable, control is not encrypted or
167 * other issue occurred
169 public String decryptControl(String control) {
170 // by default no decryption
175 * Set error code and return false. It is used to report detailed error information from inside the algorithms.
177 * @param reason reason for failure
178 * @param details details of the failure
179 * @return always false
181 boolean setError(LxErrorCode reason, String details) {
182 if (reason != null) {
183 this.reason = reason;
185 if (details != null) {
186 this.details = details;
192 * Create an authentication instance.
194 * @param type type of security algorithm
195 * @param swVersion Miniserver's software version or null if unknown
196 * @param debugId instance of the client used for debugging purposes only
197 * @param thingHandler API to the thing handler
198 * @param socket websocket to perform communication with Miniserver
199 * @param user user to authenticate
200 * @param password password to authenticate
201 * @return created security object
203 public static LxWsSecurity create(LxWsSecurityType type, String swVersion, int debugId,
204 LxServerHandlerApi thingHandler, LxWebSocket socket, String user, String password) {
205 LxWsSecurityType securityType = type;
206 if (securityType == LxWsSecurityType.AUTO && swVersion != null) {
207 String[] versions = swVersion.split("[.]");
208 if (versions != null && versions.length > 0 && Integer.parseInt(versions[0]) <= 8) {
209 securityType = LxWsSecurityType.HASH;
211 securityType = LxWsSecurityType.TOKEN;
214 if (securityType == LxWsSecurityType.HASH) {
215 return new LxWsSecurityHash(debugId, thingHandler, socket, user, password);
217 return new LxWsSecurityToken(debugId, thingHandler, socket, user, password);