1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.accumulo.core.security.crypto;
18
19 import java.io.BufferedInputStream;
20 import java.io.BufferedOutputStream;
21 import java.io.ByteArrayOutputStream;
22 import java.io.DataInputStream;
23 import java.io.DataOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.io.PushbackInputStream;
28 import java.security.InvalidAlgorithmParameterException;
29 import java.security.InvalidKeyException;
30 import java.security.SecureRandom;
31 import java.util.HashMap;
32 import java.util.Map;
33
34 import javax.crypto.Cipher;
35 import javax.crypto.CipherInputStream;
36 import javax.crypto.CipherOutputStream;
37 import javax.crypto.spec.IvParameterSpec;
38 import javax.crypto.spec.SecretKeySpec;
39
40 import org.apache.accumulo.core.conf.Property;
41 import org.apache.log4j.Logger;
42
43 /**
44 * This class contains the gritty details around setting up encrypted streams for reading and writing the log file. It obeys the interface CryptoModule, which
45 * other developers can implement to change out this logic as necessary.
46 *
47 */
48
49 @SuppressWarnings("deprecation")
50 public class DefaultCryptoModule implements CryptoModule {
51
52
53
54 private static final String ENCRYPTION_HEADER_MARKER = "---Log File Encrypted (v1)---";
55 private static Logger log = Logger.getLogger(DefaultCryptoModule.class);
56
57 public DefaultCryptoModule() {}
58
59 @Override
60 public OutputStream getEncryptingOutputStream(OutputStream out, Map<String,String> cryptoOpts) throws IOException {
61
62 log.debug("Initializing crypto output stream");
63
64 String cipherSuite = cryptoOpts.get(Property.CRYPTO_CIPHER_SUITE.getKey());
65
66 if (cipherSuite.equals("NullCipher")) {
67 return out;
68 }
69
70 String algorithmName = cryptoOpts.get(Property.CRYPTO_CIPHER_ALGORITHM_NAME.getKey());
71 String secureRNG = cryptoOpts.get(Property.CRYPTO_SECURE_RNG.getKey());
72 String secureRNGProvider = cryptoOpts.get(Property.CRYPTO_SECURE_RNG_PROVIDER.getKey());
73 SecureRandom secureRandom = DefaultCryptoModuleUtils.getSecureRandom(secureRNG, secureRNGProvider);
74 int keyLength = Integer.parseInt(cryptoOpts.get(Property.CRYPTO_CIPHER_KEY_LENGTH.getKey()));
75
76 byte[] randomKey = new byte[keyLength / 8];
77
78 Map<CryptoInitProperty,Object> cryptoInitParams = new HashMap<CryptoInitProperty,Object>();
79
80 secureRandom.nextBytes(randomKey);
81 cryptoInitParams.put(CryptoInitProperty.PLAINTEXT_SESSION_KEY, randomKey);
82
83 SecretKeyEncryptionStrategy keyEncryptionStrategy = CryptoModuleFactory.getSecretKeyEncryptionStrategy(cryptoOpts
84 .get(Property.CRYPTO_SECRET_KEY_ENCRYPTION_STRATEGY_CLASS.getKey()));
85 SecretKeyEncryptionStrategyContext keyEncryptionStrategyContext = keyEncryptionStrategy.getNewContext();
86
87 keyEncryptionStrategyContext.setPlaintextSecretKey(randomKey);
88 keyEncryptionStrategyContext.setContext(cryptoOpts);
89
90 keyEncryptionStrategyContext = keyEncryptionStrategy.encryptSecretKey(keyEncryptionStrategyContext);
91
92 byte[] encryptedRandomKey = keyEncryptionStrategyContext.getEncryptedSecretKey();
93 String opaqueId = keyEncryptionStrategyContext.getOpaqueKeyEncryptionKeyID();
94
95 OutputStream cipherOutputStream = getEncryptingOutputStream(out, cryptoOpts, cryptoInitParams);
96
97
98 byte[] initVector = (byte[]) cryptoInitParams.get(CryptoInitProperty.INITIALIZATION_VECTOR);
99
100 DataOutputStream dataOut = new DataOutputStream(out);
101
102
103
104 dataOut.writeUTF(ENCRYPTION_HEADER_MARKER);
105
106
107
108 dataOut.writeUTF(cipherSuite);
109 dataOut.writeUTF(algorithmName);
110
111
112 dataOut.writeInt(initVector.length);
113 dataOut.write(initVector);
114
115
116 dataOut.writeUTF(opaqueId);
117 dataOut.writeInt(encryptedRandomKey.length);
118 dataOut.write(encryptedRandomKey);
119
120
121
122
123
124 return cipherOutputStream;
125 }
126
127 @Override
128 public InputStream getDecryptingInputStream(InputStream in, Map<String,String> cryptoOpts) throws IOException {
129 DataInputStream dataIn = new DataInputStream(in);
130
131 String marker = dataIn.readUTF();
132
133 log.debug("Read encryption header");
134 if (marker.equals(ENCRYPTION_HEADER_MARKER)) {
135
136 String cipherSuiteFromFile = dataIn.readUTF();
137 String algorithmNameFromFile = dataIn.readUTF();
138
139
140 int initVectorLength = dataIn.readInt();
141 byte[] initVector = new byte[initVectorLength];
142 dataIn.read(initVector, 0, initVectorLength);
143
144
145 String opaqueId = dataIn.readUTF();
146 int encryptedSecretKeyLength = dataIn.readInt();
147 byte[] encryptedSecretKey = new byte[encryptedSecretKeyLength];
148 dataIn.read(encryptedSecretKey);
149
150 SecretKeyEncryptionStrategy keyEncryptionStrategy = CryptoModuleFactory.getSecretKeyEncryptionStrategy(cryptoOpts
151 .get(Property.CRYPTO_SECRET_KEY_ENCRYPTION_STRATEGY_CLASS.getKey()));
152 SecretKeyEncryptionStrategyContext keyEncryptionStrategyContext = keyEncryptionStrategy.getNewContext();
153
154 keyEncryptionStrategyContext.setOpaqueKeyEncryptionKeyID(opaqueId);
155 keyEncryptionStrategyContext.setContext(cryptoOpts);
156 keyEncryptionStrategyContext.setEncryptedSecretKey(encryptedSecretKey);
157
158 keyEncryptionStrategyContext = keyEncryptionStrategy.decryptSecretKey(keyEncryptionStrategyContext);
159
160 byte[] secretKey = keyEncryptionStrategyContext.getPlaintextSecretKey();
161
162
163
164
165
166 Map<CryptoModule.CryptoInitProperty,Object> cryptoInitParams = new HashMap<CryptoModule.CryptoInitProperty,Object>();
167 cryptoInitParams.put(CryptoInitProperty.CIPHER_SUITE, cipherSuiteFromFile);
168 cryptoInitParams.put(CryptoInitProperty.ALGORITHM_NAME, algorithmNameFromFile);
169 cryptoInitParams.put(CryptoInitProperty.PLAINTEXT_SESSION_KEY, secretKey);
170 cryptoInitParams.put(CryptoInitProperty.INITIALIZATION_VECTOR, initVector);
171
172 InputStream cipherInputStream = getDecryptingInputStream(dataIn, cryptoOpts, cryptoInitParams);
173 return cipherInputStream;
174
175 } else {
176
177
178 ByteArrayOutputStream tempByteOut = new ByteArrayOutputStream();
179 DataOutputStream tempOut = new DataOutputStream(tempByteOut);
180 tempOut.writeUTF(marker);
181
182 byte[] bytesToPutBack = tempByteOut.toByteArray();
183
184 PushbackInputStream pushbackStream = new PushbackInputStream(in, bytesToPutBack.length);
185 pushbackStream.unread(bytesToPutBack);
186
187 return pushbackStream;
188 }
189
190 }
191
192 @Override
193 public OutputStream getEncryptingOutputStream(OutputStream out, Map<String,String> conf, Map<CryptoModule.CryptoInitProperty,Object> cryptoInitParams) {
194
195 log.debug("Initializing crypto output stream");
196
197 String cipherSuite = conf.get(Property.CRYPTO_CIPHER_SUITE.getKey());
198
199 if (cipherSuite.equals("NullCipher")) {
200 return out;
201 }
202
203 String algorithmName = conf.get(Property.CRYPTO_CIPHER_ALGORITHM_NAME.getKey());
204 String secureRNG = conf.get(Property.CRYPTO_SECURE_RNG.getKey());
205 String secureRNGProvider = conf.get(Property.CRYPTO_SECURE_RNG_PROVIDER.getKey());
206 int keyLength = Integer.parseInt(conf.get(Property.CRYPTO_CIPHER_KEY_LENGTH.getKey()));
207 String keyStrategyName = conf.get(Property.CRYPTO_SECRET_KEY_ENCRYPTION_STRATEGY_CLASS.getKey());
208
209 log.debug(String.format(
210 "Using cipher suite \"%s\" (algorithm \"%s\") with key length %d with RNG \"%s\" and RNG provider \"%s\" and key encryption strategy %s", cipherSuite,
211 algorithmName, keyLength, secureRNG, secureRNGProvider, keyStrategyName));
212
213 SecureRandom secureRandom = DefaultCryptoModuleUtils.getSecureRandom(secureRNG, secureRNGProvider);
214 Cipher cipher = DefaultCryptoModuleUtils.getCipher(cipherSuite);
215 byte[] randomKey = (byte[]) cryptoInitParams.get(CryptoInitProperty.PLAINTEXT_SESSION_KEY);
216 byte[] initVector = (byte[]) cryptoInitParams.get(CryptoInitProperty.INITIALIZATION_VECTOR);
217
218
219 if (initVector != null) {
220
221 try {
222 cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(randomKey, algorithmName), new IvParameterSpec(initVector));
223 } catch (InvalidKeyException e) {
224 log.error("Accumulo encountered an unknown error in generating the secret key object (SecretKeySpec) for an encrypted stream");
225 throw new RuntimeException(e);
226 } catch (InvalidAlgorithmParameterException e) {
227 log.error("Accumulo encountered an unknown error in generating the secret key object (SecretKeySpec) for an encrypted stream");
228 throw new RuntimeException(e);
229 }
230
231 } else {
232
233
234 try {
235 cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(randomKey, algorithmName), secureRandom);
236 } catch (InvalidKeyException e) {
237 log.error("Accumulo encountered an unknown error in generating the secret key object (SecretKeySpec) for the write-ahead log");
238 throw new RuntimeException(e);
239 }
240
241
242
243 initVector = cipher.getIV();
244 cryptoInitParams.put(CryptoInitProperty.INITIALIZATION_VECTOR, initVector);
245 }
246
247 CipherOutputStream cipherOutputStream = new CipherOutputStream(out, cipher);
248 BufferedOutputStream bufferedCipherOutputStream = new BufferedOutputStream(cipherOutputStream);
249
250 return bufferedCipherOutputStream;
251 }
252
253 @Override
254 public InputStream getDecryptingInputStream(InputStream in, Map<String,String> cryptoOpts, Map<CryptoModule.CryptoInitProperty,Object> cryptoInitParams)
255 throws IOException {
256 String cipherSuite = cryptoOpts.get(Property.CRYPTO_CIPHER_SUITE.getKey());
257 String algorithmName = cryptoOpts.get(Property.CRYPTO_CIPHER_ALGORITHM_NAME.getKey());
258 String cipherSuiteFromInitParams = (String) cryptoInitParams.get(CryptoInitProperty.CIPHER_SUITE);
259 String algorithmNameFromInitParams = (String) cryptoInitParams.get(CryptoInitProperty.ALGORITHM_NAME);
260 byte[] initVector = (byte[]) cryptoInitParams.get(CryptoInitProperty.INITIALIZATION_VECTOR);
261 byte[] secretKey = (byte[]) cryptoInitParams.get(CryptoInitProperty.PLAINTEXT_SESSION_KEY);
262
263 if (initVector == null || secretKey == null || cipherSuiteFromInitParams == null || algorithmNameFromInitParams == null) {
264 log.error("Called getDecryptingInputStream() without proper crypto init params. Need initVector, plaintext key, cipher suite and algorithm name");
265 throw new RuntimeException("Called getDecryptingInputStream() without initialization vector and/or plaintext session key");
266 }
267
268
269 if (!cipherSuiteFromInitParams.equals(cipherSuite) || !algorithmNameFromInitParams.equals(algorithmName))
270 log.warn(String.format("Configured cipher suite and algorithm (\"%s\" and \"%s\") is different "
271 + "from cipher suite found in log file (\"%s\" and \"%s\")", cipherSuite, algorithmName, cipherSuiteFromInitParams, algorithmNameFromInitParams));
272
273 Cipher cipher = DefaultCryptoModuleUtils.getCipher(cipherSuiteFromInitParams);
274
275 try {
276 cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secretKey, algorithmNameFromInitParams), new IvParameterSpec(initVector));
277 } catch (InvalidKeyException e) {
278 log.error("Error when trying to initialize cipher with secret key");
279 throw new RuntimeException(e);
280 } catch (InvalidAlgorithmParameterException e) {
281 log.error("Error when trying to initialize cipher with initialization vector");
282 throw new RuntimeException(e);
283 }
284
285 BufferedInputStream bufferedDecryptingInputStream = new BufferedInputStream(new CipherInputStream(in, cipher));
286
287 return bufferedDecryptingInputStream;
288
289 }
290 }