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 }