001// --------------------------------------------------------------------------------
002// Copyright 2002-2026 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.control.user.core.client.helper;
018
019import com.echothree.control.user.core.common.CoreUtil;
020import com.echothree.control.user.core.common.form.CoreFormFactory;
021import com.echothree.model.control.core.common.transfer.BaseEncryptionKeyTransfer;
022import com.echothree.model.data.user.common.pk.UserVisitPK;
023import com.echothree.util.common.persistence.BaseKey;
024import com.echothree.util.common.persistence.BaseKeys;
025import com.echothree.util.common.persistence.EncryptionConstants;
026import com.google.common.io.BaseEncoding;
027import java.io.File;
028import java.io.FileInputStream;
029import java.io.FileOutputStream;
030import java.io.IOException;
031import java.net.InetAddress;
032import java.util.HashMap;
033import java.util.Map;
034import java.util.Properties;
035import javax.crypto.spec.SecretKeySpec;
036import javax.naming.NamingException;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040public class BaseKeysHelper {
041    
042    private BaseKeysHelper() {
043        super();
044    }
045    
046    private static class BaseKeysHelperHolder {
047        static BaseKeysHelper instance = new BaseKeysHelper();
048    }
049    
050    public static BaseKeysHelper getInstance() {
051        return BaseKeysHelperHolder.instance;
052    }
053
054    protected final Logger log = LoggerFactory.getLogger(this.getClass());
055
056    private void storeBaseKeyToProperties(String baseEncryptionKeyName, BaseKey baseKey, String which)
057            throws IOException {
058        var baseEncoding = BaseEncoding.base64();
059        var keyProperties = new Properties();
060
061        keyProperties.setProperty("key", baseEncoding.encode(baseKey.getKey().getEncoded()));
062        keyProperties.setProperty("iv", baseEncoding.encode(baseKey.getIv()));
063        keyProperties.setProperty("which", which);
064
065        var hostName = InetAddress.getLocalHost().getHostName();
066        var propertiesPath = new StringBuilder(System.getProperty("user.home"))
067                    .append(File.separator).append("keys/media").append(which)
068                    .append(File.separator).append(hostName)
069                    .append(File.separator).append(baseEncryptionKeyName);
070
071            if(new File(propertiesPath.toString()).mkdirs()) {
072                var fileName = propertiesPath.append(File.separator).append("key.xml").toString();
073
074                keyProperties.storeToXML(new FileOutputStream(propertiesPath.toString()), null);
075                log.info("Key #" + which + " stored to " + fileName);
076            } else {
077                // Save key into current user's home directory, and let them sort it out.
078                log.error("Storage of key #" + which + " failed!");
079                keyProperties.storeToXML(new FileOutputStream(System.getProperty("user.home") + File.separator + "key" + which + ".xml"), null);
080            }
081    }
082
083    private void writeBaseKeysToFiles(BaseKeys baseKeys)
084            throws IOException {
085        var baseEncryptionKeyName = baseKeys.getBaseEncryptionKeyName();
086
087        storeBaseKeyToProperties(baseEncryptionKeyName, baseKeys.getBaseKey1(), "1");
088        storeBaseKeyToProperties(baseEncryptionKeyName, baseKeys.getBaseKey2(), "2");
089        storeBaseKeyToProperties(baseEncryptionKeyName, baseKeys.getBaseKey3(), "3");
090    }
091
092    public void handleGenerateBaseKeys(UserVisitPK userVisitPK)
093            throws NamingException, IOException {
094        var commandResult = CoreUtil.getHome().generateBaseKeys(userVisitPK);
095        var executionResult = commandResult.getExecutionResult();
096        var result = executionResult.getResult();
097        var baseKeys = result.getBaseKeys();
098
099        if(baseKeys != null) {
100            writeBaseKeysToFiles(baseKeys);
101        }
102    }
103
104    private String getActiveBaseEncryptionKeyName(UserVisitPK userVisitPK)
105            throws NamingException {
106        BaseEncryptionKeyTransfer baseEncryptionKey;
107        var commandForm = CoreFormFactory.getGetBaseEncryptionKeyForm();
108        var commandResult = CoreUtil.getHome().getBaseEncryptionKey(userVisitPK, commandForm);
109        var executionResult = commandResult.getExecutionResult();
110        var result = executionResult.getResult();
111
112        baseEncryptionKey = result.getBaseEncryptionKey();
113
114        return baseEncryptionKey == null ? null : baseEncryptionKey.getBaseEncryptionKeyName();
115    }
116
117    private void loadBaseKeyFromProperties(Map<String, BaseKey> baseKeyMap, String baseEncryptionKeyName, String whichMedia)
118            throws IOException {
119        var baseEncoding = BaseEncoding.base64();
120        var keyProperties = new Properties();
121        var hostName = InetAddress.getLocalHost().getHostName();
122        var fileName = System.getProperty("user.home") +
123                File.separator + "keys/media" + whichMedia +
124                File.separator + hostName +
125                File.separator + baseEncryptionKeyName +
126                File.separator + "key.xml";
127
128        keyProperties.loadFromXML(new FileInputStream(fileName));
129
130        var secretKey1 = new SecretKeySpec(baseEncoding.decode(keyProperties.getProperty("key")), EncryptionConstants.AES_ALGORITHM);
131        var iv1 = baseEncoding.decode(keyProperties.getProperty("iv"));
132
133        var baseKey = new BaseKey(secretKey1, iv1);
134        var which = keyProperties.getProperty("which");
135        baseKeyMap.put(which, baseKey);
136        log.info("Key #" + which + " restored from " + fileName);
137    }
138
139    private BaseKeys getBaseKeysFromFiles(UserVisitPK userVisitPK)
140            throws NamingException, IOException {
141        var baseKeyMap = new HashMap<String, BaseKey>();
142        var baseEncryptionKeyName = getActiveBaseEncryptionKeyName(userVisitPK);
143
144        loadBaseKeyFromProperties(baseKeyMap, baseEncryptionKeyName, "1");
145        loadBaseKeyFromProperties(baseKeyMap, baseEncryptionKeyName, "2");
146        loadBaseKeyFromProperties(baseKeyMap, baseEncryptionKeyName, "3");
147
148        var baseKey1 = baseKeyMap.get("1");
149        var baseKey2 = baseKeyMap.get("2");
150        var baseKey3 = baseKeyMap.get("3");
151
152        return new BaseKeys(baseKey1, baseKey2, baseKey3);
153    }
154
155    public void handleLoadBaseKeys(UserVisitPK userVisitPK)
156            throws NamingException, IOException {
157        var baseKeys = getBaseKeysFromFiles(userVisitPK);
158
159        if(baseKeys.getBaseKeyCount() > 1) {
160            log.info("Two or more Base Encryption Keys found, loading keys.");
161            var commandForm = CoreFormFactory.getLoadBaseKeysForm();
162
163            commandForm.setBaseKeys(baseKeys);
164
165            CoreUtil.getHome().loadBaseKeys(userVisitPK, commandForm);
166        } else {
167            log.error("A minimum of two Base Encryption Keys are required, not loading key.");
168        }
169    }
170
171    public void handleChangeBaseKeys(UserVisitPK userVisitPK)
172            throws NamingException, IOException {
173        var baseKeys = getBaseKeysFromFiles(userVisitPK);
174
175        if(baseKeys.getBaseKeyCount() == 3) {
176            log.info("Three Base Encryption Keys found, changing keys.");
177            var commandForm = CoreFormFactory.getChangeBaseKeysForm();
178
179            commandForm.setBaseKeys(baseKeys);
180
181            var commandResult = CoreUtil.getHome().changeBaseKeys(userVisitPK, commandForm);
182            var executionResult = commandResult.getExecutionResult();
183            var result = executionResult.getResult();
184
185            writeBaseKeysToFiles(result.getBaseKeys());
186        } else {
187            log.error("Three Base Encryption Keys are required, not changing keys.");
188        }
189    }
190
191}