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.store.crypto.keymanager; 019 020 import java.io.IOException; 021 022 import org.bouncycastle.crypto.CipherParameters; 023 import org.bouncycastle.crypto.params.KeyParameter; 024 import org.bouncycastle.crypto.params.ParametersWithIV; 025 import org.cumulus4j.crypto.Cipher; 026 import org.cumulus4j.crypto.CryptoRegistry; 027 import org.cumulus4j.crypto.MACCalculator; 028 import org.cumulus4j.keymanager.back.shared.GetActiveEncryptionKeyRequest; 029 import org.cumulus4j.keymanager.back.shared.GetActiveEncryptionKeyResponse; 030 import org.cumulus4j.keymanager.back.shared.GetKeyRequest; 031 import org.cumulus4j.keymanager.back.shared.GetKeyResponse; 032 import org.cumulus4j.keymanager.back.shared.KeyEncryptionUtil; 033 import org.cumulus4j.store.crypto.AbstractCryptoSession; 034 import org.cumulus4j.store.crypto.Ciphertext; 035 import org.cumulus4j.store.crypto.CryptoContext; 036 import org.cumulus4j.store.crypto.CryptoManager; 037 import org.cumulus4j.store.crypto.Plaintext; 038 import org.cumulus4j.store.crypto.keymanager.messagebroker.MessageBroker; 039 import org.cumulus4j.store.crypto.keymanager.messagebroker.MessageBrokerRegistry; 040 import org.cumulus4j.store.model.EncryptionCoordinateSet; 041 import org.slf4j.Logger; 042 import org.slf4j.LoggerFactory; 043 044 /** 045 * <p> 046 * Implementation of {@link org.cumulus4j.store.crypto.CryptoSession CryptoSession} working with a 047 * key-manager as shown in <a target="_blank" href="http://cumulus4j.org/1.1.1/documentation/deployment-scenarios.html">Deployment scenarios</a>. 048 * </p> 049 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 050 */ 051 public class KeyManagerCryptoSession 052 extends AbstractCryptoSession 053 { 054 private static final Logger logger = LoggerFactory.getLogger(KeyManagerCryptoSession.class); 055 056 // private static final BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider(); 057 // static { 058 // Security.insertProviderAt(bouncyCastleProvider, 2); 059 // } 060 061 // private static final ChecksumAlgorithm _activeChecksumAlgorithm = ChecksumAlgorithm.SHA1; 062 // private ChecksumAlgorithm getActiveChecksumAlgorithm() 063 // { 064 // return _activeChecksumAlgorithm; 065 // } 066 // 067 // private static final EncryptionAlgorithm _activeEncryptionAlgorithm = EncryptionAlgorithm.Twofish_CBC_PKCS5Padding; // TODO this should be configurable! 068 // private EncryptionAlgorithm getActiveEncryptionAlgorithm() 069 // { 070 // return _activeEncryptionAlgorithm; 071 // } 072 073 private MessageBroker getMessageBroker() { 074 return MessageBrokerRegistry.sharedInstance().getActiveMessageBroker(); 075 } 076 077 /** 078 * <p> 079 * The <b>a</b>symmetric encryption algorithm used to encrypt the keys when they are sent from key-manager 080 * to here (app-server). 081 * </p> 082 * <p> 083 * Alternatively, we could use "EC": http://en.wikipedia.org/wiki/Elliptic_curve_cryptography 084 * </p> 085 */ 086 private static final String keyEncryptionTransformation = "RSA//OAEPWITHSHA1ANDMGF1PADDING"; 087 088 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 089 090 /** 091 * {@inheritDoc} 092 * <p> 093 * The implementation in {@link KeyManagerCryptoSession} stores every plaintext 094 * encoded in the following form: 095 * </p> 096 * <table border="1"> 097 * <tbody> 098 * <tr> 099 * <td align="right" valign="top"><b>Bytes</b></td><td><b>Description</b></td> 100 * </tr><tr> 101 * <td align="right" valign="top">1</td><td>Version number</td> 102 * </tr><tr> 103 * <td align="right" valign="top">2</td><td>{@link EncryptionCoordinateSet#getEncryptionCoordinateSetID()} (only 2 bytes, thus limiting to 65K possible values)</td> 104 * </tr><tr> 105 * <td align="right" valign="top">1</td><td><i>ivLen</i>: Length of the IV in bytes</td> 106 * </tr><tr> 107 * <td align="right" valign="top"><i>ivLen</i></td><td>Actual IV (random initialisation vector).</td> 108 * </tr><tr> 109 * <td align="right" valign="top">1</td><td><i>macKeyLen</i>: <a target="_blank" href="http://en.wikipedia.org/wiki/Message_authentication_code">MAC</a>'s key length in bytes</td> 110 * </tr><tr> 111 * <td align="right" valign="top">1</td><td><i>macIVLen</i>: MAC's IV length in bytes</td> 112 * </tr><tr> 113 * <td align="right" valign="top">1</td><td><i>macLen</i>: Actual MAC's length in bytes</td> 114 * </tr><tr> 115 * <td colspan="2"> 116 * <table bgcolor="#F0F0F0" border="1" width="100%"> 117 * <tbody> 118 * <tr> 119 * <td bgcolor="#D0D0D0" colspan="2"><b>ENCRYPTED</b></td> 120 * </tr><tr> 121 * <td align="right" valign="top"><b>Bytes</b></td><td><b>Description</b></td> 122 * </tr><tr> 123 * <td align="right" valign="top"><i>macKeyLen</i></td><td>MAC's key (random)</td> 124 * </tr><tr> 125 * <td align="right" valign="top"><i>macIVLen</i></td><td>MAC's IV (random)</td> 126 * </tr><tr> 127 * <td align="right" valign="top"><i>all until MAC</i></td><td>Actual data</td> 128 * </tr><tr> 129 * <td align="right" valign="top"><i>macLen</i></td><td>Actual MAC</td> 130 * </tr> 131 * </tbody> 132 * </table> 133 * </td> 134 * </tr> 135 * </tbody> 136 * </table> 137 */ 138 @Override 139 public Ciphertext encrypt(CryptoContext cryptoContext, Plaintext plaintext) 140 { 141 EncryptionCoordinateSet encryptionCoordinateSet = cryptoContext.getEncryptionCoordinateSetManager().createEncryptionCoordinateSet( 142 cryptoContext.getPersistenceManagerConnection(), 143 getCryptoManager().getEncryptionAlgorithm(), 144 getCryptoManager().getMACAlgorithm() 145 ); 146 String activeEncryptionAlgorithm = encryptionCoordinateSet.getCipherTransformation(); 147 148 if (encryptionCoordinateSet.getEncryptionCoordinateSetID() < 0) 149 throw new IllegalStateException("The encryptionCoordinateSetID = " + encryptionCoordinateSet.getEncryptionCoordinateSetID() + " is out of range! It must be >= 0!!!"); 150 151 if (encryptionCoordinateSet.getEncryptionCoordinateSetID() > (2 * Short.MAX_VALUE)) 152 throw new IllegalStateException("The encryptionCoordinateSetID is out of range! The maximum is " + (2 * Short.MAX_VALUE) + ", because the value is encoded as UNsigned 2-byte-number! This means, you changed the encryption algorithm or the MAC algorithm too often. Switch back to settings you already used before!"); 153 154 CryptoCache cryptoCache = ((KeyManagerCryptoManager)getCryptoManager()).getCryptoCache(); 155 156 CryptoCacheKeyDecrypterEntry keyDecryptor = null; 157 CryptoCacheCipherEntry encrypter = null; 158 try { 159 long activeEncryptionKeyID = cryptoCache.getActiveEncryptionKeyID(); 160 if (activeEncryptionKeyID >= 0) 161 encrypter = cryptoCache.acquireEncrypter(activeEncryptionAlgorithm, activeEncryptionKeyID); 162 163 if (encrypter == null) { 164 keyDecryptor = cryptoCache.acquireKeyDecryptor(keyEncryptionTransformation); 165 166 GetActiveEncryptionKeyResponse getActiveEncryptionKeyResponse; 167 try { 168 GetActiveEncryptionKeyRequest getActiveEncryptionKeyRequest = new GetActiveEncryptionKeyRequest( 169 getCryptoSessionID(), keyEncryptionTransformation, keyDecryptor.getKeyEncryptionKey().getEncodedPublicKey() 170 ); 171 getActiveEncryptionKeyResponse = getMessageBroker().query( 172 GetActiveEncryptionKeyResponse.class, 173 getActiveEncryptionKeyRequest 174 ); 175 } catch (Exception e) { 176 logger.warn("Could not query active encryption key: " + e, e); 177 throw new RuntimeException(e); 178 } 179 180 byte[] keyEncodedPlain = KeyEncryptionUtil.decryptKey(keyDecryptor.getKeyDecryptor(), getActiveEncryptionKeyResponse.getKeyEncodedEncrypted()); 181 182 activeEncryptionKeyID = getActiveEncryptionKeyResponse.getKeyID(); 183 cryptoCache.setActiveEncryptionKeyID(activeEncryptionKeyID, getActiveEncryptionKeyResponse.getActiveUntilExcl()); 184 encrypter = cryptoCache.acquireEncrypter(activeEncryptionAlgorithm, activeEncryptionKeyID, keyEncodedPlain); 185 } 186 187 Cipher cipher = encrypter.getCipher(); 188 189 byte[] mac = EMPTY_BYTE_ARRAY; 190 byte[] macKey = EMPTY_BYTE_ARRAY; 191 byte[] macIV = EMPTY_BYTE_ARRAY; 192 193 if (!CryptoManager.MAC_ALGORITHM_NONE.equals(encryptionCoordinateSet.getMACAlgorithm())) { 194 MACCalculator macCalculator = CryptoRegistry.sharedInstance().createMACCalculator(encryptionCoordinateSet.getMACAlgorithm(), true); 195 mac = macCalculator.doFinal(plaintext.getData()); 196 197 if (macCalculator.getParameters() instanceof ParametersWithIV) { 198 ParametersWithIV pwiv = (ParametersWithIV) macCalculator.getParameters(); 199 macIV = pwiv.getIV(); 200 macKey = ((KeyParameter)pwiv.getParameters()).getKey(); 201 } 202 else if (macCalculator.getParameters() instanceof KeyParameter) { 203 macKey = ((KeyParameter)macCalculator.getParameters()).getKey(); 204 } 205 else 206 throw new IllegalStateException("macCalculator.getParameters() returned an instance of an unknown type: " + (macCalculator.getParameters() == null ? null : macCalculator.getParameters().getClass().getName())); 207 } 208 209 byte[] iv = ((ParametersWithIV)cipher.getParameters()).getIV(); 210 211 if (iv.length > 255) 212 throw new IllegalStateException("IV too long! Cannot encode length in 1 byte!"); 213 214 if (macKey.length > 255) 215 throw new IllegalStateException("macKey too long! Cannot encode length in 1 byte!"); 216 217 if (macIV.length > 255) 218 throw new IllegalStateException("macIV too long! Cannot encode length in 1 byte!"); 219 220 if (mac.length > 255) 221 throw new IllegalStateException("mac too long! Cannot encode length in 1 byte!"); 222 223 int outLength = ( 224 1 // version 225 + 2 // encryptionCoordinateSetID 226 + 1 // IV length in bytes 227 + iv.length // actual IV 228 + 1 // macKeyLength in bytes 229 + 1 // macIVLength in bytes 230 + 1 // MAC length in bytes 231 + cipher.getOutputSize( 232 macKey.length // actual MAC key 233 + macIV.length // actual MAC IV 234 + plaintext.getData().length // actual plaintext 235 + mac.length // actual MAC 236 ) 237 ); 238 239 byte[] out = new byte[outLength]; 240 int outOff = 0; 241 out[outOff++] = 1; // version 1 242 243 // encryptionCoordinateSetID as UNsigned short 244 out[outOff++] = (byte)(encryptionCoordinateSet.getEncryptionCoordinateSetID() >>> 8); 245 out[outOff++] = (byte)encryptionCoordinateSet.getEncryptionCoordinateSetID(); 246 247 // IV length 248 out[outOff++] = (byte)iv.length; 249 250 // actual IV 251 System.arraycopy(iv, 0, out, outOff, iv.length); 252 outOff += iv.length; 253 254 out[outOff++] = (byte)macKey.length; 255 out[outOff++] = (byte)macIV.length; 256 out[outOff++] = (byte)mac.length; 257 258 outOff += cipher.update(macKey, 0, macKey.length, out, outOff); 259 outOff += cipher.update(macIV, 0, macIV.length, out, outOff); 260 outOff += cipher.update(plaintext.getData(), 0, plaintext.getData().length, out, outOff); 261 outOff += cipher.update(mac, 0, mac.length, out, outOff); 262 outOff += cipher.doFinal(out, outOff); 263 264 if (outOff < outLength) { 265 logger.warn( 266 "encrypt: Output byte array was created bigger than necessary. Will shrink it now. outOff={} encryptedLength={}", 267 outOff, outLength 268 ); 269 byte tmp[] = new byte[outOff]; 270 System.arraycopy(out, 0, tmp, 0, tmp.length); 271 out = tmp; 272 } 273 274 Ciphertext ciphertext = new Ciphertext(); 275 ciphertext.setData(out); 276 ciphertext.setKeyID(activeEncryptionKeyID); 277 return ciphertext; 278 } catch (RuntimeException e) { 279 logger.error("encrypt: " + e, e); 280 throw e; 281 } catch (Exception e) { 282 logger.error("encrypt: " + e, e); 283 throw new RuntimeException(e); 284 } finally { 285 cryptoCache.releaseKeyDecryptor(keyDecryptor); 286 cryptoCache.releaseCipherEntry(encrypter); 287 } 288 } 289 290 @Override 291 public Plaintext decrypt(CryptoContext cryptoContext, Ciphertext ciphertext) 292 { 293 CryptoCache cryptoCache = ((KeyManagerCryptoManager)getCryptoManager()).getCryptoCache(); 294 295 CryptoCacheKeyDecrypterEntry keyDecryptor = null; 296 CryptoCacheCipherEntry decrypter = null; 297 try { 298 long keyID = ciphertext.getKeyID(); 299 int inOff = 0; 300 byte[] in = ciphertext.getData(); 301 int version = in[inOff++] & 0xff; 302 if (version != 1) 303 throw new IllegalArgumentException("Ciphertext is of version " + version + " which is not supported!"); 304 305 int encryptionCoordinateSetID = (in[inOff++] << 8) & 0xffff; 306 encryptionCoordinateSetID += in[inOff++] & 0xff; 307 308 EncryptionCoordinateSet encryptionCoordinateSet = cryptoContext.getEncryptionCoordinateSetManager().getEncryptionCoordinateSet( 309 cryptoContext.getPersistenceManagerConnection(), encryptionCoordinateSetID 310 ); 311 if (encryptionCoordinateSet == null) 312 throw new IllegalStateException("There is no EncryptionCoordinateSet with encryptionCoordinateSetID=" + encryptionCoordinateSetID + "!"); 313 314 int ivLength = in[inOff++] & 0xff; 315 byte[] iv = new byte[ivLength]; 316 System.arraycopy(in, inOff, iv, 0, iv.length); 317 inOff += iv.length; 318 319 int macKeyLength = in[inOff++] & 0xff; 320 int macIVLength = in[inOff++] & 0xff; 321 int macLength = in[inOff++] & 0xff; 322 323 decrypter = cryptoCache.acquireDecrypter(encryptionCoordinateSet.getCipherTransformation(), keyID, iv); 324 if (decrypter == null) { 325 keyDecryptor = cryptoCache.acquireKeyDecryptor(keyEncryptionTransformation); 326 327 GetKeyResponse getKeyResponse; 328 try { 329 GetKeyRequest getKeyRequest = new GetKeyRequest( 330 getCryptoSessionID(), ciphertext.getKeyID(), 331 keyEncryptionTransformation, keyDecryptor.getKeyEncryptionKey().getEncodedPublicKey() 332 ); 333 getKeyResponse = getMessageBroker().query( 334 GetKeyResponse.class, getKeyRequest 335 ); 336 } catch (Exception e) { 337 logger.warn("Could not query key " + ciphertext.getKeyID() + ": " + e, e); 338 throw new RuntimeException(e); 339 } 340 341 byte[] keyEncodedPlain = KeyEncryptionUtil.decryptKey(keyDecryptor.getKeyDecryptor(), getKeyResponse.getKeyEncodedEncrypted()); 342 343 decrypter = cryptoCache.acquireDecrypter(encryptionCoordinateSet.getCipherTransformation(), keyID, keyEncodedPlain, iv); 344 } 345 346 int inCryptLength = in.length - inOff; 347 int outLength = decrypter.getCipher().getOutputSize(inCryptLength); 348 byte[] out = new byte[outLength]; 349 int outOff = 0; 350 outOff += decrypter.getCipher().update(in, inOff, inCryptLength, out, 0); 351 outOff += decrypter.getCipher().doFinal(out, outOff); 352 353 if (logger.isDebugEnabled() && outOff != outLength) 354 logger.debug("decrypt: precalculated output-size does not match actually written output: expected={} actual={}", outLength, outOff); 355 356 int dataOff = 0; 357 MACCalculator macCalculator = null; 358 if (!CryptoManager.MAC_ALGORITHM_NONE.equals(encryptionCoordinateSet.getMACAlgorithm())) { 359 macCalculator = CryptoRegistry.sharedInstance().createMACCalculator(encryptionCoordinateSet.getMACAlgorithm(), false); 360 361 CipherParameters macKeyParam = new KeyParameter(out, 0, macKeyLength); 362 dataOff += macKeyLength; 363 364 CipherParameters macParams; 365 if (macIVLength == 0) 366 macParams = macKeyParam; 367 else { 368 macParams = new ParametersWithIV(macKeyParam, out, dataOff, macIVLength); 369 dataOff += macIVLength; 370 } 371 372 macCalculator.init(macParams); 373 } 374 375 int dataLength = outOff - dataOff - macLength; 376 int macOff = dataOff + dataLength; 377 378 if (macCalculator != null) { 379 byte[] newMAC = new byte[macCalculator.getMacSize()]; 380 macCalculator.update(out, dataOff, dataLength); 381 macCalculator.doFinal(newMAC, 0); 382 383 if (newMAC.length != macLength) 384 throw new IOException("MACs have different length! Expected MAC has " + macLength + " bytes and newly calculated MAC has " + newMAC.length + " bytes!"); 385 386 for (int i = 0; i < macLength; ++i) { 387 byte expected = out[macOff + i]; 388 if (expected != newMAC[i]) 389 throw new IOException("MAC mismatch! mac[" + i + "] was expected to be " + expected + " but was " + newMAC[i]); 390 } 391 } 392 393 byte[] decrypted = new byte[dataLength]; 394 System.arraycopy(out, dataOff, decrypted, 0, decrypted.length); 395 Plaintext plaintext = new Plaintext(); 396 plaintext.setData(decrypted); 397 return plaintext; 398 } catch (RuntimeException e) { 399 logger.error("decrypt: " + e, e); 400 throw e; 401 } catch (Exception e) { 402 logger.error("decrypt: " + e, e); 403 throw new RuntimeException(e); 404 } finally { 405 cryptoCache.releaseKeyDecryptor(keyDecryptor); 406 cryptoCache.releaseCipherEntry(decrypter); 407 } 408 } 409 410 @Override 411 public void close() { 412 super.close(); 413 414 // Our caches are used across multiple sessions for performance reasons, 415 // hence we cannot close the caches here (maybe we might consider closing the 416 // cache when the last session is closed, later). 417 418 doNothing(); // suppress PMD warning - I want this overridden method here in this class for documentation reasons. Marco :-) 419 } 420 421 private static final void doNothing() { } 422 }