001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package  org.jpos.security;
020
021import java.io.BufferedInputStream;
022import org.jpos.core.Configurable;
023import org.jpos.core.Configuration;
024import org.jpos.core.ConfigurationException;
025import org.jpos.iso.ISOUtil;
026import org.jpos.util.LogEvent;
027import org.jpos.util.LogSource;
028import org.jpos.util.Logger;
029
030import java.io.File;
031import java.io.FileInputStream;
032import java.io.FileOutputStream;
033import java.io.InputStream;
034import java.util.HashMap;
035import java.util.Map;
036import java.util.Properties;
037
038
039/**
040 * Implements SecureKeyStore using a properties file.
041 * @author Hani S. Kirollos
042 * @version $Revision$ $Date$
043 * @see java.util.Properties
044 */
045public class SimpleKeyFile
046        implements SecureKeyStore, Configurable, LogSource  {
047    Properties props = new Properties();
048    File file;
049    String header = "Key File";
050    /** Logger receiving load/save diagnostic events. */
051    protected Logger logger = null;
052    /** Logger realm associated with this store. */
053    protected String realm = null;
054
055    /** Default constructor. */
056    public SimpleKeyFile () {
057    }
058
059    /**
060     * Constructs the store and immediately loads keys from {@code keyFileName}.
061     *
062     * @param keyFileName path to the properties file backing this store
063     * @throws SecureKeyStoreException if the file cannot be created or loaded
064     */
065    public SimpleKeyFile (String keyFileName) throws SecureKeyStoreException
066    {
067        init(keyFileName);
068    }
069
070    /**
071     * Loads (or creates) the underlying properties file and reads its contents.
072     *
073     * @param keyFileName path to the properties file backing this store
074     * @throws SecureKeyStoreException if the file cannot be created or loaded
075     */
076    public void init (String keyFileName) throws SecureKeyStoreException {
077        file = new File(keyFileName);
078        try {
079            if (!file.exists())
080                file.createNewFile();
081            load();
082        } catch (Exception e) {
083            throw  new SecureKeyStoreException(e);
084        }
085    }
086
087    @Override
088    public void setLogger (Logger logger, String realm) {
089        this.logger = logger;
090        this.realm = realm;
091    }
092
093    @Override
094    public Logger getLogger () {
095        return  logger;
096    }
097
098    @Override
099    public String getRealm () {
100        return  realm;
101    }
102
103    /**
104     *
105     * @param cfg configuration object
106     * @throws ConfigurationException if configuration is invalid
107     */
108    @Override
109    public void setConfiguration (Configuration cfg) throws ConfigurationException {
110        try {
111            init(cfg.get("key-file"));
112            header = cfg.get("file-header", header);
113        } catch (Exception e) {
114            throw  new ConfigurationException(e);
115        }
116    }
117
118    @Override
119    public synchronized SecureKey getKey (String alias) throws SecureKeyStoreException {
120        SecureKey secureKey = null;
121        LogEvent evt = logger != null ? new LogEvent(this, "get-key") : null;
122        if (evt != null)
123            evt.addMessage("alias", alias);
124        try {
125            load();
126            String keyClassName = getProperty(alias, "class");
127            Class c = Class.forName(keyClassName);
128            secureKey = (SecureKey)c.newInstance();
129            if (!(secureKey instanceof SecureDESKey))
130                throw  new SecureKeyStoreException("Unsupported SecureKey class: " +
131                        secureKey.getClass().getName());
132            byte[] keyBytes = ISOUtil.hex2byte(getProperty(alias, "key"));
133            short keyLength = Short.parseShort(getProperty(alias, "length"));
134            String keyType = getProperty(alias, "type");
135            byte[] KeyCheckValue = ISOUtil.hex2byte(getProperty(alias, "checkvalue"));
136            secureKey = new SecureDESKey(keyLength, keyType, keyBytes, KeyCheckValue);
137            if (evt != null)
138                evt.addMessage(secureKey);
139        } catch (Exception e) {
140            if (evt == null) // this is an exception, we want to log it, even if we don't have an assigned logger
141                evt = new LogEvent(this, "get-key-error", alias);
142            evt.addMessage(e);
143            throw  e instanceof SecureKeyStoreException ? (SecureKeyStoreException) e : new SecureKeyStoreException(e);
144        } finally {
145            if (evt != null)
146                Logger.log(evt);
147        }
148        return  secureKey;
149    }
150
151    @Override
152    public synchronized void setKey (String alias, SecureKey secureKey) throws SecureKeyStoreException {
153        LogEvent evt = new LogEvent(this, "set-key");
154        evt.addMessage("alias", alias);
155        evt.addMessage(secureKey);
156        try {
157            if (!(secureKey instanceof SecureDESKey))
158                throw  new SecureKeyStoreException("Unsupported SecureKey class: " +
159                        secureKey.getClass().getName());
160            load();                 // load new changes (possibly made manually on the file)
161            setProperty(alias, "class", secureKey.getClass().getName());
162            setProperty(alias, "key", ISOUtil.hexString(secureKey.getKeyBytes()));
163            setProperty(alias, "length",  Short.toString(secureKey.getKeyLength()));
164            setProperty(alias, "type", secureKey.getKeyType());
165            String keyCheckValueHexString = ISOUtil.hexString(((SecureDESKey)secureKey).getKeyCheckValue());
166            setProperty(alias, "checkvalue", keyCheckValueHexString);
167            store();
168        } catch (Exception e) {
169            evt.addMessage(e);
170            throw  e instanceof SecureKeyStoreException ? (SecureKeyStoreException) e : new SecureKeyStoreException(e);
171        } finally {
172            Logger.log(evt);
173        }
174    }
175
176    /**
177     * Loads the underlying properties file into memory.
178     *
179     * @throws SecureKeyStoreException if the file is unreadable or malformed
180     */
181    void load () throws SecureKeyStoreException {
182        InputStream in;
183        try {
184            if (!file.canRead())
185                throw  new SecureKeyStoreException("Can't read from file: " + file.getCanonicalPath());
186            in = new BufferedInputStream(new FileInputStream(file));
187            try {
188                props.load(in);
189            } finally {
190                in.close();
191            }
192        } catch (Exception e) {
193            throw  new SecureKeyStoreException(e);
194        }
195    }
196
197    /**
198     * Persists the current in-memory key map back to the properties file.
199     *
200     * @throws SecureKeyStoreException if the file is unwritable or the write fails
201     */
202    void store () throws SecureKeyStoreException {
203        try {
204            if (!file.canWrite())
205                throw  new SecureKeyStoreException("Can't write to file: " + file.getCanonicalPath());
206            try (FileOutputStream out = new FileOutputStream(file)) {
207                props.store(out, header);
208                out.flush();
209            }
210        } catch (Exception e) {
211            throw  new SecureKeyStoreException(e);
212        }
213    }
214
215    /**
216     * Returns the trimmed property stored under {@code alias.subName}.
217     *
218     * @param alias key alias
219     * @param subName property name within the alias
220     * @return the trimmed property value
221     * @throws SecureKeyStoreException if the property is not present
222     */
223    public String getProperty (String alias, String subName) throws SecureKeyStoreException {
224        // key here has nothing to do with cryptographic keys
225        String key = alias + "." + subName;
226        String value = props.getProperty(key);
227        if (value == null)
228            throw  new SecureKeyStoreException("Key can't be retrieved. Can't get property: " + key);
229        return value.trim();
230    }
231
232    /**
233     * Sets the property stored under {@code alias.subName}.
234     *
235     * @param alias key alias
236     * @param subName property name within the alias
237     * @param value new property value
238     */
239    public void setProperty (String alias, String subName, String value) {
240        // key here has nothing to do with cryptographic keys
241        String key = alias + "." + subName;
242        props.setProperty(key, value);
243    }
244
245    @Override
246    public Map<String, SecureKey> getKeys() throws SecureKeyStoreException {
247        Map<String, SecureKey> keys = new HashMap<>();
248        for (Object k : props.keySet()) {
249            String keyStr = (String) k;
250            String alias = keyStr.substring(0, keyStr.lastIndexOf('.'));
251            if (!keys.containsKey(alias)) {
252                keys.put(alias, getKey(alias));
253            }
254        }
255        return keys;
256    }
257}