Repository: knox Updated Branches: refs/heads/master 551505640 -> d0726a227
KNOX-944 - Make PBE Hashing, Iteration Count and Salt Configurable and Manageable Project: http://git-wip-us.apache.org/repos/asf/knox/repo Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/d0726a22 Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/d0726a22 Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/d0726a22 Branch: refs/heads/master Commit: d0726a227d4f10e57489554b8adf8406e6d96920 Parents: 5515056 Author: Larry McCay <[email protected]> Authored: Sat May 20 12:26:32 2017 -0400 Committer: Larry McCay <[email protected]> Committed: Sat May 20 12:35:45 2017 -0400 ---------------------------------------------------------------------- .../gateway/config/impl/GatewayConfigImpl.java | 35 ++++ .../security/impl/DefaultCryptoService.java | 14 +- .../security/impl/DefaultMasterService.java | 2 +- .../services/security/CryptoServiceTest.java | 75 ++++++-- .../hadoop/gateway/config/GatewayConfig.java | 42 +++++ .../services/security/MasterService.java | 2 - .../services/security/impl/AESEncryptor.java | 152 --------------- .../security/impl/CMFMasterService.java | 15 +- .../security/impl/ConfigurableEncryptor.java | 188 +++++++++++++++++++ .../hadoop/gateway/GatewayTestConfig.java | 29 +++ .../hadoop/gateway/GatewayTestConfig.java | 30 +++ 11 files changed, 408 insertions(+), 176 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java ---------------------------------------------------------------------- diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java index 3952d7b..368787a 100644 --- a/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java +++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java @@ -192,6 +192,12 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig { public static final String COOKIE_SCOPING_ENABLED = GATEWAY_CONFIG_FILE_PREFIX + ".scope.cookies.feature.enabled"; public static final boolean DEFAULT_COOKIE_SCOPING_FEATURE_ENABLED = false; + private static final String CRYPTO_ALGORITHM = GATEWAY_CONFIG_FILE_PREFIX + ".crypto.algorithm"; + private static final String CRYPTO_PBE_ALGORITHM = GATEWAY_CONFIG_FILE_PREFIX + ".crypto.pbe.algorithm"; + private static final String CRYPTO_TRANSFORMATION = GATEWAY_CONFIG_FILE_PREFIX + ".crypto.transformation"; + private static final String CRYPTO_SALTSIZE = GATEWAY_CONFIG_FILE_PREFIX + ".crypto.salt.size"; + private static final String CRYPTO_ITERATION_COUNT = GATEWAY_CONFIG_FILE_PREFIX + ".crypto.iteration.count"; + private static final String CRYPTO_KEY_LENGTH = GATEWAY_CONFIG_FILE_PREFIX + ".crypto.key.length"; private static List<String> DEFAULT_GLOBAL_RULES_SERVICES; @@ -824,4 +830,33 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig { return value; } + @Override + public String getAlgorithm() { + return getVar(CRYPTO_ALGORITHM, null); + } + + @Override + public String getPBEAlgorithm() { + return getVar(CRYPTO_PBE_ALGORITHM, null); + } + + @Override + public String getTransformation() { + return getVar(CRYPTO_TRANSFORMATION, null); + } + + @Override + public String getSaltSize() { + return getVar(CRYPTO_SALTSIZE, null); + } + + @Override + public String getIterationCount() { + return getVar(CRYPTO_ITERATION_COUNT, null); + } + + @Override + public String getKeyLength() { + return getVar(CRYPTO_KEY_LENGTH, null); + } } http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-server/src/main/java/org/apache/hadoop/gateway/services/security/impl/DefaultCryptoService.java ---------------------------------------------------------------------- diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/security/impl/DefaultCryptoService.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/security/impl/DefaultCryptoService.java index b8348dc..b6ef5a3 100644 --- a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/security/impl/DefaultCryptoService.java +++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/security/impl/DefaultCryptoService.java @@ -43,7 +43,9 @@ public class DefaultCryptoService implements CryptoService { private AliasService as = null; private KeystoreService ks = null; - private HashMap<String,AESEncryptor> encryptorCache = new HashMap<>(); + private HashMap<String,ConfigurableEncryptor> encryptorCache = + new HashMap<String,ConfigurableEncryptor>(); + private GatewayConfig config = null; public void setKeystoreService(KeystoreService ks) { this.ks = ks; @@ -56,7 +58,8 @@ public class DefaultCryptoService implements CryptoService { @Override public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException { - if (as == null) { + this.config = config; + if (as == null) { throw new ServiceLifecycleException("Alias service is not set"); } } @@ -188,11 +191,12 @@ public class DefaultCryptoService implements CryptoService { // The assumption here is that lock contention will be less of a performance issue than the cost of object creation. // We have seen via profiling that AESEncryptor instantiation is very expensive. - private final AESEncryptor getEncryptor( final String clusterName, final char[] password ) { + private final ConfigurableEncryptor getEncryptor( final String clusterName, final char[] password ) { synchronized( encryptorCache ) { - AESEncryptor encryptor = encryptorCache.get( clusterName ); + ConfigurableEncryptor encryptor = encryptorCache.get( clusterName ); if( encryptor == null ) { - encryptor = new AESEncryptor( String.valueOf( password ) ); + encryptor = new ConfigurableEncryptor( String.valueOf( password ) ); + encryptor.init(config); encryptorCache.put( clusterName, encryptor ); } return encryptor; http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-server/src/main/java/org/apache/hadoop/gateway/services/security/impl/DefaultMasterService.java ---------------------------------------------------------------------- diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/security/impl/DefaultMasterService.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/security/impl/DefaultMasterService.java index 47368d0..ece8445 100644 --- a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/security/impl/DefaultMasterService.java +++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/security/impl/DefaultMasterService.java @@ -43,7 +43,7 @@ public class DefaultMasterService extends CMFMasterService implements MasterServ boolean persisting = options.get( "persist-master").equals("true"); String securityDir = config.getGatewaySecurityDir(); String filename = "master"; - setupMasterSecret(securityDir, filename, persisting); + setupMasterSecret(securityDir, filename, persisting, config); } } http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-server/src/test/java/org/apache/hadoop/gateway/services/security/CryptoServiceTest.java ---------------------------------------------------------------------- diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/services/security/CryptoServiceTest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/services/security/CryptoServiceTest.java index bcd5ec2..57e2abc 100644 --- a/gateway-server/src/test/java/org/apache/hadoop/gateway/services/security/CryptoServiceTest.java +++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/services/security/CryptoServiceTest.java @@ -19,15 +19,17 @@ package org.apache.hadoop.gateway.services.security; import org.apache.hadoop.gateway.config.GatewayConfig; import org.apache.hadoop.gateway.services.ServiceLifecycleException; -import org.apache.hadoop.gateway.services.security.impl.AESEncryptor; +import org.apache.hadoop.gateway.services.security.impl.ConfigurableEncryptor; import org.apache.hadoop.gateway.services.security.impl.DefaultCryptoService; import org.apache.hadoop.test.category.ManualTests; import org.apache.hadoop.test.category.MediumTests; +import org.easymock.EasyMock; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; import java.security.cert.Certificate; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -113,42 +115,91 @@ public class CryptoServiceTest { cs = new DefaultCryptoService(); ((DefaultCryptoService)cs).setAliasService(as); } - + @Test - public void testAESEncryptor() throws Exception { + public void testCryptoServiceAES() throws Exception { + GatewayConfig config = EasyMock.createNiceMock( GatewayConfig.class ); + EasyMock.expect(config.getAlgorithm()).andReturn("AES"); + EasyMock.expect(config.getPBEAlgorithm()).andReturn("PBKDF2WithHmacSHA1"); + EasyMock.expect(config.getSaltSize()).andReturn("16"); + EasyMock.expect(config.getIterationCount()).andReturn("65536"); + EasyMock.expect(config.getKeyLength()).andReturn("128"); + EasyMock.expect(config.getTransformation()).andReturn("AES/CBC/PKCS5Padding"); + EasyMock.replay(config); + // password to create key - same Encryptor String queryString = "url=http://localhost:50070/api/v1/blahblah"; - AESEncryptor aes0 = new AESEncryptor("password"); + ConfigurableEncryptor aes0 = new ConfigurableEncryptor("password"); + aes0.init(config); + cs.init(config, new HashMap<String,String>()); EncryptionResult result0 = cs.encryptForCluster("Test", "encrypt_url", queryString.getBytes("UTF8")); - byte[] decrypted0 = aes0.decrypt(result0.salt, result0.iv, result0.cipher); + byte[] decrypted0 = cs.decryptForCluster("Test", "encrypt_url", result0.cipher, result0.iv, result0.salt); assertEquals(queryString, new String(decrypted0, "UTF8")); assertEquals(queryString.getBytes("UTF8").length, decrypted0.length); assertEquals(queryString.getBytes("UTF8").length, new String(decrypted0, "UTF8").toCharArray().length); - + } + + @Test + public void testCryptoServiceDES() throws Exception { + GatewayConfig config = EasyMock.createNiceMock( GatewayConfig.class ); + EasyMock.expect(config.getAlgorithm()).andReturn("DES"); + EasyMock.expect(config.getPBEAlgorithm()).andReturn("PBKDF2WithHmacSHA1"); + EasyMock.expect(config.getSaltSize()).andReturn("16"); + EasyMock.expect(config.getIterationCount()).andReturn("65536"); + EasyMock.expect(config.getKeyLength()).andReturn("128"); + EasyMock.expect(config.getTransformation()).andReturn("DES"); + EasyMock.replay(config); + // password to create key - same Encryptor - AESEncryptor aes = new AESEncryptor("Test"); + String queryString = "url=http://localhost:50070/api/v1/blahblah"; + ConfigurableEncryptor aes0 = new ConfigurableEncryptor("password"); + aes0.init(config); + cs.init(config, new HashMap<String,String>()); + EncryptionResult result0 = cs.encryptForCluster("Test", "encrypt_url", queryString.getBytes("UTF8")); + byte[] decrypted0 = cs.decryptForCluster("Test", "encrypt_url", result0.cipher, result0.iv, result0.salt); + assertEquals(queryString, new String(decrypted0, "UTF8")); + assertEquals(queryString.getBytes("UTF8").length, decrypted0.length); + assertEquals(queryString.getBytes("UTF8").length, new String(decrypted0, "UTF8").toCharArray().length); + } + + @Test + public void testConfigurableEncryptor() throws Exception { + GatewayConfig config = EasyMock.createNiceMock( GatewayConfig.class ); + EasyMock.expect(config.getAlgorithm()).andReturn("AES"); + EasyMock.expect(config.getPBEAlgorithm()).andReturn("PBKDF2WithHmacSHA1"); + EasyMock.expect(config.getSaltSize()).andReturn("16"); + EasyMock.expect(config.getIterationCount()).andReturn("65536"); + EasyMock.expect(config.getKeyLength()).andReturn("128"); + EasyMock.expect(config.getTransformation()).andReturn("AES/CBC/PKCS5Padding"); + EasyMock.replay(config); + + // password to create key - same Encryptor + ConfigurableEncryptor aes = new ConfigurableEncryptor("Test"); + aes.init(config); EncryptionResult result = aes.encrypt("larry".getBytes("UTF8")); byte[] decrypted = aes.decrypt(result.salt, result.iv, result.cipher); assertEquals(new String(decrypted, "UTF8"), "larry"); // password to create key - different Encryptor - AESEncryptor aes2 = new AESEncryptor("Test"); + ConfigurableEncryptor aes2 = new ConfigurableEncryptor("Test"); + aes2.init(config); decrypted = aes2.decrypt(result.salt, result.iv, result.cipher); assertEquals(new String(decrypted, "UTF8"), "larry"); - // password to create key resolved from alias - same Encryptor - AESEncryptor aes3 = new AESEncryptor(new String(as.getPasswordFromAliasForCluster("test", "encrypt_url"))); + ConfigurableEncryptor aes3 = new ConfigurableEncryptor(new String(as.getPasswordFromAliasForCluster("test", "encrypt_url"))); + aes3.init(config); result = aes3.encrypt("larry".getBytes("UTF8")); decrypted = aes3.decrypt(result.salt, result.iv, result.cipher); assertEquals(new String(decrypted, "UTF8"), "larry"); // password to create key resolved from alias - different Encryptor - AESEncryptor aes4 = new AESEncryptor(new String(as.getPasswordFromAliasForCluster("test", "encrypt_url"))); + ConfigurableEncryptor aes4 = new ConfigurableEncryptor(new String(as.getPasswordFromAliasForCluster("test", "encrypt_url"))); + aes4.init(config); decrypted = aes4.decrypt(result.salt, result.iv, result.cipher); assertEquals(new String(decrypted, "UTF8"), "larry"); } - + @Test //@Ignore public void testEncryptionOfQueryStrings() throws Exception { http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java index 9d8e044..af03bbd 100644 --- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java +++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java @@ -236,4 +236,46 @@ public interface GatewayConfig { */ String getHeaderNameForRemoteAddress(); + /** + * Configured Algorithm name to be used by the CryptoService + * and MasterService implementations + * @return + */ + String getAlgorithm(); + + /** + * Configured Algorithm name to be used by the CryptoService + * for password based encryption + * @return + */ + String getPBEAlgorithm(); + + /** + * Configured Transformation name to be used by the CryptoService + * and MasterService implementations + * @return + */ + String getTransformation(); + + /** + * Configured SaltSize to be used by the CryptoService + * and MasterService implementations + * @return + */ + String getSaltSize(); + + /** + * Configured IterationCount to be used by the CryptoService + * and MasterService implementations + * @return + */ + String getIterationCount(); + + /** + * Configured KeyLength to be used by the CryptoService + * and MasterService implementations + * @return + */ + String getKeyLength(); + } http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/MasterService.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/MasterService.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/MasterService.java index 6d42dfe..ff84565 100644 --- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/MasterService.java +++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/MasterService.java @@ -18,7 +18,5 @@ package org.apache.hadoop.gateway.services.security; public interface MasterService { - public abstract char[] getMasterSecret(); - } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/AESEncryptor.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/AESEncryptor.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/AESEncryptor.java deleted file mode 100644 index f6ff910..0000000 --- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/AESEncryptor.java +++ /dev/null @@ -1,152 +0,0 @@ -/** - * 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.hadoop.gateway.services.security.impl; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.InvalidParameterSpecException; -import java.security.spec.KeySpec; - -import javax.crypto.Cipher; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.hadoop.gateway.i18n.GatewaySpiMessages; -import org.apache.hadoop.gateway.i18n.messages.MessagesFactory; -import org.apache.hadoop.gateway.services.security.EncryptionResult; - -public class AESEncryptor { - private static final GatewaySpiMessages LOG = MessagesFactory.get( GatewaySpiMessages.class ); - - private static final int ITERATION_COUNT = 65536; - private static final int KEY_LENGTH = 128; - - private Cipher ecipher; - private Cipher dcipher; - private SecretKey secret; - private byte[] salt = null; - private char[] passPhrase = null; - - public AESEncryptor(String passPhrase) { - try { - this.passPhrase = passPhrase.toCharArray(); - salt = new byte[8]; - SecureRandom rnd = new SecureRandom(); - rnd.nextBytes(salt); - - SecretKey tmp = getKeyFromPassword(passPhrase); - secret = new SecretKeySpec (tmp.getEncoded(), "AES"); - - ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - ecipher.init(Cipher.ENCRYPT_MODE, secret); - - dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - byte[] iv = ecipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); - dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); - } catch (NoSuchAlgorithmException e) { - LOG.failedToEncryptPassphrase( e ); - } catch (NoSuchPaddingException e) { - LOG.failedToEncryptPassphrase( e ); - } catch (InvalidKeyException e) { - LOG.failedToEncryptPassphrase( e ); - } catch (InvalidParameterSpecException e) { - LOG.failedToEncryptPassphrase( e ); - } catch (InvalidAlgorithmParameterException e) { - LOG.failedToEncryptPassphrase( e ); - } - } - - AESEncryptor(SecretKey secret) { - try { - this.secret = new SecretKeySpec (secret.getEncoded(), "AES"); - - ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - ecipher.init(Cipher.ENCRYPT_MODE, secret); - - dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - byte[] iv = ecipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); - dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); - } catch (NoSuchAlgorithmException e) { - LOG.failedToEncryptPassphrase( e ); - } catch (NoSuchPaddingException e) { - LOG.failedToEncryptPassphrase( e ); - } catch (InvalidKeyException e) { - LOG.failedToEncryptPassphrase( e ); - } catch (InvalidParameterSpecException e) { - LOG.failedToEncryptPassphrase( e ); - } catch (InvalidAlgorithmParameterException e) { - LOG.failedToEncryptPassphrase( e ); - } - } - - public SecretKey getKeyFromPassword(String passPhrase) { - return getKeyFromPassword(passPhrase, salt); - } - - public SecretKey getKeyFromPassword(String passPhrase, byte[] salt) { - SecretKeyFactory factory; - SecretKey key = null; - try { - factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - KeySpec spec = new PBEKeySpec(passPhrase.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH); - key = factory.generateSecret(spec); - } catch (NoSuchAlgorithmException e) { - LOG.failedToGenerateKeyFromPassword( e ); - } catch (InvalidKeySpecException e) { - LOG.failedToGenerateKeyFromPassword( e ); - } - - return key; - } - - public EncryptionResult encrypt(String encrypt) throws Exception { - byte[] bytes = encrypt.getBytes("UTF8"); - EncryptionResult atom = encrypt(bytes); - return atom; - } - - public EncryptionResult encrypt(byte[] plain) throws Exception { - EncryptionResult atom = new EncryptionResult(salt, ecipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(), ecipher.doFinal(plain)); - return atom; - } - - public String decrypt(String salt, String iv, String cipher) throws Exception { - byte[] decrypted = decrypt(salt.getBytes("UTF8"), iv.getBytes("UTF8"), cipher.getBytes("UTF8")); - return new String(decrypted, "UTF8"); - } - - public byte[] decrypt(byte[] salt, byte[] iv, byte[] encrypt) throws Exception { - SecretKey tmp = getKeyFromPassword(new String(passPhrase), salt); - secret = new SecretKeySpec(tmp.getEncoded(), "AES"); - - dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); - return dcipher.doFinal(encrypt); - } - - public byte[] decrypt(byte[] encrypt) throws Exception { - dcipher.init(Cipher.DECRYPT_MODE, secret); - return dcipher.doFinal(encrypt); - } -} http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/CMFMasterService.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/CMFMasterService.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/CMFMasterService.java index 7960787..5a37f9b 100644 --- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/CMFMasterService.java +++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/CMFMasterService.java @@ -20,6 +20,7 @@ package org.apache.hadoop.gateway.services.security.impl; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.apache.commons.net.ntp.TimeStamp; +import org.apache.hadoop.gateway.config.GatewayConfig; import org.apache.hadoop.gateway.i18n.GatewaySpiMessages; import org.apache.hadoop.gateway.i18n.messages.MessagesFactory; import org.apache.hadoop.gateway.services.ServiceLifecycleException; @@ -39,7 +40,7 @@ public class CMFMasterService { private static final String MASTER_PERSISTENCE_TAG = "#1.0# " + TimeStamp.getCurrentTime().toDateString(); protected char[] master = null; protected String serviceName = null; - private AESEncryptor aes = new AESEncryptor(MASTER_PASSPHRASE); + private ConfigurableEncryptor encryptor = new ConfigurableEncryptor(MASTER_PASSPHRASE); public CMFMasterService(String serviceName) { super(); @@ -50,6 +51,13 @@ public class CMFMasterService { return this.master; } + public void setupMasterSecret(String securityDir, String filename, + boolean persisting, GatewayConfig config) + throws ServiceLifecycleException { + encryptor.init(config); + setupMasterSecret(securityDir, filename, persisting); + } + protected void setupMasterSecret(String securityDir, boolean persisting) throws ServiceLifecycleException { setupMasterSecret(securityDir, serviceName + "-master", persisting); } @@ -60,7 +68,6 @@ public class CMFMasterService { try { initializeFromMaster(masterFile); } catch (Exception e) { - // TODO Auto-generated catch block throw new ServiceLifecycleException("Unable to load the persisted master secret.", e); } } @@ -146,7 +153,7 @@ public class CMFMasterService { private EncryptionResult encryptMaster(char[] master) { // TODO Auto-generated method stub try { - return aes.encrypt(new String(master)); + return encryptor.encrypt(new String(master)); } catch (Exception e) { LOG.failedToEncryptMasterSecret(e); } @@ -160,7 +167,7 @@ public class CMFMasterService { LOG.loadingFromPersistentMaster( tag ); String line = new String(Base64.decodeBase64(lines.get(1))); String[] parts = line.split("::"); - this.master = new String(aes.decrypt(Base64.decodeBase64(parts[0]), Base64.decodeBase64(parts[1]), Base64.decodeBase64(parts[2])), "UTF8").toCharArray(); + this.master = new String(encryptor.decrypt(Base64.decodeBase64(parts[0]), Base64.decodeBase64(parts[1]), Base64.decodeBase64(parts[2])), "UTF8").toCharArray(); } catch (IOException e) { LOG.failedToInitializeFromPersistentMaster(masterFile.getName(), e); throw e; http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/ConfigurableEncryptor.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/ConfigurableEncryptor.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/ConfigurableEncryptor.java new file mode 100644 index 0000000..49be71d --- /dev/null +++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/impl/ConfigurableEncryptor.java @@ -0,0 +1,188 @@ +/** + * 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.hadoop.gateway.services.security.impl; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.InvalidParameterSpecException; +import java.security.spec.KeySpec; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.hadoop.gateway.config.GatewayConfig; +import org.apache.hadoop.gateway.i18n.GatewaySpiMessages; +import org.apache.hadoop.gateway.i18n.messages.MessagesFactory; +import org.apache.hadoop.gateway.services.security.EncryptionResult; + +public class ConfigurableEncryptor { + private static final GatewaySpiMessages LOG = MessagesFactory.get( GatewaySpiMessages.class ); + + private static final int ITERATION_COUNT = 65536; + private static final int KEY_LENGTH = 128; + + private Cipher ecipher; + private Cipher dcipher; + private SecretKey secret; + private byte[] salt = null; + private char[] passPhrase = null; + private String alg = "AES"; + private String pbeAlg = "PBKDF2WithHmacSHA1"; + private String transformation = "AES/CBC/PKCS5Padding"; + private int saltSize = 8; + private int iterationCount = ITERATION_COUNT; + private int keyLength = KEY_LENGTH; + + public ConfigurableEncryptor(String passPhrase) { + try { + this.passPhrase = passPhrase.toCharArray(); + salt = new byte[saltSize]; + SecureRandom rnd = new SecureRandom(); + rnd.nextBytes(salt); + + SecretKey tmp = getKeyFromPassword(passPhrase); + secret = new SecretKeySpec (tmp.getEncoded(), alg); + + ecipher = Cipher.getInstance(transformation); + ecipher.init(Cipher.ENCRYPT_MODE, secret); + + dcipher = Cipher.getInstance(transformation); + byte[] iv = ecipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); + dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); + } catch (NoSuchAlgorithmException e) { + LOG.failedToEncryptPassphrase( e ); + } catch (NoSuchPaddingException e) { + LOG.failedToEncryptPassphrase( e ); + } catch (InvalidKeyException e) { + LOG.failedToEncryptPassphrase( e ); + } catch (InvalidParameterSpecException e) { + LOG.failedToEncryptPassphrase( e ); + } catch (InvalidAlgorithmParameterException e) { + LOG.failedToEncryptPassphrase( e ); + } + } + + ConfigurableEncryptor(SecretKey secret) { + try { + this.secret = new SecretKeySpec (secret.getEncoded(), alg); + + ecipher = Cipher.getInstance(transformation); + ecipher.init(Cipher.ENCRYPT_MODE, secret); + + dcipher = Cipher.getInstance(transformation); + byte[] iv = ecipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); + dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); + } catch (NoSuchAlgorithmException e) { + LOG.failedToEncryptPassphrase( e ); + } catch (NoSuchPaddingException e) { + LOG.failedToEncryptPassphrase( e ); + } catch (InvalidKeyException e) { + LOG.failedToEncryptPassphrase( e ); + } catch (InvalidParameterSpecException e) { + LOG.failedToEncryptPassphrase( e ); + } catch (InvalidAlgorithmParameterException e) { + LOG.failedToEncryptPassphrase( e ); + } + } + + public void init(GatewayConfig config) { + if (config != null) { + String alg = config.getAlgorithm(); + if (alg != null) { + this.alg = alg; + } + String pbeAlg = config.getPBEAlgorithm(); + if (pbeAlg != null) { + this.pbeAlg = pbeAlg; + } + String transformation = config.getTransformation(); + if (transformation != null) { + this.transformation = transformation; + } + String saltSize = config.getSaltSize(); + if (saltSize != null) { + this.saltSize = Integer.parseInt(saltSize); + } + String iterationCount = config.getIterationCount(); + if (iterationCount != null) { + this.iterationCount = Integer.parseInt(iterationCount); + } + String keyLength = config.getKeyLength(); + if (keyLength != null) { + this.keyLength = Integer.parseInt(keyLength); + } + } + } + + public SecretKey getKeyFromPassword(String passPhrase) { + return getKeyFromPassword(passPhrase, salt); + } + + public SecretKey getKeyFromPassword(String passPhrase, byte[] salt) { + SecretKeyFactory factory; + SecretKey key = null; + try { + factory = SecretKeyFactory.getInstance(pbeAlg); + KeySpec spec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount, keyLength); + key = factory.generateSecret(spec); + } catch (NoSuchAlgorithmException e) { + LOG.failedToGenerateKeyFromPassword( e ); + } catch (InvalidKeySpecException e) { + LOG.failedToGenerateKeyFromPassword( e ); + } + + return key; + } + + public EncryptionResult encrypt(String encrypt) throws Exception { + byte[] bytes = encrypt.getBytes("UTF8"); + EncryptionResult atom = encrypt(bytes); + return atom; + } + + public EncryptionResult encrypt(byte[] plain) throws Exception { + EncryptionResult atom = new EncryptionResult(salt, ecipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(), ecipher.doFinal(plain)); + return atom; + } + + public String decrypt(String salt, String iv, String cipher) throws Exception { + byte[] decrypted = decrypt(salt.getBytes("UTF8"), iv.getBytes("UTF8"), cipher.getBytes("UTF8")); + return new String(decrypted, "UTF8"); + } + + public byte[] decrypt(byte[] salt, byte[] iv, byte[] encrypt) throws Exception { + SecretKey tmp = getKeyFromPassword(new String(passPhrase), salt); + secret = new SecretKeySpec(tmp.getEncoded(), alg); + + dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); + return dcipher.doFinal(encrypt); + } + + public byte[] decrypt(byte[] encrypt) throws Exception { + dcipher.init(Cipher.DECRYPT_MODE, secret); + return dcipher.doFinal(encrypt); + } +} http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-test-release-utils/src/main/java/org/apache/hadoop/gateway/GatewayTestConfig.java ---------------------------------------------------------------------- diff --git a/gateway-test-release-utils/src/main/java/org/apache/hadoop/gateway/GatewayTestConfig.java b/gateway-test-release-utils/src/main/java/org/apache/hadoop/gateway/GatewayTestConfig.java index c079531..f41046b 100644 --- a/gateway-test-release-utils/src/main/java/org/apache/hadoop/gateway/GatewayTestConfig.java +++ b/gateway-test-release-utils/src/main/java/org/apache/hadoop/gateway/GatewayTestConfig.java @@ -478,4 +478,33 @@ public class GatewayTestConfig extends Configuration implements GatewayConfig { return "X-Forwarded-For"; } + @Override + public String getAlgorithm() { + return null; + } + + @Override + public String getPBEAlgorithm() { + return null; + } + + @Override + public String getTransformation() { + return null; + } + + @Override + public String getSaltSize() { + return null; + } + + @Override + public String getIterationCount() { + return null; + } + + @Override + public String getKeyLength() { + return null; + } } http://git-wip-us.apache.org/repos/asf/knox/blob/d0726a22/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayTestConfig.java ---------------------------------------------------------------------- diff --git a/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayTestConfig.java b/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayTestConfig.java index e96dde2..580d875 100644 --- a/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayTestConfig.java +++ b/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayTestConfig.java @@ -535,4 +535,34 @@ public class GatewayTestConfig extends Configuration implements GatewayConfig { public String getHeaderNameForRemoteAddress() { return "X-Forwarded-For"; } + + @Override + public String getAlgorithm() { + return null; + } + + @Override + public String getPBEAlgorithm() { + return null; + } + + @Override + public String getTransformation() { + return null; + } + + @Override + public String getSaltSize() { + return null; + } + + @Override + public String getIterationCount() { + return null; + } + + @Override + public String getKeyLength() { + return null; + } }
