]> git.basschouten.com Git - openhab-addons.git/blob
bc724ad84d105d3d01dac42a492a5be9a8487f00
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.loxone.internal.security;
14
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;
20
21 import javax.crypto.Mac;
22 import javax.crypto.spec.SecretKeySpec;
23
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;
31
32 /**
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.
36  *
37  * @author Pawel Pieczul - initial contribution
38  *
39  */
40 public abstract class LxWsSecurity {
41     final int debugId;
42     final String user;
43     final String password;
44     final LxWebSocket socket;
45     final LxServerHandlerApi thingHandler;
46
47     LxErrorCode reason;
48
49     private String details;
50     private boolean cancel = false;
51     private final Lock authenticationLock = new ReentrantLock();
52
53     /**
54      * Create an authentication instance.
55      *
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
61      */
62     LxWsSecurity(int debugId, LxServerHandlerApi thingHandler, LxWebSocket socket, String user, String password) {
63         this.debugId = debugId;
64         this.thingHandler = thingHandler;
65         this.socket = socket;
66         this.user = user;
67         this.password = password;
68     }
69
70     /**
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).
76      *
77      * @param doneCallback callback to execute when authentication is finished or failed
78      */
79     public void authenticate(BiConsumer<LxErrorCode, String> doneCallback) {
80         Runnable init = () -> {
81             authenticationLock.lock();
82             try {
83                 execute();
84                 doneCallback.accept(reason, details);
85             } finally {
86                 authenticationLock.unlock();
87             }
88         };
89         new Thread(init).start();
90     }
91
92     /**
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.
95      *
96      * @return true when authentication granted
97      */
98     abstract boolean execute();
99
100     /**
101      * Cancel authentication procedure and any pending activities.
102      * It is supposed to be overridden by implementing classes.
103      */
104     public void cancel() {
105         cancel = true;
106     }
107
108     /**
109      * Check a response received from the Miniserver for errors, interpret it and store the results in class fields.
110      *
111      * @param response response received from the Miniserver
112      * @return {@link LxErrorCode#OK} when response is correct or a specific {@link LxErrorCode}
113      */
114     boolean checkResponse(LxResponse response) {
115         if (response == null || response.subResponse == null || cancel) {
116             reason = LxErrorCode.COMMUNICATION_ERROR;
117             return false;
118         }
119         reason = response.getResponseCode();
120         return (reason == LxErrorCode.OK);
121     }
122
123     /**
124      * Hash string (e.g. containing user name and password or token) according to the algorithm required by the
125      * Miniserver.
126      *
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
131      */
132     String hashString(String string, String hashKeyHex, boolean sha256) {
133         if (string == null || hashKeyHex == null) {
134             return null;
135         }
136         try {
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);
141             mac.init(signKey);
142             byte[] rawData = mac.doFinal(string.getBytes());
143             return HexUtils.bytesToHex(rawData);
144         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
145             return null;
146         }
147     }
148
149     /**
150      * Encrypt string using current encryption algorithm.
151      *
152      * @param string input string to encrypt
153      * @return encrypted string
154      */
155     public String encrypt(String string) {
156         // by default no encryption
157         return string;
158     }
159
160     /**
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
163      * original form.
164      *
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
168      */
169     public String decryptControl(String control) {
170         // by default no decryption
171         return control;
172     }
173
174     /**
175      * Set error code and return false. It is used to report detailed error information from inside the algorithms.
176      *
177      * @param reason reason for failure
178      * @param details details of the failure
179      * @return always false
180      */
181     boolean setError(LxErrorCode reason, String details) {
182         if (reason != null) {
183             this.reason = reason;
184         }
185         if (details != null) {
186             this.details = details;
187         }
188         return false;
189     }
190
191     /**
192      * Create an authentication instance.
193      *
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
202      */
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;
210             } else {
211                 securityType = LxWsSecurityType.TOKEN;
212             }
213         }
214         if (securityType == LxWsSecurityType.HASH) {
215             return new LxWsSecurityHash(debugId, thingHandler, socket, user, password);
216         } else {
217             return new LxWsSecurityToken(debugId, thingHandler, socket, user, password);
218         }
219     }
220 }