| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.accumulo.core.security.crypto; |
| |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.IOException; |
| import java.security.InvalidKeyException; |
| import java.security.Key; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.SecureRandom; |
| |
| import javax.crypto.Cipher; |
| import javax.crypto.IllegalBlockSizeException; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| import org.apache.accumulo.core.conf.Property; |
| import org.apache.accumulo.core.util.CachedConfiguration; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.hadoop.fs.FileSystem; |
| import org.apache.hadoop.fs.Path; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * A {@link SecretKeyEncryptionStrategy} that gets its key from HDFS and caches it for IO. |
| */ |
| public class CachingHDFSSecretKeyEncryptionStrategy implements SecretKeyEncryptionStrategy { |
| |
| @SuppressWarnings("deprecation") |
| private static final Property INSTANCE_DFS_DIR = Property.INSTANCE_DFS_DIR; |
| private static final Logger log = LoggerFactory.getLogger(CachingHDFSSecretKeyEncryptionStrategy.class); |
| private SecretKeyCache secretKeyCache = new SecretKeyCache(); |
| |
| @Override |
| public CryptoModuleParameters encryptSecretKey(CryptoModuleParameters context) { |
| try { |
| secretKeyCache.ensureSecretKeyCacheInitialized(context); |
| doKeyEncryptionOperation(Cipher.WRAP_MODE, context); |
| } catch (IOException e) { |
| log.error("{}", e.getMessage(), e); |
| throw new RuntimeException(e); |
| } |
| return context; |
| } |
| |
| @Override |
| public CryptoModuleParameters decryptSecretKey(CryptoModuleParameters context) { |
| try { |
| secretKeyCache.ensureSecretKeyCacheInitialized(context); |
| doKeyEncryptionOperation(Cipher.UNWRAP_MODE, context); |
| } catch (IOException e) { |
| log.error("{}", e.getMessage(), e); |
| throw new RuntimeException(e); |
| } |
| return context; |
| } |
| |
| private void doKeyEncryptionOperation(int encryptionMode, CryptoModuleParameters params) throws IOException { |
| Cipher cipher = DefaultCryptoModuleUtils.getCipher(params.getAllOptions().get(Property.CRYPTO_DEFAULT_KEY_STRATEGY_CIPHER_SUITE.getKey())); |
| |
| try { |
| cipher.init(encryptionMode, new SecretKeySpec(secretKeyCache.getKeyEncryptionKey(), params.getAlgorithmName())); |
| } catch (InvalidKeyException e) { |
| log.error("{}", e.getMessage(), e); |
| throw new RuntimeException(e); |
| } |
| |
| if (Cipher.UNWRAP_MODE == encryptionMode) { |
| try { |
| Key plaintextKey = cipher.unwrap(params.getEncryptedKey(), params.getAlgorithmName(), Cipher.SECRET_KEY); |
| params.setPlaintextKey(plaintextKey.getEncoded()); |
| } catch (InvalidKeyException e) { |
| log.error("{}", e.getMessage(), e); |
| throw new RuntimeException(e); |
| } catch (NoSuchAlgorithmException e) { |
| log.error("{}", e.getMessage(), e); |
| throw new RuntimeException(e); |
| } |
| } else { |
| Key plaintextKey = new SecretKeySpec(params.getPlaintextKey(), params.getAlgorithmName()); |
| try { |
| byte[] encryptedSecretKey = cipher.wrap(plaintextKey); |
| params.setEncryptedKey(encryptedSecretKey); |
| params.setOpaqueKeyEncryptionKeyID(secretKeyCache.getPathToKeyName()); |
| } catch (InvalidKeyException e) { |
| log.error("{}", e.getMessage(), e); |
| throw new RuntimeException(e); |
| } catch (IllegalBlockSizeException e) { |
| log.error("{}", e.getMessage(), e); |
| throw new RuntimeException(e); |
| } |
| |
| } |
| } |
| |
| private static class SecretKeyCache { |
| |
| private boolean initialized = false; |
| private byte[] keyEncryptionKey; |
| private String pathToKeyName; |
| |
| public SecretKeyCache() {} |
| |
| public synchronized void ensureSecretKeyCacheInitialized(CryptoModuleParameters context) throws IOException { |
| |
| if (initialized) { |
| return; |
| } |
| |
| // First identify if the KEK already exists |
| pathToKeyName = getFullPathToKey(context); |
| |
| if (pathToKeyName == null || pathToKeyName.equals("")) { |
| pathToKeyName = Property.CRYPTO_DEFAULT_KEY_STRATEGY_KEY_LOCATION.getDefaultValue(); |
| } |
| |
| // TODO ACCUMULO-2530 Ensure volumes a properly supported |
| Path pathToKey = new Path(pathToKeyName); |
| FileSystem fs = FileSystem.get(CachedConfiguration.getInstance()); |
| |
| DataInputStream in = null; |
| try { |
| if (!fs.exists(pathToKey)) { |
| initializeKeyEncryptionKey(fs, pathToKey, context); |
| } |
| |
| in = fs.open(pathToKey); |
| |
| int keyEncryptionKeyLength = in.readInt(); |
| keyEncryptionKey = new byte[keyEncryptionKeyLength]; |
| in.readFully(keyEncryptionKey); |
| |
| initialized = true; |
| |
| } catch (IOException e) { |
| log.error("Could not initialize key encryption cache", e); |
| } finally { |
| IOUtils.closeQuietly(in); |
| } |
| } |
| |
| private void initializeKeyEncryptionKey(FileSystem fs, Path pathToKey, CryptoModuleParameters params) throws IOException { |
| DataOutputStream out = null; |
| try { |
| out = fs.create(pathToKey); |
| // Very important, lets hedge our bets |
| fs.setReplication(pathToKey, (short) 5); |
| SecureRandom random = DefaultCryptoModuleUtils.getSecureRandom(params.getRandomNumberGenerator(), params.getRandomNumberGeneratorProvider()); |
| int keyLength = params.getKeyLength(); |
| byte[] newRandomKeyEncryptionKey = new byte[keyLength / 8]; |
| random.nextBytes(newRandomKeyEncryptionKey); |
| out.writeInt(newRandomKeyEncryptionKey.length); |
| out.write(newRandomKeyEncryptionKey); |
| out.flush(); |
| } finally { |
| if (out != null) { |
| out.close(); |
| } |
| } |
| |
| } |
| |
| private String getFullPathToKey(CryptoModuleParameters params) { |
| String pathToKeyName = params.getAllOptions().get(Property.CRYPTO_DEFAULT_KEY_STRATEGY_KEY_LOCATION.getKey()); |
| String instanceDirectory = params.getAllOptions().get(INSTANCE_DFS_DIR.getKey()); |
| |
| if (pathToKeyName == null) { |
| pathToKeyName = Property.CRYPTO_DEFAULT_KEY_STRATEGY_KEY_LOCATION.getDefaultValue(); |
| } |
| |
| if (instanceDirectory == null) { |
| instanceDirectory = INSTANCE_DFS_DIR.getDefaultValue(); |
| } |
| |
| if (!pathToKeyName.startsWith("/")) { |
| pathToKeyName = "/" + pathToKeyName; |
| } |
| |
| String fullPath = instanceDirectory + pathToKeyName; |
| return fullPath; |
| } |
| |
| public byte[] getKeyEncryptionKey() { |
| return keyEncryptionKey; |
| } |
| |
| public String getPathToKeyName() { |
| return pathToKeyName; |
| } |
| } |
| |
| } |