]> git.basschouten.com Git - openhab-addons.git/blob
6f61c00c7d42b7b41ddf3ec52201da2c811d3c80
[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.miio.internal.cloud;
14
15 import java.io.ByteArrayOutputStream;
16 import java.io.DataOutputStream;
17 import java.io.File;
18 import java.io.FileWriter;
19 import java.io.IOException;
20 import java.net.URI;
21 import java.nio.charset.StandardCharsets;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.util.ArrayList;
26 import java.util.Base64;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Random;
30 import java.util.Set;
31 import java.util.TreeMap;
32
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.openhab.binding.miio.internal.MiIoBindingConstants;
36 import org.openhab.binding.miio.internal.MiIoCryptoException;
37 import org.openhab.core.config.core.ConfigConstants;
38 import org.slf4j.Logger;
39
40 /**
41  * The {@link CloudUtil} class is used for supporting functions for Xiaomi cloud access
42  *
43  * @author Marcel Verpaalen - Initial contribution
44  */
45 @NonNullByDefault
46 public class CloudUtil {
47
48     private static final Random RANDOM = new Random();
49     private static final String DB_FOLDER_NAME = ConfigConstants.getUserDataFolder() + File.separator
50             + MiIoBindingConstants.BINDING_ID;
51
52     /**
53      * Saves the Xiaomi cloud device info with tokens to file
54      *
55      * @param data file content
56      * @param country county server
57      * @param logger
58      */
59     public static void saveDeviceInfoFile(String data, String country, Logger logger) {
60         File folder = new File(DB_FOLDER_NAME);
61         if (!folder.exists()) {
62             folder.mkdirs();
63         }
64         File dataFile = new File(folder, "miioTokens-" + country + ".json");
65         try (FileWriter writer = new FileWriter(dataFile)) {
66             writer.write(data);
67             logger.debug("Devices token info saved to {}", dataFile.getAbsolutePath());
68         } catch (IOException e) {
69             logger.debug("Failed to write token file '{}': {}", dataFile.getName(), e.getMessage());
70         }
71     }
72
73     /**
74      * Generate signature for the request.
75      *
76      * @param method http request method. GET or POST
77      * @param requestUrl the full request url. e.g.: http://api.xiaomi.com/getUser?id=123321
78      * @param params request params. This should be a TreeMap because the
79      *            parameters are required to be in lexicographic order.
80      * @param signedNonce secret key for encryption.
81      * @return hash value for the values provided
82      * @throws MiIoCryptoException
83      */
84     public static String generateSignature(@Nullable String requestUrl, @Nullable String signedNonce, String nonce,
85             @Nullable Map<String, String> params) throws MiIoCryptoException {
86         if (signedNonce == null || signedNonce.length() == 0) {
87             throw new MiIoCryptoException("key is not nullable");
88         }
89         List<String> exps = new ArrayList<String>();
90
91         if (requestUrl != null) {
92             URI uri = URI.create(requestUrl);
93             exps.add(uri.getPath());
94         }
95         exps.add(signedNonce);
96         exps.add(nonce);
97
98         if (params != null && !params.isEmpty()) {
99             final TreeMap<String, String> sortedParams = new TreeMap<String, String>(params);
100             Set<Map.Entry<String, String>> entries = sortedParams.entrySet();
101             for (Map.Entry<String, String> entry : entries) {
102                 exps.add(String.format("%s=%s", entry.getKey(), entry.getValue()));
103             }
104         }
105         boolean first = true;
106         StringBuilder sb = new StringBuilder();
107         for (String s : exps) {
108             if (!first) {
109                 sb.append('&');
110             } else {
111                 first = false;
112             }
113             sb.append(s);
114         }
115         return CloudCrypto.hMacSha256Encode(Base64.getDecoder().decode(signedNonce),
116                 sb.toString().getBytes(StandardCharsets.UTF_8));
117     }
118
119     public static String generateNonce(long milli) throws IOException {
120         ByteArrayOutputStream output = new ByteArrayOutputStream();
121         DataOutputStream dataOutputStream = new DataOutputStream(output);
122         dataOutputStream.writeLong(RANDOM.nextLong());
123         dataOutputStream.writeInt((int) (milli / 60000));
124         dataOutputStream.flush();
125         return Base64.getEncoder().encodeToString(output.toByteArray());
126     }
127
128     public static String signedNonce(String ssecret, String nonce) throws IOException, MiIoCryptoException {
129         byte[] byteArrayS = Base64.getDecoder().decode(ssecret.getBytes(StandardCharsets.UTF_8));
130         byte[] byteArrayN = Base64.getDecoder().decode(nonce.getBytes(StandardCharsets.UTF_8));
131         ByteArrayOutputStream output = new ByteArrayOutputStream();
132         output.write(byteArrayS);
133         output.write(byteArrayN);
134         return CloudCrypto.sha256Hash(output.toByteArray());
135     }
136
137     public static void writeBytesToFileNio(byte[] bFile, String fileDest) throws IOException {
138         Path path = Paths.get(fileDest);
139         Files.write(path, bFile);
140     }
141 }