]> git.basschouten.com Git - openhab-addons.git/blob
dbe7c45c045e3cf18a13675cd14bee4895f442a1
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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      * @return hashed string or null if failed
130      */
131     String hashString(String string, String hashKeyHex) {
132         if (string == null || hashKeyHex == null) {
133             return null;
134         }
135         try {
136             byte[] hashKeyBytes = HexUtils.hexToBytes(hashKeyHex);
137             SecretKeySpec signKey = new SecretKeySpec(hashKeyBytes, "HmacSHA1");
138             Mac mac = Mac.getInstance("HmacSHA1");
139             mac.init(signKey);
140             byte[] rawData = mac.doFinal(string.getBytes());
141             return HexUtils.bytesToHex(rawData);
142         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
143             return null;
144         }
145     }
146
147     /**
148      * Encrypt string using current encryption algorithm.
149      *
150      * @param string input string to encrypt
151      * @return encrypted string
152      */
153     public String encrypt(String string) {
154         // by default no encryption
155         return string;
156     }
157
158     /**
159      * Check if control is encrypted and decrypt it using current decryption algorithm.
160      * If control is not encrypted or decryption is not available or not ready, the control should be returned in its
161      * original form.
162      *
163      * @param control control to be decrypted
164      * @return decrypted control or original control in case decryption is unavailable, control is not encrypted or
165      *         other issue occurred
166      */
167     public String decryptControl(String control) {
168         // by default no decryption
169         return control;
170     }
171
172     /**
173      * Set error code and return false. It is used to report detailed error information from inside the algorithms.
174      *
175      * @param reason reason for failure
176      * @param details details of the failure
177      * @return always false
178      */
179     boolean setError(LxErrorCode reason, String details) {
180         if (reason != null) {
181             this.reason = reason;
182         }
183         if (details != null) {
184             this.details = details;
185         }
186         return false;
187     }
188
189     /**
190      * Create an authentication instance.
191      *
192      * @param type type of security algorithm
193      * @param swVersion Miniserver's software version or null if unknown
194      * @param debugId instance of the client used for debugging purposes only
195      * @param thingHandler API to the thing handler
196      * @param socket websocket to perform communication with Miniserver
197      * @param user user to authenticate
198      * @param password password to authenticate
199      * @return created security object
200      */
201     public static LxWsSecurity create(LxWsSecurityType type, String swVersion, int debugId,
202             LxServerHandlerApi thingHandler, LxWebSocket socket, String user, String password) {
203         LxWsSecurityType securityType = type;
204         if (securityType == LxWsSecurityType.AUTO && swVersion != null) {
205             String[] versions = swVersion.split("[.]");
206             if (versions != null && versions.length > 0 && Integer.parseInt(versions[0]) <= 8) {
207                 securityType = LxWsSecurityType.HASH;
208             } else {
209                 securityType = LxWsSecurityType.TOKEN;
210             }
211         }
212         if (securityType == LxWsSecurityType.HASH) {
213             return new LxWsSecurityHash(debugId, thingHandler, socket, user, password);
214         } else {
215             return new LxWsSecurityToken(debugId, thingHandler, socket, user, password);
216         }
217     }
218 }