]> git.basschouten.com Git - openhab-addons.git/blob
4e5941bf13fc7eb1488c6cf8a49f8933b43dc851
[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.io.homekit.internal;
14
15 import java.math.BigInteger;
16 import java.security.InvalidAlgorithmParameterException;
17 import java.util.Base64;
18 import java.util.Collection;
19 import java.util.HashSet;
20 import java.util.stream.Collectors;
21
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.core.storage.Storage;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import io.github.hapjava.server.HomekitAuthInfo;
28 import io.github.hapjava.server.impl.HomekitServer;
29
30 /**
31  * Provides a mechanism to store authenticated HomeKit client details inside the
32  * StorageService, by implementing HomekitAuthInfo.
33  *
34  * @author Andy Lintner - Initial contribution
35  */
36 public class HomekitAuthInfoImpl implements HomekitAuthInfo {
37     private final Logger logger = LoggerFactory.getLogger(HomekitAuthInfoImpl.class);
38     public static final String STORAGE_KEY = "homekit";
39     private static final String STORAGE_MAC = "mac";
40     private static final String STORAGE_SALT = "salt";
41     private static final String STORAGE_PRIVATE_KEY = "privateKey";
42     private static final String STORAGE_USER_PREFIX = "user_";
43
44     private final Storage<Object> storage;
45     private String mac;
46     private BigInteger salt;
47     private byte[] privateKey;
48     private String pin;
49     private String setupId;
50     private boolean blockUserDeletion;
51
52     public HomekitAuthInfoImpl(Storage<Object> storage, String pin, String setupId, boolean blockUserDeletion)
53             throws InvalidAlgorithmParameterException {
54         this.storage = storage;
55         this.pin = pin;
56         this.setupId = setupId;
57         this.blockUserDeletion = blockUserDeletion;
58         initializeStorage();
59     }
60
61     public void setBlockUserDeletion(boolean blockUserDeletion) {
62         this.blockUserDeletion = blockUserDeletion;
63     }
64
65     @Override
66     public void createUser(String username, byte[] publicKey, boolean isAdmin) {
67         logger.trace("create user {}", username);
68         final String userKey = createUserKey(username);
69         final String encodedPublicKey = Base64.getEncoder().encodeToString(publicKey);
70         storage.put(userKey, encodedPublicKey);
71         logger.trace("stored user key {} with value {}", userKey, encodedPublicKey);
72     }
73
74     @Override
75     public String getMac() {
76         return mac;
77     }
78
79     public void setMac(String mac) {
80         this.mac = mac;
81     }
82
83     @Override
84     public String getPin() {
85         return pin;
86     }
87
88     public void setPin(String pin) {
89         this.pin = pin;
90     }
91
92     @Override
93     public String getSetupId() {
94         return setupId;
95     }
96
97     public void setSetupId(String setupId) {
98         this.setupId = setupId;
99     }
100
101     @Override
102     public byte[] getPrivateKey() {
103         return privateKey;
104     }
105
106     @Override
107     public BigInteger getSalt() {
108         return salt;
109     }
110
111     @Override
112     public byte[] getUserPublicKey(String username) {
113         final String encodedKey = (String) storage.get(createUserKey(username));
114         if (encodedKey != null) {
115             return Base64.getDecoder().decode(encodedKey);
116         } else {
117             return null;
118         }
119     }
120
121     @Override
122     public void removeUser(String username) {
123         logger.trace("remove user {}", username);
124         if (!this.blockUserDeletion) {
125             storage.remove(createUserKey(username));
126         } else {
127             logger.debug("deletion of the user was blocked by binding settings");
128         }
129     }
130
131     @Override
132     public boolean hasUser() {
133         Collection<String> keys = storage.getKeys();
134         return keys.stream().anyMatch(this::isUserKey);
135     }
136
137     @Override
138     public Collection<String> listUsers() {
139         Collection<String> keys = storage.getKeys();
140         // don't forget to strip user_ prefix
141         return keys.stream().filter(this::isUserKey).map(u -> u.substring(5)).collect(Collectors.toList());
142     }
143
144     @Override
145     public boolean userIsAdmin(String username) {
146         return true;
147     }
148
149     public void clear() {
150         if (!this.blockUserDeletion) {
151             for (String key : new HashSet<>(storage.getKeys())) {
152                 if (isUserKey(key)) {
153                     storage.remove(key);
154                 }
155             }
156             mac = HomekitServer.generateMac();
157             storage.put(STORAGE_MAC, mac);
158             storage.remove(STORAGE_SALT);
159             storage.remove(STORAGE_PRIVATE_KEY);
160             try {
161                 initializeStorage();
162                 logger.info("All users cleared from HomeKit bridge; re-pairing required.");
163             } catch (InvalidAlgorithmParameterException e) {
164                 logger.warn(
165                         "Failed generating new encryption settings for HomeKit bridge; re-pairing required, but will likely fail.");
166             }
167         } else {
168             logger.warn("Deletion of HomeKit users was blocked by addon settings.");
169         }
170     }
171
172     private String createUserKey(String username) {
173         return STORAGE_USER_PREFIX + username;
174     }
175
176     private boolean isUserKey(String key) {
177         return key.startsWith(STORAGE_USER_PREFIX);
178     }
179
180     private void initializeStorage() throws InvalidAlgorithmParameterException {
181         mac = (String) storage.get(STORAGE_MAC);
182         final @Nullable Object saltConfig = storage.get(STORAGE_SALT);
183         final @Nullable Object privateKeyConfig = storage.get(STORAGE_PRIVATE_KEY);
184         if (mac == null) {
185             logger.warn(
186                     "could not find existing MAC in {}. Generating new MAC. This will require re-pairing of iOS devices.",
187                     storage.getClass().getName());
188             mac = HomekitServer.generateMac();
189             storage.put(STORAGE_MAC, mac);
190         }
191         if (saltConfig == null) {
192             salt = HomekitServer.generateSalt();
193             storage.put(STORAGE_SALT, salt.toString());
194         } else {
195             salt = new BigInteger(saltConfig.toString());
196         }
197         if (privateKeyConfig == null) {
198             privateKey = HomekitServer.generateKey();
199             storage.put(STORAGE_PRIVATE_KEY, Base64.getEncoder().encodeToString(privateKey));
200         } else {
201             privateKey = Base64.getDecoder().decode(privateKeyConfig.toString());
202         }
203     }
204 }