This is an automated email from the ASF dual-hosted git repository.

smolnar pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new cf74bb1  AMBARI-24742. Implementing a new service to be used in case 
we want to encrypt/decrypt sensitive information using custom/environment 
master key (also injected into Guice) (#2430)
cf74bb1 is described below

commit cf74bb1b22677bd4886748a75d003033b2cbcf74
Author: Sandor Molnar <smol...@apache.org>
AuthorDate: Mon Oct 8 22:20:40 2018 +0200

    AMBARI-24742. Implementing a new service to be used in case we want to 
encrypt/decrypt sensitive information using custom/environment master key (also 
injected into Guice) (#2430)
---
 .../ambari/server/controller/ControllerModule.java |   3 +
 .../security/encryption/AESEncryptionService.java  | 112 +++++++++++++++
 .../security/encryption/EncryptionService.java     | 153 +++++++++++++++++++++
 .../security/encryption/MasterKeyServiceImpl.java  |  53 ++-----
 .../apache/ambari/server/utils/TextEncoding.java   |  24 ++++
 .../internal/CredentialResourceProviderTest.java   |   2 +-
 .../encryption/CredentialProviderTest.java         |   2 +-
 .../encryption/CredentialStoreServiceImplTest.java |   2 +-
 .../security/encryption/CredentialStoreTest.java   |   2 +-
 .../security/encryption/EncryptionServiceTest.java | 100 ++++++++++++++
 .../security/encryption/MasterKeyServiceTest.java  |  10 +-
 11 files changed, 413 insertions(+), 50 deletions(-)

diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
index 6df8951..972dbce 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
@@ -113,8 +113,10 @@ import 
org.apache.ambari.server.security.SecurityHelperImpl;
 import org.apache.ambari.server.security.authorization.AuthorizationHelper;
 import 
org.apache.ambari.server.security.authorization.internal.InternalAuthenticationInterceptor;
 import 
org.apache.ambari.server.security.authorization.internal.RunWithInternalSecurityContext;
+import org.apache.ambari.server.security.encryption.AESEncryptionService;
 import org.apache.ambari.server.security.encryption.CredentialStoreService;
 import org.apache.ambari.server.security.encryption.CredentialStoreServiceImpl;
+import org.apache.ambari.server.security.encryption.EncryptionService;
 import 
org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandlerFactory;
 import org.apache.ambari.server.serveraction.users.CollectionPersisterService;
 import 
org.apache.ambari.server.serveraction.users.CollectionPersisterServiceFactory;
@@ -328,6 +330,7 @@ public class ControllerModule extends AbstractModule {
     bind(KerberosHelper.class).to(KerberosHelperImpl.class);
 
     bind(CredentialStoreService.class).to(CredentialStoreServiceImpl.class);
+    bind(EncryptionService.class).to(AESEncryptionService.class);
 
     bind(Configuration.class).toInstance(configuration);
     bind(OsFamily.class).toInstance(os_family);
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/AESEncryptionService.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/AESEncryptionService.java
new file mode 100644
index 0000000..ffb92ba
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/AESEncryptionService.java
@@ -0,0 +1,112 @@
+/*
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.ambari.server.security.encryption;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.ambari.server.utils.TextEncoding;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+
+import com.google.inject.Singleton;
+
+@Singleton
+public class AESEncryptionService implements EncryptionService {
+
+  private static final String ENCODED_TEXT_FIELD_DELIMITER = "::";
+  private static final String UTF_8_CHARSET = StandardCharsets.UTF_8.name();
+
+  private MasterKeyService environmentMasterKeyService;
+
+  @Override
+  public String encrypt(String toBeEncrypted) throws Exception {
+    return encrypt(toBeEncrypted, TextEncoding.BASE_64);
+  }
+
+  @Override
+  public String encrypt(String toBeEncrypted, TextEncoding textEncoding) 
throws Exception {
+    return encrypt(toBeEncrypted, getAmbariMasterKey(), textEncoding);
+  }
+
+  @Override
+  public String encrypt(String toBeEncrypted, String key) throws Exception {
+    return encrypt(toBeEncrypted, key, TextEncoding.BASE_64);
+  }
+
+  @Override
+  public String encrypt(String toBeEncrypted, String key, TextEncoding 
textEncoding) throws Exception {
+    final AESEncryptor aes = new AESEncryptor(key);
+    final EncryptionResult encryptionResult = aes.encrypt(toBeEncrypted);
+    return TextEncoding.BASE_64 == textEncoding ? 
encodeEncryptionResultBase64(encryptionResult) : 
encodeEncryptionResultBinHex(encryptionResult);
+  }
+
+  private final String getAmbariMasterKey() {
+    initEnvironmentMasterKeyService();
+    return String.valueOf(environmentMasterKeyService.getMasterSecret());
+  }
+
+  private void initEnvironmentMasterKeyService() {
+    if (environmentMasterKeyService == null) {
+      environmentMasterKeyService = new MasterKeyServiceImpl();
+      if (!environmentMasterKeyService.isMasterKeyInitialized()) {
+        throw new SecurityException("You are trying to use a persisted master 
key but its initialization has been failed!");
+      }
+    }
+  }
+
+  private String encodeEncryptionResultBase64(EncryptionResult 
encryptionResult) throws UnsupportedEncodingException {
+    return 
Base64.encodeBase64String((Base64.encodeBase64String(encryptionResult.salt) + 
ENCODED_TEXT_FIELD_DELIMITER + Base64.encodeBase64String(encryptionResult.iv)
+        + ENCODED_TEXT_FIELD_DELIMITER + 
Base64.encodeBase64String(encryptionResult.cipher)).getBytes(UTF_8_CHARSET));
+  }
+
+  private String encodeEncryptionResultBinHex(EncryptionResult 
encryptionResult) throws UnsupportedEncodingException {
+    return Hex.encodeHexString((Hex.encodeHexString(encryptionResult.salt) + 
ENCODED_TEXT_FIELD_DELIMITER + Hex.encodeHexString(encryptionResult.iv)
+        + ENCODED_TEXT_FIELD_DELIMITER + 
Hex.encodeHexString(encryptionResult.cipher)).getBytes(UTF_8_CHARSET));
+  }
+
+  @Override
+  public String decrypt(String toBeDecrypted) throws Exception {
+    return decrypt(toBeDecrypted, TextEncoding.BASE_64);
+  }
+
+  @Override
+  public String decrypt(String toBeDecrypted, TextEncoding textEncoding) 
throws Exception {
+    return decrypt(toBeDecrypted, getAmbariMasterKey(), textEncoding);
+  }
+
+  @Override
+  public String decrypt(String toBeDecrypted, String key) throws Exception {
+    return decrypt(toBeDecrypted, key, TextEncoding.BASE_64);
+  }
+
+  @Override
+  public String decrypt(String toBeDecrypted, String key, TextEncoding 
textEncoding) throws Exception {
+    final byte[] decodedValue = TextEncoding.BASE_64 == textEncoding ? 
Base64.decodeBase64(toBeDecrypted) : Hex.decodeHex(toBeDecrypted.toCharArray());
+    final String decodedText = new String(decodedValue, UTF_8_CHARSET);
+    final String[] decodedParts = 
decodedText.split(ENCODED_TEXT_FIELD_DELIMITER);
+    final AESEncryptor aes = new AESEncryptor(key);
+    if (TextEncoding.BASE_64 == textEncoding) {
+      return new String(aes.decrypt(Base64.decodeBase64(decodedParts[0]), 
Base64.decodeBase64(decodedParts[1]), Base64.decodeBase64(decodedParts[2])), 
UTF_8_CHARSET);
+    } else {
+      return new String(
+          aes.decrypt(Hex.decodeHex(decodedParts[0].toCharArray()), 
Hex.decodeHex(decodedParts[1].toCharArray()), 
Hex.decodeHex(decodedParts[2].toCharArray())),
+          UTF_8_CHARSET);
+    }
+  }
+}
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/EncryptionService.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/EncryptionService.java
new file mode 100644
index 0000000..2f4c2f9
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/EncryptionService.java
@@ -0,0 +1,153 @@
+/*
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.ambari.server.security.encryption;
+
+import org.apache.ambari.server.utils.TextEncoding;
+
+/**
+ * This interface provides useful methods to encrypt/decrypt sensitive
+ * information. As of now the underlying implementation uses AES encryption
+ * which may be changed in the future (or you will have the option to plugin
+ * your own security algorithm).
+ * 
+ * Text encoding takes places during encryption thus decryption starts with 
text
+ * decoding of the same encoding type. Currently the following text encoding
+ * types are supported:
+ * <ul>
+ * <li>BASE_64 (default)
+ * <li>BinHex
+ * </ul>
+ *
+ * @see TextEncoding
+ *
+ */
+public interface EncryptionService {
+
+  /**
+   * Encrypts the given text using Ambari's master key found in the 
environment.
+   * The returned value will be encoded with BASE_64 encoding.
+   * 
+   * @param toBeEncrypted
+   *          the text to be encrypted
+   * @return the String representation of the encrypted text
+   * @throws Exception
+   *           in case any error happened during the encryption process
+   */
+  String encrypt(String toBeEncrypted) throws Exception;
+
+  /**
+   * Encrypts the given text using Ambari's master key found in the 
environment.
+   * The returned value will be encoded with the given text encoding.
+   * 
+   * @param toBeEncrypted
+   *          the text to be encrypted
+   * @param textEncoding
+   *          the text encoding which the encrypted text is encoded with
+   * @return the String representation of the encrypted text
+   * @throws Exception
+   *           in case any error happened during the encryption process
+   */
+  String encrypt(String toBeEncrypted, TextEncoding textEncoding) throws 
Exception;
+
+  /**
+   * Encrypts the given text using the given key. The returned value will be
+   * encoded with BASE_64 encoding.
+   * 
+   * @param toBeEncrypted
+   *          the text to be encrypted
+   * @param key
+   *          the key to be used for encryption
+   * @return the String representation of the encrypted text
+   * @throws Exception
+   *           in case any error happened during the encryption process
+   */
+  String encrypt(String toBeEncrypted, String key) throws Exception;
+
+  /**
+   * Encrypts the given text using the given key.The returned value will be
+   * encoded with the given text encoding.
+   * 
+   * @param toBeEncrypted
+   *          the text to be encrypted
+   * @param key
+   *          the key to be used for encryption
+   * @param textEncoding
+   *          the text encoding which the encrypted text is encoded with
+   * @return the String representation of the encrypted text
+   * @throws Exception
+   *           in case any error happened during the encryption process
+   */
+  String encrypt(String toBeEncrypted, String key, TextEncoding textEncoding) 
throws Exception;
+
+  /**
+   * Decrypts the given text (must be encoded with BASE_64 encoding) using
+   * Ambari's master key found in the environment.
+   * 
+   * @param toBeDecrypted
+   *          the text to be decrypted
+   * @return the String representation of the decrypted text
+   * @throws Exception
+   *           in case any error happened during the decryption process
+   */
+  public String decrypt(String toBeDecrypted) throws Exception;
+
+  /**
+   * Decrypts the given text (must be encoded with the given text encoding) 
using
+   * Ambari's master key found in the environment.
+   * 
+   * @param toBeDecrypted
+   *          the text to be decrypted
+   * @param textEncoding
+   *          the text encoding which <code>toBeDecrypted</code> is encoded 
with
+   * @return the String representation of the decrypted text
+   * @throws Exception
+   *           in case any error happened during the decryption process
+   */
+  public String decrypt(String toBeDecrypted, TextEncoding textEncoding) 
throws Exception;
+
+  /**
+   * Decrypts the given text (must be encoded with BASE_64 encoding) using the
+   * given key.
+   * 
+   * @param toBeDecrypted
+   *          the text to be decrypted
+   * @param key
+   *          the key to be used for decryption
+   * @return the String representation of the decrypted text
+   * @throws Exception
+   *           in case any error happened during the decryption process
+   */
+  public String decrypt(String toBeDecrypted, String key) throws Exception;
+
+  /**
+   * Decrypts the given text (must be encoded with the given text encoding) 
using
+   * the given key.
+   * 
+   * @param toBeDecrypted
+   *          the text to be decrypted
+   * @param key
+   *          the key to be used for decryption
+   * @param textEncoding
+   *          the text encoding which <code>toBeDecrypted</code> is encoded 
with
+   * @return the String representation of the decrypted text
+   * @throws Exception
+   *           in case any error happened during the decryption process
+   */
+  public String decrypt(String toBeDecrypted, String key, TextEncoding 
textEncoding) throws Exception;
+
+}
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
index 3b38856..8a8dd58 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
@@ -32,7 +32,6 @@ import java.util.Map;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.utils.AmbariPath;
-import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.net.ntp.TimeStamp;
 import org.slf4j.Logger;
@@ -42,7 +41,7 @@ public class MasterKeyServiceImpl implements MasterKeyService 
{
   private static final Logger LOG = 
LoggerFactory.getLogger(MasterKeyServiceImpl.class);
   private static final String MASTER_PASSPHRASE = "masterpassphrase";
   private static final String MASTER_PERSISTENCE_TAG_PREFIX = "#1.0# ";
-  private static final AESEncryptor aes = new AESEncryptor(MASTER_PASSPHRASE);
+  private final EncryptionService encryptionService = new 
AESEncryptionService();
 
   private char[] master = null;
 
@@ -118,7 +117,8 @@ public class MasterKeyServiceImpl implements 
MasterKeyService {
       }
     }
 
-    if (persistMasterKey && !MasterKeyServiceImpl.initializeMasterKeyFile(new 
File(masterKeyLocation), masterKey)) {
+    final MasterKeyServiceImpl masterKeyService = new 
MasterKeyServiceImpl(masterKey);
+    if (persistMasterKey && !masterKeyService.initializeMasterKeyFile(new 
File(masterKeyLocation), masterKey)) {
       System.exit(1);
     } else {
       System.exit(0);
@@ -137,14 +137,13 @@ public class MasterKeyServiceImpl implements 
MasterKeyService {
    * @param masterKey     the master key
    * @return true if the master key was written to the specified file; 
otherwise false
    */
-  public static boolean initializeMasterKeyFile(File masterKeyFile, String 
masterKey) {
+   public boolean initializeMasterKeyFile(File masterKeyFile, String 
masterKey) {
     LOG.debug("Persisting master key into {}", 
masterKeyFile.getAbsolutePath());
 
-    EncryptionResult atom = null;
-
+    String encryptedMasterKey = null;
     if (masterKey != null) {
       try {
-        atom = aes.encrypt(masterKey);
+        encryptedMasterKey = encryptionService.encrypt(masterKey, 
MASTER_PASSPHRASE);
       } catch (Exception e) {
         LOG.error(String.format("Failed to encrypt master key, no changes have 
been made: %s", e.getLocalizedMessage()), e);
         return false;
@@ -154,22 +153,12 @@ public class MasterKeyServiceImpl implements 
MasterKeyService {
     if (masterKeyFile.exists()) {
       if ((masterKeyFile.length() == 0) || isMasterKeyFile(masterKeyFile)) {
         LOG.info(String.format("Master key file exists at %s, resetting.", 
masterKeyFile.getAbsolutePath()));
-        FileChannel fileChannel = null;
-        try {
-          fileChannel = new FileOutputStream(masterKeyFile).getChannel();
+        try (FileOutputStream fos = new FileOutputStream(masterKeyFile); 
FileChannel fileChannel = fos.getChannel();) {
           fileChannel.truncate(0);
         } catch (FileNotFoundException e) {
           LOG.error(String.format("Failed to open key file at %s: %s", 
masterKeyFile.getAbsolutePath(), e.getLocalizedMessage()), e);
         } catch (IOException e) {
           LOG.error(String.format("Failed to reset key file at %s: %s", 
masterKeyFile.getAbsolutePath(), e.getLocalizedMessage()), e);
-        } finally {
-          if (fileChannel != null) {
-            try {
-              fileChannel.close();
-            } catch (IOException e) {
-              // Ignore...
-            }
-          }
         }
       } else {
         LOG.info(String.format("File exists at %s, but may not be a master key 
file. " +
@@ -178,16 +167,11 @@ public class MasterKeyServiceImpl implements 
MasterKeyService {
       }
     }
 
-    if (atom != null) {
+    if (encryptedMasterKey != null) {
       try {
         ArrayList<String> lines = new ArrayList<>();
         lines.add(MASTER_PERSISTENCE_TAG_PREFIX + 
TimeStamp.getCurrentTime().toDateString());
-
-        String line = Base64.encodeBase64String((
-            Base64.encodeBase64String(atom.salt) + "::" +
-                Base64.encodeBase64String(atom.iv) + "::" +
-                Base64.encodeBase64String(atom.cipher)).getBytes("UTF8"));
-        lines.add(line);
+        lines.add(encryptedMasterKey);
         FileUtils.writeLines(masterKeyFile, "UTF8", lines);
 
         // restrict os permissions to only the user running this process
@@ -212,22 +196,11 @@ public class MasterKeyServiceImpl implements 
MasterKeyService {
    * @return true if the file is identitified as "master key" file; otherwise 
false
    */
   private static boolean isMasterKeyFile(File file) {
-    FileReader reader = null;
-
-    try {
-      reader = new FileReader(file);
+    try (FileReader reader = new FileReader(file);) {
       char[] buffer = new char[MASTER_PERSISTENCE_TAG_PREFIX.length()];
       return (reader.read(buffer) == buffer.length) && Arrays.equals(buffer, 
MASTER_PERSISTENCE_TAG_PREFIX.toCharArray());
     } catch (Exception e) {
       // Ignore, assume the file is not a master key file...
-    } finally {
-      if (reader != null) {
-        try {
-          reader.close();
-        } catch (IOException e) {
-          // Ignore...
-        }
-      }
     }
 
     return false;
@@ -302,11 +275,7 @@ public class MasterKeyServiceImpl implements 
MasterKeyService {
       List<String> lines = FileUtils.readLines(masterFile, "UTF8");
       String tag = lines.get(0);
       LOG.info("Loading from persistent master: " + tag);
-      String line = new String(Base64.decodeBase64(lines.get(1)));
-      String[] parts = line.split("::");
-      master = new String(aes.decrypt(Base64.decodeBase64(parts[0]),
-          Base64.decodeBase64(parts[1]), Base64.decodeBase64(parts[2])),
-          "UTF8").toCharArray();
+      master = encryptionService.decrypt(lines.get(1), 
MASTER_PASSPHRASE).toCharArray();
     } catch (Exception e) {
       e.printStackTrace();
       throw e;
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/utils/TextEncoding.java 
b/ambari-server/src/main/java/org/apache/ambari/server/utils/TextEncoding.java
new file mode 100644
index 0000000..92a7340
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/utils/TextEncoding.java
@@ -0,0 +1,24 @@
+/*
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.ambari.server.utils;
+
+public enum TextEncoding {
+
+  BASE_64, BIN_HEX;
+
+}
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CredentialResourceProviderTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CredentialResourceProviderTest.java
index 08c9839..14a9ea9 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CredentialResourceProviderTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CredentialResourceProviderTest.java
@@ -81,7 +81,7 @@ public class CredentialResourceProviderTest {
   public void setUp() throws Exception {
     tmpFolder.create();
     final File masterKeyFile = 
tmpFolder.newFile(Configuration.MASTER_KEY_FILENAME_DEFAULT);
-    
Assert.assertTrue(MasterKeyServiceImpl.initializeMasterKeyFile(masterKeyFile, 
"secret"));
+    Assert.assertTrue(new 
MasterKeyServiceImpl("dummyKey").initializeMasterKeyFile(masterKeyFile, 
"secret"));
 
     injector = Guice.createInjector(new AbstractModule() {
       @Override
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialProviderTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialProviderTest.java
index 7abbcbb..cded559 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialProviderTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialProviderTest.java
@@ -40,7 +40,7 @@ public class CredentialProviderTest {
 
   private void createMasterKey() throws IOException {
     File f = tmpFolder.newFile(Configuration.MASTER_KEY_FILENAME_DEFAULT);
-    Assert.assertTrue(MasterKeyServiceImpl.initializeMasterKeyFile(f, 
"blahblah!"));
+    Assert.assertTrue(new 
MasterKeyServiceImpl("dummyKey").initializeMasterKeyFile(f, "blahblah!"));
     MasterKeyService ms = new MasterKeyServiceImpl(f);
     if (!ms.isMasterKeyInitialized()) {
       throw new ExceptionInInitializerError("Cannot create master key.");
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImplTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImplTest.java
index 7f9f3fd..3a35710 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImplTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImplTest.java
@@ -55,7 +55,7 @@ public class CredentialStoreServiceImplTest {
   public void setUp() throws Exception {
     tmpFolder.create();
     final File masterKeyFile = 
tmpFolder.newFile(Configuration.MASTER_KEY_FILENAME_DEFAULT);
-    
Assert.assertTrue(MasterKeyServiceImpl.initializeMasterKeyFile(masterKeyFile, 
"secret"));
+    Assert.assertTrue(new 
MasterKeyServiceImpl("dummyKey").initializeMasterKeyFile(masterKeyFile, 
"secret"));
 
     Injector injector = Guice.createInjector(new AbstractModule() {
       @Override
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreTest.java
index f83c46a..c9487ed 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreTest.java
@@ -241,7 +241,7 @@ public class CredentialStoreTest {
 
     @Override
     public MasterKeyService createPersisted(File masterKeyFile, String 
masterKey) {
-      MasterKeyServiceImpl.initializeMasterKeyFile(masterKeyFile, masterKey);
+      new 
MasterKeyServiceImpl("dummyKey").initializeMasterKeyFile(masterKeyFile, 
masterKey);
       return new MasterKeyServiceImpl(masterKeyFile);
     }
   }
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/EncryptionServiceTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/EncryptionServiceTest.java
new file mode 100644
index 0000000..49bd871
--- /dev/null
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/EncryptionServiceTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.ambari.server.security.encryption;
+
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+import static org.powermock.api.easymock.PowerMock.mockStatic;
+import static org.powermock.api.easymock.PowerMock.replayAll;
+import static org.powermock.api.easymock.PowerMock.verifyAll;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.utils.TextEncoding;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import junit.framework.Assert;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ MasterKeyServiceImpl.class })
+@PowerMockIgnore({ "javax.crypto.*" })
+public class EncryptionServiceTest {
+
+  @Rule
+  private final TemporaryFolder tmpFolder = new TemporaryFolder();
+
+  private final EncryptionService encryptionService = new 
AESEncryptionService();
+
+  @Test
+  public void testEncryptAndDecryptUsingCustomKeyWithBase64Encoding() throws 
Exception {
+    testEncryptAndDecryptUsingCustomKey(TextEncoding.BASE_64);
+  }
+
+  @Test
+  public void testEncryptAndDecryptUsingCustomKeyWithBinHex64Encoding() throws 
Exception {
+    testEncryptAndDecryptUsingCustomKey(TextEncoding.BIN_HEX);
+  }
+
+  public void testEncryptAndDecryptUsingCustomKey(TextEncoding textEncoding) 
throws Exception {
+    final String key = "mySuperS3cr3tMast3rKey!";
+    final String toBeEncrypted = "mySuperS3cr3tP4ssW0rD!";
+    final String encrypted = encryptionService.encrypt(toBeEncrypted, key, 
textEncoding);
+    final String decrypted = encryptionService.decrypt(encrypted, key, 
textEncoding);
+    assertEquals(toBeEncrypted, decrypted);
+  }
+
+  @Test
+  public void testEncryptAndDecryptUsingPersistedMasterKey() throws Exception {
+    final String fileDir = tmpFolder.newFolder("keys").getAbsolutePath();
+    final File masterKeyFile = new File(fileDir, "master");
+    final String masterKey = "mySuperS3cr3tMast3rKey!";
+    final MasterKeyServiceImpl ms = new MasterKeyServiceImpl("dummyKey");
+    Assert.assertTrue(ms.initializeMasterKeyFile(masterKeyFile, masterKey));
+
+    final String toBeEncrypted = "mySuperS3cr3tP4ssW0rD!";
+
+    setupEnvironmentVariableExpectations(masterKeyFile);
+    final String encrypted = encryptionService.encrypt(toBeEncrypted);
+    final String decrypted = encryptionService.decrypt(encrypted);
+    verifyAll();
+    assertEquals(toBeEncrypted, decrypted);
+  }
+
+  @Test(expected = SecurityException.class)
+  public void 
shouldThrowSecurityExceptionInCaseOfEncryptingWithNonExistingPersistedMasterKey()
 throws Exception {
+    final String toBeEncrypted = "mySuperS3cr3tP4ssW0rD!";
+    encryptionService.encrypt(toBeEncrypted);
+  }
+
+  private void setupEnvironmentVariableExpectations(final File masterKeyFile) {
+    final Map<String, String> sysEnvironment = new HashMap<>();
+    sysEnvironment.put(Configuration.MASTER_KEY_LOCATION.getKey(), 
masterKeyFile.getAbsolutePath());
+    mockStatic(System.class);
+    expect(System.getenv()).andReturn(sysEnvironment);
+    replayAll();
+  }
+}
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/MasterKeyServiceTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/MasterKeyServiceTest.java
index 5f76072..032bb23 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/MasterKeyServiceTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/MasterKeyServiceTest.java
@@ -64,9 +64,10 @@ public class MasterKeyServiceTest extends TestCase {
   @Test
   public void testInitializeMasterKey() throws Exception {
     File masterKeyFile = new File(fileDir, "master");
-    
Assert.assertTrue(MasterKeyServiceImpl.initializeMasterKeyFile(masterKeyFile, 
"ThisisSomePassPhrase"));
+    MasterKeyServiceImpl ms = new MasterKeyServiceImpl("dummyKey");
+    Assert.assertTrue(ms.initializeMasterKeyFile(masterKeyFile, 
"ThisisSomePassPhrase"));
 
-    MasterKeyService ms = new MasterKeyServiceImpl(masterKeyFile);
+    ms = new MasterKeyServiceImpl(masterKeyFile);
     Assert.assertTrue(ms.isMasterKeyInitialized());
 
     Assert.assertTrue(masterKeyFile.exists());
@@ -112,9 +113,10 @@ public class MasterKeyServiceTest extends TestCase {
   public void testReadFromEnvAsPath() throws Exception {
     // Create a master key
     File masterKeyFile = new File(fileDir, "master");
-    
Assert.assertTrue(MasterKeyServiceImpl.initializeMasterKeyFile(masterKeyFile, 
"ThisisSomePassPhrase"));
+    MasterKeyServiceImpl ms = new MasterKeyServiceImpl("dummyKey");
+    Assert.assertTrue(ms.initializeMasterKeyFile(masterKeyFile, 
"ThisisSomePassPhrase"));
 
-    MasterKeyService ms = new MasterKeyServiceImpl(masterKeyFile);
+    ms = new MasterKeyServiceImpl(masterKeyFile);
     Assert.assertTrue(ms.isMasterKeyInitialized());
     Assert.assertTrue(masterKeyFile.exists());
 

Reply via email to