View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.accumulo.core.security.crypto;
19  
20  import java.io.ByteArrayOutputStream;
21  import java.io.DataInputStream;
22  import java.io.DataOutputStream;
23  import java.io.IOException;
24  import java.net.URI;
25  import java.net.URISyntaxException;
26  import java.security.InvalidAlgorithmParameterException;
27  import java.security.InvalidKeyException;
28  import java.security.SecureRandom;
29  import java.util.HashMap;
30  import java.util.Map;
31  
32  import javax.crypto.Cipher;
33  import javax.crypto.CipherOutputStream;
34  import javax.crypto.spec.IvParameterSpec;
35  import javax.crypto.spec.SecretKeySpec;
36  
37  import org.apache.accumulo.core.conf.Property;
38  import org.apache.accumulo.core.util.CachedConfiguration;
39  import org.apache.hadoop.fs.FileSystem;
40  import org.apache.hadoop.fs.Path;
41  import org.apache.log4j.Logger;
42  
43  public class DefaultSecretKeyEncryptionStrategy implements SecretKeyEncryptionStrategy {
44    
45    private static final Logger log = Logger.getLogger(DefaultSecretKeyEncryptionStrategy.class); 
46    
47    public static class DefaultSecretKeyEncryptionStrategyContext implements SecretKeyEncryptionStrategyContext {
48  
49      private byte[] plaintextSecretKey;
50      private byte[] encryptedSecretKey;
51      private Map<String, String> context;
52      private String opaqueKeyId;
53      
54      @Override
55      public String getOpaqueKeyEncryptionKeyID() {
56        return opaqueKeyId;
57      }
58  
59      @Override
60      public void setOpaqueKeyEncryptionKeyID(String id) {
61        this.opaqueKeyId = id;
62      }
63  
64      @Override
65      public byte[] getPlaintextSecretKey() {
66        return plaintextSecretKey;
67      }
68  
69      @Override
70      public void setPlaintextSecretKey(byte[] key) {
71        this.plaintextSecretKey = key;
72      }
73  
74      @Override
75      public byte[] getEncryptedSecretKey() {
76        return encryptedSecretKey;
77      }
78  
79      @Override
80      public void setEncryptedSecretKey(byte[] key) {
81        this.encryptedSecretKey = key;
82      }
83  
84      @Override
85      public Map<String,String> getContext() {
86        return context;
87      }
88  
89      @Override
90      public void setContext(Map<String,String> context) {
91        this.context = context;
92      }
93    }
94    
95    
96    @Override
97    public SecretKeyEncryptionStrategyContext encryptSecretKey(SecretKeyEncryptionStrategyContext context)  {
98      String hdfsURI = context.getContext().get(Property.CRYPTO_DEFAULT_KEY_STRATEGY_HDFS_URI.getKey());
99      String pathToKeyName = context.getContext().get(Property.CRYPTO_DEFAULT_KEY_STRATEGY_KEY_LOCATION.getKey());
100     Path pathToKey = new Path(pathToKeyName);
101     
102     FileSystem fs = getHadoopFileSystem(hdfsURI);
103     try {
104       
105       doKeyEncryptionOperation(Cipher.ENCRYPT_MODE, context, pathToKeyName, pathToKey, fs);
106       
107     } catch (IOException e) {
108       log.error(e);
109       throw new RuntimeException(e);
110     }
111   
112     return context;
113     
114   }
115 
116   private void initializeKeyEncryptingKey(FileSystem fs, Path pathToKey, SecretKeyEncryptionStrategyContext context) throws IOException {
117     Map<String, String> cryptoContext = context.getContext(); 
118     DataOutputStream out = fs.create(pathToKey);
119     // Very important, lets hedge our bets
120     fs.setReplication(pathToKey, (short) 5);
121     
122     // Write number of context entries
123     out.writeInt(cryptoContext.size());
124     
125     for (String key : cryptoContext.keySet()) {
126       out.writeUTF(key);
127       out.writeUTF(cryptoContext.get(key));
128     }
129     
130     SecureRandom random = DefaultCryptoModuleUtils.getSecureRandom(cryptoContext.get(Property.CRYPTO_SECURE_RNG.getKey()), cryptoContext.get(Property.CRYPTO_SECURE_RNG_PROVIDER.getKey()));
131     int keyLength = Integer.parseInt(cryptoContext.get(Property.CRYPTO_CIPHER_KEY_LENGTH.getKey()));
132     byte[] newRandomKeyEncryptionKey = new byte[keyLength / 8];
133     
134     random.nextBytes(newRandomKeyEncryptionKey);
135     
136     Cipher cipher = DefaultCryptoModuleUtils.getCipher(cryptoContext.get(Property.CRYPTO_CIPHER_SUITE.getKey()));
137     try {
138       cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(newRandomKeyEncryptionKey, cryptoContext.get(Property.CRYPTO_CIPHER_ALGORITHM_NAME.getKey())), random);
139     } catch (InvalidKeyException e) {
140       log.error(e);
141       throw new RuntimeException(e);
142     }
143     
144     byte[] initVector = cipher.getIV();
145     
146     out.writeInt(initVector.length);
147     out.write(initVector);
148     
149     out.writeInt(newRandomKeyEncryptionKey.length);
150     out.write(newRandomKeyEncryptionKey);
151     
152     out.flush();
153     out.close();
154     
155   }
156 
157   private FileSystem getHadoopFileSystem(String hdfsURI) {
158     FileSystem fs = null;
159     
160     if (hdfsURI != null && !hdfsURI.equals("")) {
161       try {
162         fs = FileSystem.get(CachedConfiguration.getInstance());
163       } catch (IOException e) {
164         log.error(e);
165         throw new RuntimeException(e);
166       }
167     }
168     else {
169       try {
170         fs = FileSystem.get(new URI(hdfsURI), CachedConfiguration.getInstance());
171       } catch (URISyntaxException e) {
172         log.error(e);
173         throw new RuntimeException(e);
174       } catch (IOException e) {
175         log.error(e);
176         throw new RuntimeException(e);
177       }
178       
179       
180     }
181     return fs;
182   }
183   
184   @Override
185   public SecretKeyEncryptionStrategyContext decryptSecretKey(SecretKeyEncryptionStrategyContext context) {
186     String hdfsURI = context.getContext().get(Property.CRYPTO_DEFAULT_KEY_STRATEGY_HDFS_URI.getKey());
187     String pathToKeyName = context.getContext().get(Property.CRYPTO_DEFAULT_KEY_STRATEGY_KEY_LOCATION.getKey());
188     Path pathToKey = new Path(pathToKeyName);
189     
190     FileSystem fs = getHadoopFileSystem(hdfsURI);
191     try {
192       doKeyEncryptionOperation(Cipher.DECRYPT_MODE, context, pathToKeyName, pathToKey, fs);
193       
194       
195     } catch (IOException e) {
196       log.error(e);
197       throw new RuntimeException(e);
198     }
199     
200     return context;
201   }
202 
203   private void doKeyEncryptionOperation(int encryptionMode, SecretKeyEncryptionStrategyContext context, String pathToKeyName, Path pathToKey, FileSystem fs)
204       throws IOException {
205     DataInputStream in = null;
206     try {
207       if (!fs.exists(pathToKey)) {
208         
209         if (encryptionMode == Cipher.DECRYPT_MODE) {
210           log.error("There was a call to decrypt the session key but no key encryption key exists.  Either restore it, reconfigure the conf file to point to it in HDFS, or throw the affected data away and begin again.");
211           throw new RuntimeException("Could not find key encryption key file in configured location in HDFS ("+pathToKeyName+")");
212         } else {
213           initializeKeyEncryptingKey(fs, pathToKey, context);
214         }
215       }
216       in = fs.open(pathToKey);
217       
218       int numOfOpts = in.readInt();
219       Map<String, String> optsFromFile = new HashMap<String, String>();
220       
221       for (int i = 0; i < numOfOpts; i++) {
222         String key = in.readUTF();
223         String value = in.readUTF();
224         
225         optsFromFile.put(key, value);
226       }
227       
228       int ivLength = in.readInt();
229       byte[] iv = new byte[ivLength];
230       in.read(iv);
231       
232       
233       int keyEncryptionKeyLength = in.readInt();
234       byte[] keyEncryptionKey = new byte[keyEncryptionKeyLength];
235       in.read(keyEncryptionKey);
236       
237       Cipher cipher = DefaultCryptoModuleUtils.getCipher(optsFromFile.get(Property.CRYPTO_CIPHER_SUITE.getKey()));
238 
239       try {
240         cipher.init(encryptionMode, new SecretKeySpec(keyEncryptionKey, optsFromFile.get(Property.CRYPTO_CIPHER_ALGORITHM_NAME.getKey())), new IvParameterSpec(iv));
241       } catch (InvalidKeyException e) {
242         log.error(e);
243         throw new RuntimeException(e);
244       } catch (InvalidAlgorithmParameterException e) {
245         log.error(e);
246         throw new RuntimeException(e);
247       }
248 
249       ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
250       CipherOutputStream cipherStream = new CipherOutputStream(byteArrayOutputStream, cipher);
251       
252       
253       if (Cipher.DECRYPT_MODE == encryptionMode) {
254         cipherStream.write(context.getEncryptedSecretKey());
255         cipherStream.flush();        
256         byte[] plaintextSecretKey = byteArrayOutputStream.toByteArray();
257 
258         cipherStream.close();
259         
260         context.setPlaintextSecretKey(plaintextSecretKey);
261       } else {
262         cipherStream.write(context.getPlaintextSecretKey());
263         cipherStream.flush();        
264         byte[] encryptedSecretKey = byteArrayOutputStream.toByteArray();
265 
266         cipherStream.close();
267         
268         context.setEncryptedSecretKey(encryptedSecretKey);
269         context.setOpaqueKeyEncryptionKeyID(pathToKeyName);
270       }
271       
272     } finally {
273       if (in != null) {
274         in.close();
275       }
276     }
277   }
278 
279   @Override
280   public SecretKeyEncryptionStrategyContext getNewContext() {
281     return new DefaultSecretKeyEncryptionStrategyContext();
282   }
283   
284 }