2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.io.homekit.internal;
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;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.core.storage.Storage;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
27 import io.github.hapjava.server.HomekitAuthInfo;
28 import io.github.hapjava.server.impl.HomekitServer;
31 * Provides a mechanism to store authenticated HomeKit client details inside the
32 * StorageService, by implementing HomekitAuthInfo.
34 * @author Andy Lintner - Initial contribution
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_";
44 private final Storage<Object> storage;
46 private BigInteger salt;
47 private byte[] privateKey;
49 private String setupId;
50 private boolean blockUserDeletion;
52 public HomekitAuthInfoImpl(Storage<Object> storage, String pin, String setupId, boolean blockUserDeletion)
53 throws InvalidAlgorithmParameterException {
54 this.storage = storage;
56 this.setupId = setupId;
57 this.blockUserDeletion = blockUserDeletion;
61 public void setBlockUserDeletion(boolean blockUserDeletion) {
62 this.blockUserDeletion = blockUserDeletion;
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);
75 public String getMac() {
79 public void setMac(String mac) {
84 public String getPin() {
88 public void setPin(String pin) {
93 public String getSetupId() {
97 public void setSetupId(String setupId) {
98 this.setupId = setupId;
102 public byte[] getPrivateKey() {
107 public BigInteger getSalt() {
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);
122 public void removeUser(String username) {
123 logger.trace("remove user {}", username);
124 if (!this.blockUserDeletion) {
125 storage.remove(createUserKey(username));
127 logger.debug("deletion of the user was blocked by binding settings");
132 public boolean hasUser() {
133 Collection<String> keys = storage.getKeys();
134 return keys.stream().anyMatch(this::isUserKey);
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());
145 public boolean userIsAdmin(String username) {
149 public void clear() {
150 logger.trace("clear all users");
151 if (!this.blockUserDeletion) {
152 for (String key : new HashSet<>(storage.getKeys())) {
153 if (isUserKey(key)) {
158 logger.debug("deletion of users information was blocked by binding settings");
162 private String createUserKey(String username) {
163 return STORAGE_USER_PREFIX + username;
166 private boolean isUserKey(String key) {
167 return key.startsWith(STORAGE_USER_PREFIX);
170 private void initializeStorage() throws InvalidAlgorithmParameterException {
171 mac = (String) storage.get(STORAGE_MAC);
172 final @Nullable Object saltConfig = storage.get(STORAGE_SALT);
173 final @Nullable Object privateKeyConfig = storage.get(STORAGE_PRIVATE_KEY);
176 "could not find existing MAC in {}. Generating new MAC. This will require re-pairing of iOS devices.",
177 storage.getClass().getName());
178 mac = HomekitServer.generateMac();
179 storage.put(STORAGE_MAC, mac);
181 if (saltConfig == null) {
182 salt = HomekitServer.generateSalt();
183 storage.put(STORAGE_SALT, salt.toString());
185 salt = new BigInteger(saltConfig.toString());
187 if (privateKeyConfig == null) {
188 privateKey = HomekitServer.generateKey();
189 storage.put(STORAGE_PRIVATE_KEY, Base64.getEncoder().encodeToString(privateKey));
191 privateKey = Base64.getDecoder().decode(privateKeyConfig.toString());