org.openhab.binding.loxone.internal.core.LxWsSecurity.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.loxone.internal.core.LxWsSecurity.java

Source

/**
 * Copyright (c) 2010-2019 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.loxone.internal.core;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.openhab.binding.loxone.internal.core.LxJsonResponse.LxJsonSubResponse;
import org.openhab.binding.loxone.internal.core.LxServer.Configuration;
import org.openhab.binding.loxone.internal.core.LxWsClient.LxWebSocket;

/**
 * Security abstract class providing authentication and encryption services.
 * Used by the {@link LxWsClient} during connection establishment to authenticate user and during message exchange for
 * encryption and decryption or the messages.
 *
 * @author Pawel Pieczul - initial contribution
 *
 */
abstract class LxWsSecurity {
    final int debugId;
    final String user;
    final String password;
    final LxWebSocket socket;
    final Configuration configuration;

    LxOfflineReason reason;
    String details;
    boolean cancel = false;

    private final Lock authenticationLock = new ReentrantLock();

    /**
     * Create an authentication instance.
     *
     * @param debugId
     *            instance of the client used for debugging purposes only
     * @param configuration
     *            configuration object for getting and setting custom properties (e.g. token)
     * @param socket
     *            websocket to perform communication with Miniserver
     * @param user
     *            user to authenticate
     * @param password
     *            password to authenticate
     */
    LxWsSecurity(int debugId, Configuration configuration, LxWebSocket socket, String user, String password) {
        this.debugId = debugId;
        this.configuration = configuration;
        this.socket = socket;
        this.user = user;
        this.password = password;
    }

    /**
     * Initiate user authentication. This method will return immediately and authentication will be done in a separate
     * thread asynchronously. On successful or unsuccessful completion, a provided callback will be called with
     * information about failed reason and details of failure. In case of success, the reason value will be
     * {@link LxOfflineReason#NONE}
     * Only one authentication can run in parallel and must be performed sequentially (create no more threads).
     *
     * @param doneCallback
     *            callback to execute when authentication is finished or failed
     */
    void authenticate(BiConsumer<LxOfflineReason, String> doneCallback) {
        Runnable init = () -> {
            authenticationLock.lock();
            try {
                execute();
                doneCallback.accept(reason, details);
            } finally {
                authenticationLock.unlock();
            }
        };
        new Thread(init).start();
    }

    /**
     * Perform user authentication using a specific authentication algorithm.
     * This method will be executed in a dedicated thread to allow sending synchronous messages to the Miniserver.
     *
     * @return
     *         true when authentication granted
     */
    abstract boolean execute();

    /**
     * Cancel authentication procedure and any pending activities.
     * It is supposed to be overridden by implementing classes.
     */
    void cancel() {
        cancel = true;
    }

    /**
     * Check a response received from the Miniserver for errors, interpret it and store the results in class fields.
     *
     * @param response
     *            response received from the Miniserver
     * @return
     *         {@link LxOfflineReason#NONE} when response is correct or a specific {@link LxOfflineReason}
     */
    boolean checkResponse(LxJsonSubResponse response) {
        if (response == null || cancel) {
            reason = LxOfflineReason.COMMUNICATION_ERROR;
            return false;
        }
        reason = LxOfflineReason.getReason(response.code);
        return (reason == LxOfflineReason.NONE);
    }

    /**
     * Hash string (e.g. containing user name and password or token) according to the algorithm required by the
     * Miniserver.
     *
     * @param string
     *            string to be hashed
     * @param hashKeyHex
     *            hash key received from the Miniserver in hex format
     * @return
     *         hashed string or null if failed
     */
    String hashString(String string, String hashKeyHex) {
        try {
            byte[] hashKeyBytes = Hex.decodeHex(hashKeyHex.toCharArray());
            SecretKeySpec signKey = new SecretKeySpec(hashKeyBytes, "HmacSHA1");
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(signKey);
            byte[] rawData = mac.doFinal(string.getBytes());
            return Hex.encodeHexString(rawData);
        } catch (DecoderException | NoSuchAlgorithmException | InvalidKeyException e) {
            return null;
        }
    }

    /**
     * Encrypt string using current encryption algorithm.
     *
     * @param string
     *            input string to encrypt
     * @return
     *         encrypted string
     */
    String encrypt(String string) {
        // by default no encryption
        return string;
    }

    /**
     * Check if control is encrypted and decrypt it using current decryption algorithm.
     * If control is not encrypted or decryption is not available or not ready, the control should be returned in its
     * original form.
     *
     * @param control
     *            control to be decrypted
     * @return
     *         decrypted control or original control in case decryption is unavailable, control is not encrypted or
     *         other issue occurred
     */
    String decryptControl(String control) {
        // by default no decryption
        return control;
    }

    /**
     * Set error code and return false. It is used to report detailed error information from inside the algorithms.
     *
     * @param reason
     *            reason for failure
     * @param details
     *            details of the failure
     * @return
     *         always false
     */
    boolean setError(LxOfflineReason reason, String details) {
        if (reason != null) {
            this.reason = reason;
        }
        if (details != null) {
            this.details = details;
        }
        return false;
    }

    /**
     * Create an authentication instance.
     *
     * @param type
     *            type of security algorithm
     * @param swVersion
     *            Miniserver's software version or null if unknown
     * @param debugId
     *            instance of the client used for debugging purposes only
     * @param configuration
     *            configuration object for getting and setting custom properties (e.g. token)
     * @param socket
     *            websocket to perform communication with Miniserver
     * @param user
     *            user to authenticate
     * @param password
     *            password to authenticate
     * @return
     *         created security object
     */
    static LxWsSecurity create(LxWsSecurityType type, String swVersion, int debugId, Configuration configuration,
            LxWebSocket socket, String user, String password) {
        LxWsSecurityType securityType = type;
        if (securityType == LxWsSecurityType.AUTO && swVersion != null) {
            String[] versions = swVersion.split("[.]");
            if (versions != null && versions.length > 0 && Integer.parseInt(versions[0]) <= 8) {
                securityType = LxWsSecurityType.HASH;
            } else {
                securityType = LxWsSecurityType.TOKEN;
            }
        }
        if (securityType == LxWsSecurityType.HASH) {
            return new LxWsSecurityHash(debugId, configuration, socket, user, password);
        } else {
            return new LxWsSecurityToken(debugId, configuration, socket, user, password);
        }
    }

}