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}