001// --------------------------------------------------------------------------------
002// Copyright 2002-2024 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.control.user.core.common.result.ChangeBaseKeysResult;
022import com.echothree.control.user.core.common.result.GenerateBaseKeysResult;
023import com.echothree.control.user.core.common.result.GetBaseEncryptionKeyResult;
024import com.echothree.model.control.core.common.transfer.BaseEncryptionKeyTransfer;
025import com.echothree.model.data.user.common.pk.UserVisitPK;
026import com.echothree.util.common.persistence.BaseKey;
027import com.echothree.util.common.persistence.BaseKeys;
028import com.echothree.util.common.persistence.EncryptionConstants;
029import com.google.common.io.BaseEncoding;
030import java.io.File;
031import java.io.FileInputStream;
032import java.io.FileOutputStream;
033import java.io.IOException;
034import java.net.InetAddress;
035import java.util.HashMap;
036import java.util.Map;
037import java.util.Properties;
038import javax.crypto.spec.SecretKeySpec;
039import javax.naming.NamingException;
040import org.apache.commons.logging.Log;
041import org.apache.commons.logging.LogFactory;
042
043public class BaseKeysHelper {
044    
045    private BaseKeysHelper() {
046        super();
047    }
048    
049    private static class BaseKeysHelperHolder {
050        static BaseKeysHelper instance = new BaseKeysHelper();
051    }
052    
053    public static BaseKeysHelper getInstance() {
054        return BaseKeysHelperHolder.instance;
055    }
056
057    private Log log = LogFactory.getLog(this.getClass());
058
059    private void storeBaseKeyToProperties(String baseEncryptionKeyName, BaseKey baseKey, String which)
060            throws IOException {
061        var baseEncoding = BaseEncoding.base64();
062        var keyProperties = new Properties();
063
064        keyProperties.setProperty("key", baseEncoding.encode(baseKey.getKey().getEncoded()));
065        keyProperties.setProperty("iv", baseEncoding.encode(baseKey.getIv()));
066        keyProperties.setProperty("which", which);
067
068            String hostName = InetAddress.getLocalHost().getHostName();
069            StringBuilder propertiesPath = new StringBuilder(System.getProperty("user.home"))
070                    .append(File.separator).append("keys/media").append(which)
071                    .append(File.separator).append(hostName)
072                    .append(File.separator).append(baseEncryptionKeyName);
073
074            if(new File(propertiesPath.toString()).mkdirs()) {
075                String fileName = propertiesPath.append(File.separator).append("key.xml").toString();
076
077                keyProperties.storeToXML(new FileOutputStream(propertiesPath.toString()), null);
078                log.info("Key #" + which + " stored to " + fileName);
079            } else {
080                // Save key into current user's home directory, and let them sort it out.
081                log.error("Storage of key #" + which + " failed!");
082                keyProperties.storeToXML(new FileOutputStream(System.getProperty("user.home") + File.separator + "key" + which + ".xml"), null);
083            }
084    }
085
086    private void writeBaseKeysToFiles(BaseKeys baseKeys)
087            throws IOException {
088        var baseEncryptionKeyName = baseKeys.getBaseEncryptionKeyName();
089
090        storeBaseKeyToProperties(baseEncryptionKeyName, baseKeys.getBaseKey1(), "1");
091        storeBaseKeyToProperties(baseEncryptionKeyName, baseKeys.getBaseKey2(), "2");
092        storeBaseKeyToProperties(baseEncryptionKeyName, baseKeys.getBaseKey3(), "3");
093    }
094
095    public void handleGenerateBaseKeys(UserVisitPK userVisitPK)
096            throws NamingException, IOException {
097        var commandResult = CoreUtil.getHome().generateBaseKeys(userVisitPK);
098        var executionResult = commandResult.getExecutionResult();
099        var result = (GenerateBaseKeysResult)executionResult.getResult();
100        var baseKeys = result.getBaseKeys();
101
102        if(baseKeys != null) {
103            writeBaseKeysToFiles(baseKeys);
104        }
105    }
106
107    private String getActiveBaseEncryptionKeyName(UserVisitPK userVisitPK)
108            throws NamingException {
109        BaseEncryptionKeyTransfer baseEncryptionKey;
110        var commandForm = CoreFormFactory.getGetBaseEncryptionKeyForm();
111        var commandResult = CoreUtil.getHome().getBaseEncryptionKey(userVisitPK, commandForm);
112        var executionResult = commandResult.getExecutionResult();
113        var result = (GetBaseEncryptionKeyResult)executionResult.getResult();
114
115        baseEncryptionKey = result.getBaseEncryptionKey();
116
117        return baseEncryptionKey == null ? null : baseEncryptionKey.getBaseEncryptionKeyName();
118    }
119
120    private void loadBaseKeyFromProperties(Map<String, BaseKey> baseKeyMap, String baseEncryptionKeyName, String whichMedia)
121            throws IOException {
122        var baseEncoding = BaseEncoding.base64();
123        var keyProperties = new Properties();
124        var hostName = InetAddress.getLocalHost().getHostName();
125        var propertiesPath = new StringBuilder(System.getProperty("user.home"))
126                .append(File.separator).append("keys/media").append(whichMedia)
127                .append(File.separator).append(hostName)
128                .append(File.separator).append(baseEncryptionKeyName);
129        var fileName = propertiesPath.append(File.separator).append("key.xml").toString();
130
131        keyProperties.loadFromXML(new FileInputStream(fileName));
132
133        var secretKey1 = new SecretKeySpec(baseEncoding.decode(keyProperties.getProperty("key")), EncryptionConstants.algorithm);
134        byte[] iv1 = baseEncoding.decode(keyProperties.getProperty("iv"));
135
136        var baseKey = new BaseKey(secretKey1, iv1);
137        var which = keyProperties.getProperty("which");
138        baseKeyMap.put(which, baseKey);
139        log.info("Key #" + which + " restored from " + fileName);
140    }
141
142    private BaseKeys getBaseKeysFromFiles(UserVisitPK userVisitPK)
143            throws NamingException, IOException {
144        var baseKeyMap = new HashMap<String, BaseKey>();
145        var baseEncryptionKeyName = getActiveBaseEncryptionKeyName(userVisitPK);
146
147        loadBaseKeyFromProperties(baseKeyMap, baseEncryptionKeyName, "1");
148        loadBaseKeyFromProperties(baseKeyMap, baseEncryptionKeyName, "2");
149        loadBaseKeyFromProperties(baseKeyMap, baseEncryptionKeyName, "3");
150
151        var baseKey1 = baseKeyMap.get("1");
152        var baseKey2 = baseKeyMap.get("2");
153        var baseKey3 = baseKeyMap.get("3");
154
155        return new BaseKeys(baseKey1, baseKey2, baseKey3);
156    }
157
158    public void handleLoadBaseKeys(UserVisitPK userVisitPK)
159            throws NamingException, IOException {
160        var baseKeys = getBaseKeysFromFiles(userVisitPK);
161
162        if(baseKeys.getBaseKeyCount() > 1) {
163            log.info("Two or more Base Encryption Keys found, loading keys.");
164            var commandForm = CoreFormFactory.getLoadBaseKeysForm();
165
166            commandForm.setBaseKeys(baseKeys);
167
168            CoreUtil.getHome().loadBaseKeys(userVisitPK, commandForm);
169        } else {
170            log.error("A minimum of two Base Encryption Keys are required, not loading key.");
171        }
172    }
173
174    public void handleChangeBaseKeys(UserVisitPK userVisitPK)
175            throws NamingException, IOException {
176        var baseKeys = getBaseKeysFromFiles(userVisitPK);
177
178        if(baseKeys.getBaseKeyCount() == 3) {
179            log.info("Three Base Encryption Keys found, changing keys.");
180            var commandForm = CoreFormFactory.getChangeBaseKeysForm();
181
182            commandForm.setBaseKeys(baseKeys);
183
184            var commandResult = CoreUtil.getHome().changeBaseKeys(userVisitPK, commandForm);
185            var executionResult = commandResult.getExecutionResult();
186            var result = (ChangeBaseKeysResult)executionResult.getResult();
187
188            writeBaseKeysToFiles(result.getBaseKeys());
189        } else {
190            log.error("Three Base Encryption Keys are required, not changing keys.");
191        }
192    }
193
194}