001 /* 002 * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org 003 * Copyright (C) 2011 NightLabs Consulting GmbH 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 package org.cumulus4j.keystore; 019 020 import java.io.IOException; 021 import java.util.Date; 022 import java.util.List; 023 024 import org.cumulus4j.keystore.prop.Long2LongSortedMapProperty; 025 import org.cumulus4j.keystore.prop.Property; 026 import org.slf4j.Logger; 027 import org.slf4j.LoggerFactory; 028 029 /** 030 * <p> 031 * Key management strategy determining the currently active encryption key by the current time. 032 * </p><p> 033 * See <a target="_blank" href="../../../documentation/date-dependent-key-strategy.html">Date-dependent key-strategy</a> for further 034 * details. 035 * </p> 036 * 037 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 038 */ 039 public class DateDependentKeyStrategy 040 { 041 private static final Logger logger = LoggerFactory.getLogger(DateDependentKeyStrategy.class); 042 043 private KeyStore keyStore; 044 045 /** 046 * Name of the {@link Property} where the key-strategy's timestamp-to-key-map is stored. 047 * The property is of type {@link Long2LongSortedMapProperty}. 048 */ 049 public static final String PROPERTY_ACTIVE_FROM_TIMESTAMP_2_KEY_ID = "DateDependentKeyStrategy.activeFromTimestamp2KeyID"; 050 051 /** 052 * Create a new instance for the given {@link KeyStore}. 053 * @param keyStore the <code>KeyStore</code> to work with. Must not be <code>null</code>. 054 */ 055 public DateDependentKeyStrategy(KeyStore keyStore) 056 { 057 if (keyStore == null) 058 throw new IllegalArgumentException("keyStore == null"); 059 060 this.keyStore = keyStore; 061 } 062 063 /** 064 * Get the {@link KeyStore} that was passed to {@link #DateDependentKeyStrategy(KeyStore)}. 065 * @return the <code>KeyStore</code> this strategy instance works with. Never <code>null</code>. 066 */ 067 public KeyStore getKeyStore() { 068 return keyStore; 069 } 070 071 /** 072 * <p> 073 * Initialise an {@link KeyStore#isEmpty() empty} <code>KeyStore</code>. 074 * </p><p> 075 * This initialisation consists of creating a user and a few (thousand) keys. How many keys, 076 * depends on the parameters <code>keyActivityPeriodMSec</code> and <code>keyStorePeriodMSec</code>. 077 * The keys are added to a {@link Long2LongSortedMapProperty} (i.e. a <code>Map</code>) with the 078 * key being the "from-timestamp" and the value being the key-ID. The "from-timestamp" is the time 079 * (including) from which on the key will be used as "active encryption key". The "active encryption 080 * key" is the key, that will be used for encryption in the app-server at a certain moment in time. 081 * </p> 082 * 083 * @param userName the initial user to be created. 084 * @param password the password for the initial user. 085 * @param keyActivityPeriodMSec how long (in millisec) should each key be valid. If < 1, the 086 * default value of 24 hours (= 86400000 msec) will be used. 087 * @param keyStorePeriodMSec how long should the key store have fresh, unused keys. This number 088 * divided by the <code>keyActivityPeriodMSec</code> determines, how many keys must be generated. 089 * If < 1, the default value of 50 years (50 * 365 days - ignoring leap years!) will be used. 090 * @throws IOException if writing to the key-store-file failed. 091 * @throws KeyStoreNotEmptyException if the <code>KeyStore</code> is not {@link KeyStore#isEmpty() empty}. 092 */ 093 public void init(String userName, char[] password, long keyActivityPeriodMSec, long keyStorePeriodMSec) 094 throws IOException, KeyStoreNotEmptyException 095 { 096 if (!keyStore.isEmpty()) 097 throw new IllegalStateException("Key store is not empty! Cannot initialise!"); 098 099 if (keyActivityPeriodMSec < 1) 100 keyActivityPeriodMSec = 24L * 3600L * 1000L; 101 102 if (keyStorePeriodMSec < 1) 103 keyStorePeriodMSec = 50L * 365L * 24L * 3600L * 1000L; 104 105 long numberOfKeysToGenerate = 1 + keyStorePeriodMSec / keyActivityPeriodMSec; 106 logger.debug("init: Calculated numberOfKeysToGenerate={}", numberOfKeysToGenerate); 107 108 if (numberOfKeysToGenerate > Integer.MAX_VALUE) 109 throw new KeyStoreNotEmptyException("Calculated numberOfKeysToGenerate=" + numberOfKeysToGenerate + " is out of range! Maximum allowed value is " + Integer.MAX_VALUE + "! Reduce keyStorePeriodMSec or increase keyActivityPeriodMSec!"); 110 111 try { 112 keyStore.createUser(null, null, userName, password); 113 } catch (AuthenticationException e) { 114 throw new RuntimeException(e); 115 } catch (UserAlreadyExistsException e) { 116 throw new RuntimeException(e); 117 } 118 119 String authUserName = userName; 120 char[] authPassword = password; 121 122 try { 123 List<GeneratedKey> generatedKeys = keyStore.generateKeys(authUserName, authPassword, (int)numberOfKeysToGenerate); 124 long activeFromTimestamp = System.currentTimeMillis(); 125 Long2LongSortedMapProperty activeFromTimestamp2KeyIDMapProperty = keyStore.getProperty(authUserName, authPassword, Long2LongSortedMapProperty.class, PROPERTY_ACTIVE_FROM_TIMESTAMP_2_KEY_ID); 126 for (GeneratedKey generatedKey : generatedKeys) { 127 activeFromTimestamp2KeyIDMapProperty.getValue().put(activeFromTimestamp, generatedKey.getKeyID()); 128 // calculate next validFromTimestamp 129 activeFromTimestamp += keyActivityPeriodMSec; 130 } 131 132 // Put -1 as END marker. 133 activeFromTimestamp2KeyIDMapProperty.getValue().put(activeFromTimestamp, -1L); 134 135 // Write the property. 136 keyStore.setProperty(authUserName, authPassword, activeFromTimestamp2KeyIDMapProperty); 137 } catch (AuthenticationException e) { // We just created this user - if that exception really occurs here, it's definitely a RuntimeException. 138 throw new RuntimeException(e); 139 } 140 } 141 142 // /** 143 // * Get the timestamp till when the active key will be valid (excluding). This 144 // * is a convenience method delegating to {@link #getActiveKeyValidUntilExcl(String, char[], Date)} 145 // * with the argument <code>timestamp</code> being <code>null</code>. 146 // * 147 // * @param authUserName the authenticated user authorizing this action. 148 // * @param authPassword the password for authenticating the user specified by <code>authUserName</code>. 149 // * @return the timestamp at which the current active key will stop being active. 150 // * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code> 151 // * is not correct for the given <code>authUserName</code>. 152 // */ 153 // public Date getActiveKeyValidUntilExcl(String authUserName, char[] authPassword) throws AuthenticationException 154 // { 155 // return getActiveKeyValidUntilExcl(authUserName, authPassword, null); 156 // } 157 158 // /** 159 // * Get the timestamp till when the active key will be valid (excluding). The active key is 160 // * determined based on the given <code>timestamp</code> (which can be <code>null</code> to mean 'now'). 161 // * 162 // * @param authUserName the authenticated user authorizing this action. 163 // * @param authPassword the password for authenticating the user specified by <code>authUserName</code>. 164 // * @param timestamp the timestamp specifying the time at which the queried key is / was / will be active. 165 // * Can be <code>null</code>, which is interpreted as NOW. 166 // * @return the timestamp at which the given key is valid. This is always after the given timestamp (or now, if <code>timestamp == null</code>), 167 // * even if there is no key with this validity, because the key-store is extrapolated if necessary (in an eternal cycle). 168 // * <p> 169 // * For example, if the key-store was generated with daily (24 h) key-rotation and keys for the time range from 170 // * 2011-01-01 00:00 [including] until 2011-04-01 00:00 [excluding] 171 // * and the given timestamp is 2011-05-01 23:45, the active key will be exactly the same as it was 2011-02-01 23:45. Though 172 // * this key originally was valid only till 2011-02-02 00:00 [excluding], the result of this method would now be 2011-05-02 00:00. 173 // * </p> 174 // * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code> 175 // * is not correct for the given <code>authUserName</code>. 176 // */ 177 // public Date getActiveKeyValidUntilExcl(String authUserName, char[] authPassword, Date timestamp) throws AuthenticationException 178 // { 179 // return new Date(determineActiveKeyIDAndValidFromAndValidTo(authUserName, authPassword, timestamp)[2]); 180 // } 181 182 // /** 183 // * Get the currently active key's ID. This 184 // * is a convenience method delegating to {@link #getActiveKeyID(String, char[], Date)} 185 // * with the argument <code>timestamp</code> being <code>null</code>. 186 // * 187 // * @param authUserName the authenticated user authorizing this action. 188 // * @param authPassword the password for authenticating the user specified by <code>authUserName</code>. 189 // * @return 190 // * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code> 191 // * is not correct for the given <code>authUserName</code>. 192 // */ 193 // public long getActiveKeyID(String authUserName, char[] authPassword) 194 // throws AuthenticationException 195 // { 196 // return getActiveKeyID(authUserName, authPassword, null); 197 // } 198 199 /** 200 * <p> 201 * Get the details of the key which is / was / will be active at the given <code>timestamp</code>. 202 * </p> 203 * @param authUserName the authenticated user authorizing this action. 204 * @param authPassword the password for authenticating the user specified by <code>authUserName</code>. 205 * @param timestamp the timestamp at which the active key should be determined. If <code>null</code>, NOW (<code>new Date()</code>) is assumed. 206 * @return the active key at the given <code>timestamp</code>. 207 * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code> 208 * is not correct for the given <code>authUserName</code>. 209 */ 210 public ActiveKey getActiveKey(String authUserName, char[] authPassword, Date timestamp) 211 throws AuthenticationException 212 { 213 if (timestamp == null) 214 timestamp = new Date(); 215 216 Long2LongSortedMapProperty activeFromTimestamp2KeyIDMapProperty = keyStore.getProperty(authUserName, authPassword, Long2LongSortedMapProperty.class, PROPERTY_ACTIVE_FROM_TIMESTAMP_2_KEY_ID); 217 if (activeFromTimestamp2KeyIDMapProperty.getValue().isEmpty()) 218 throw new IllegalStateException("There is no property named '" + PROPERTY_ACTIVE_FROM_TIMESTAMP_2_KEY_ID + "'! Obviously the key-store was not initalised for this strategy!"); 219 220 if (activeFromTimestamp2KeyIDMapProperty.getValue().get(activeFromTimestamp2KeyIDMapProperty.getValue().lastKey()) != -1L) 221 throw new IllegalStateException("Expected last entry to be the END marker, but it is not!"); 222 223 long timestampMSec = timestamp.getTime(); 224 if (timestampMSec < activeFromTimestamp2KeyIDMapProperty.getValue().firstKey()) { 225 logger.warn("getActiveKeyID: timestamp is out of range (before). Will reuse another key via cyclic extrapolation."); 226 while (timestampMSec < activeFromTimestamp2KeyIDMapProperty.getValue().firstKey()) 227 timestampMSec += activeFromTimestamp2KeyIDMapProperty.getValue().lastKey() - activeFromTimestamp2KeyIDMapProperty.getValue().firstKey(); 228 } 229 230 if (timestampMSec >= activeFromTimestamp2KeyIDMapProperty.getValue().lastKey()) { 231 logger.warn("getActiveKeyID: timestamp is out of range (after). Will reuse another key via cyclic extrapolation."); 232 while (timestampMSec >= activeFromTimestamp2KeyIDMapProperty.getValue().lastKey()) { 233 timestampMSec -= activeFromTimestamp2KeyIDMapProperty.getValue().lastKey() - activeFromTimestamp2KeyIDMapProperty.getValue().firstKey(); 234 } 235 } 236 237 Long currentActiveFromTimestamp = activeFromTimestamp2KeyIDMapProperty.getValue().headMap(timestampMSec + 1L).lastKey(); // We add 1, because our contract is INCLUSIVE while headMap is EXCLUSIVE. 238 Long keyID = activeFromTimestamp2KeyIDMapProperty.getValue().get(currentActiveFromTimestamp); 239 if (keyID == null) 240 throw new IllegalStateException("keyID == null for currentActiveFromTimestamp == " + currentActiveFromTimestamp); 241 242 if (keyID < 0) 243 throw new IllegalStateException("keyID < 0"); 244 245 Long currentActiveUntilTimestamp = activeFromTimestamp2KeyIDMapProperty.getValue().tailMap(timestampMSec + 1L).firstKey(); 246 247 long diff = timestamp.getTime() - timestampMSec; 248 249 return new ActiveKey( 250 keyID, 251 new Date(currentActiveFromTimestamp + diff), 252 new Date(currentActiveUntilTimestamp + diff) 253 ); 254 } 255 256 /** 257 * Descriptor of the active key. 258 * 259 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 260 */ 261 public static final class ActiveKey 262 { 263 private long keyID; 264 private Date activeFromIncl; 265 private Date activeToExcl; 266 267 private ActiveKey(long keyID, Date activeFromIncl, Date activeToExcl) 268 { 269 this.keyID = keyID; 270 this.activeFromIncl = activeFromIncl; 271 this.activeToExcl = activeToExcl; 272 } 273 274 /** 275 * Get the key's identifier. 276 * @return the key-ID. 277 */ 278 public long getKeyID() { 279 return keyID; 280 } 281 /** 282 * <p> 283 * Get the timestamp from which on the key is active (including). 284 * </p> 285 * <p> 286 * This timestamp is extrapolated (if necessary) according to the timestamp given to 287 * {@link DateDependentKeyStrategy#getActiveKey(String, char[], Date)}. Please see 288 * the documentation of {@link #getActiveToExcl()} for more details about this extrapolation. 289 * </p> 290 * @return the timestamp from which on the key is active (including). 291 */ 292 public Date getActiveFromIncl() { 293 return activeFromIncl; 294 } 295 296 /** 297 * <p> 298 * Get the timestamp until which the key is active (excluding). 299 * </p> 300 * <p> 301 * This is always after (never before, never equal to) the timestamp given 302 * to {@link DateDependentKeyStrategy#getActiveKey(String, char[], Date)}, 303 * even if there is no key with this validity in the key-store, because the key-store is extrapolated if necessary 304 * (in an eternal cycle). 305 * </p> 306 * <p> 307 * For example, if the key-store was generated with daily (24 h) key-rotation and keys for the time range from 308 * 2011-01-01 00:00 [including] until 2011-04-01 00:00 [excluding] 309 * and the given timestamp is 2011-05-01 23:45, the active key will be exactly the same as it was 2011-02-01 23:45. Though 310 * this key originally was valid only till 2011-02-02 00:00 [excluding], the result of this method would now be 2011-05-02 00:00. 311 * </p> 312 * @return the timestamp until which the key is active (excluding). 313 */ 314 public Date getActiveToExcl() { 315 return activeToExcl; 316 } 317 } 318 }