2 * Copyright (c) 2010-2021 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;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.core.storage.Storage;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
26 import io.github.hapjava.server.HomekitAuthInfo;
27 import io.github.hapjava.server.impl.HomekitServer;
30 * Provides a mechanism to store authenticated HomeKit client details inside the
31 * StorageService, by implementing HomekitAuthInfo.
33 * @author Andy Lintner - Initial contribution
35 public class HomekitAuthInfoImpl implements HomekitAuthInfo {
36 private final Logger logger = LoggerFactory.getLogger(HomekitAuthInfoImpl.class);
37 public static final String STORAGE_KEY = "homekit";
38 private static final String STORAGE_MAC = "mac";
39 private static final String STORAGE_SALT = "salt";
40 private static final String STORAGE_PRIVATE_KEY = "privateKey";
41 private static final String STORAGE_USER_PREFIX = "user_";
43 private final Storage<String> storage;
45 private BigInteger salt;
46 private byte[] privateKey;
48 private String setupId;
50 public HomekitAuthInfoImpl(Storage<String> storage, String pin, String setupId)
51 throws InvalidAlgorithmParameterException {
52 this.storage = storage;
54 this.setupId = setupId;
59 public void createUser(String username, byte[] publicKey) {
60 logger.trace("Create user {}", username);
61 final String userKey = createUserKey(username);
62 final String encodedPublicKey = Base64.getEncoder().encodeToString(publicKey);
63 storage.put(userKey, encodedPublicKey);
64 logger.trace("Stored user key {} with value {}", userKey, encodedPublicKey);
68 public String getMac() {
72 public void setMac(String mac) {
77 public String getPin() {
81 public void setPin(String pin) {
86 public String getSetupId() {
90 public void setSetupId(String setupId) {
91 this.setupId = setupId;
95 public byte[] getPrivateKey() {
100 public BigInteger getSalt() {
105 public byte[] getUserPublicKey(String username) {
106 final String encodedKey = storage.get(createUserKey(username));
107 if (encodedKey != null) {
108 return Base64.getDecoder().decode(encodedKey);
115 public void removeUser(String username) {
116 logger.trace("Remove user {}", username);
117 storage.remove(createUserKey(username));
121 public boolean hasUser() {
122 Collection<String> keys = storage.getKeys();
123 return keys.stream().anyMatch(this::isUserKey);
126 public void clear() {
127 logger.trace("Clear all users");
128 for (String key : new HashSet<>(storage.getKeys())) {
129 if (isUserKey(key)) {
135 private String createUserKey(String username) {
136 return STORAGE_USER_PREFIX + username;
139 private boolean isUserKey(String key) {
140 return key.startsWith(STORAGE_USER_PREFIX);
143 private void initializeStorage() throws InvalidAlgorithmParameterException {
144 mac = storage.get(STORAGE_MAC);
145 final @Nullable Object saltConfig = storage.get(STORAGE_SALT);
146 final @Nullable Object privateKeyConfig = storage.get(STORAGE_PRIVATE_KEY);
149 "Could not find existing MAC in {}. Generating new MAC. This will require re-pairing of iOS devices.",
150 storage.getClass().getName());
151 mac = HomekitServer.generateMac();
152 storage.put(STORAGE_MAC, mac);
154 if (saltConfig == null) {
155 salt = HomekitServer.generateSalt();
156 storage.put(STORAGE_SALT, salt.toString());
158 salt = new BigInteger(saltConfig.toString());
160 if (privateKeyConfig == null) {
161 privateKey = HomekitServer.generateKey();
162 storage.put(STORAGE_PRIVATE_KEY, Base64.getEncoder().encodeToString(privateKey));
164 privateKey = Base64.getDecoder().decode(privateKeyConfig.toString());