/*
* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.amazonaws.cloudhsm.examples;
import com.amazonaws.cloudhsm.jce.provider.CloudHsmCluster;
import com.amazonaws.cloudhsm.jce.provider.CloudHsmLoggingConfig;
import com.amazonaws.cloudhsm.jce.provider.CloudHsmProvider;
import com.amazonaws.cloudhsm.jce.provider.CloudHsmProviderConfig;
import com.amazonaws.cloudhsm.jce.provider.CloudHsmServer;
import com.amazonaws.cloudhsm.jce.provider.OptionalParameters;
import javax.crypto.Cipher;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyPair;
import java.security.Security;
import java.security.Signature;
import java.text.MessageFormat;
import java.util.Base64;
import java.util.Random;
/**
* Demonstrate basic Multi User Multi Cluster operation while connecting to 2 different clusters.
*
* Usage: The info of the 2 clusters for multi user multi cluster use cases should be provided as command
* line arguments as below.
*
*
* > hsm ca cert file path is the absolute path. e.g. /opt/cloudhsm/etc/customerCA.crt, /opt/cloudhsm/etc/customerCA2.crt
* > hsm ip is the ip address of the HSM in a cluster. e.g. 1.2.3.4
* > user pin will be of the format as :. e.g. cu:password.
*/
public class MultiUserMultiClusterRunner {
private static final String CLUSTER_DESEDE_PROVIDER_NAME = "cluster_desede_provider";
private static final String CLUSTER_RSA_PROVIDER_NAME = "cluster_rsa_provider";
private static String helpString = "MultiUserMultiClusterRunner\n" +
"This sample demonstrates how to connect to 2 clusters and perform operation.\n\n" +
"Options\n" +
"\t--help Display this message.\n" +
"\t--cluster-1-hsm-ca-cert Absolute path of hsm ca cert file for cluster 1. e.g. /opt/cloudhsm/etc/customerCA.crt\n" +
"\t--cluster-1-hsm-ip HSM IP for a HSM in cluster 1.\n" +
"\t--cluster-1-user-pin login pin for cluster 1. e.g. as : like cu1:password1.\n" +
"\t--cluster-2-hsm-ca-cert Absolute path of hsm ca cert file for cluster 2. e.g. /opt/cloudhsm/etc/customerCA2.crt\n" +
"\t--cluster-2-hsm-ip HSM IP for a HSM in cluster 2.\n" +
"\t--cluster-2-user-pin login pin cluster 2. e.g. as : like cu2:password2.\n\n";
public static void main(String[] args) throws Exception {
String hsmFilePath1 = null;
String hsmIp1 = null;
String userLoginPin1 = null;
String hsmFilePath2 = null;
String hsmIp2 = null;
String userLoginPin2 = null;
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "--cluster-1-hsm-ca-cert":
hsmFilePath1 = args[++i];
break;
case "--cluster-1-hsm-ip":
hsmIp1 = args[++i];
break;
case "--cluster-1-user-pin":
userLoginPin1 = args[++i];
break;
case "--cluster-2-hsm-ca-cert":
hsmFilePath2 = args[++i];
break;
case "--cluster-2-hsm-ip":
hsmIp2 = args[++i];
break;
case "--cluster-2-user-pin":
userLoginPin2 = args[++i];
break;
case "--help":
help();
return;
}
}
if (isInvalid(hsmFilePath1) || isInvalid(hsmIp1) || isInvalid(userLoginPin1) || isInvalid(hsmFilePath2)
|| isInvalid(hsmIp2) || isInvalid(userLoginPin2)) {
help();
return;
}
if (System.getenv().containsKey("HSM_USER") || System.getenv().containsKey("HSM_PASSWORD") ) {
throw new IllegalStateException("The env variables HSM_USER and HSM_PASSWORD need to be unset before running the sample");
}
try {
CloudHsmProvider desedeClusterProvider = null;
if (Security.getProvider(CLUSTER_DESEDE_PROVIDER_NAME) == null) {
desedeClusterProvider = createProvider(CLUSTER_DESEDE_PROVIDER_NAME, hsmFilePath1, hsmIp1);
Security.addProvider(desedeClusterProvider);
System.out.println(MessageFormat.format("Created and added CloudHsmProvider with unique ID: {0} to the JavaSecurity configuration", CLUSTER_DESEDE_PROVIDER_NAME));
String[] userNameAndPassword = userLoginPin1.split(":");
String user1 = userNameAndPassword[0];
String password1 = userNameAndPassword[1];
LoginRunner.loginWithPinOnGivenProvider(user1, password1, CLUSTER_DESEDE_PROVIDER_NAME);
}
CloudHsmProvider rsaClusterProvider = null;
if (Security.getProvider(CLUSTER_RSA_PROVIDER_NAME) == null) {
rsaClusterProvider = createProvider(CLUSTER_RSA_PROVIDER_NAME, hsmFilePath2, hsmIp2);
Security.addProvider(rsaClusterProvider);
System.out.println(MessageFormat.format(" Created and added CloudHsmProvider with unique ID: {0} to the JavaSecurity configuration", CLUSTER_RSA_PROVIDER_NAME));
String[] userNameAndPassword = userLoginPin2.split(":");
String user2 = userNameAndPassword[0];
String password2 = userNameAndPassword[1];
LoginRunner.loginWithPinOnGivenProvider(user2, password2, CLUSTER_RSA_PROVIDER_NAME);
}
System.out.println("\nStarted executing DESede operations on provider " + CLUSTER_DESEDE_PROVIDER_NAME);
performDESedeEncryptAndDecrypt();
System.out.println("Finished executing operations on provider " + CLUSTER_DESEDE_PROVIDER_NAME + "\n");
LoginRunner.logout(desedeClusterProvider);
System.out.println("Started executing RSA operations on provider " + CLUSTER_RSA_PROVIDER_NAME);
performRSAEncryptAndDecrypt();
performRSASignAndVerify();
System.out.println("Finished executing operations on provider " + CLUSTER_RSA_PROVIDER_NAME);
LoginRunner.logout(rsaClusterProvider);
} catch (IOException ex) {
System.out.println(ex);
}
}
private static boolean isInvalid(String requiredParameter) {
return requiredParameter == null || requiredParameter.isEmpty();
}
private static void help() {
System.out.println(helpString);
}
public static CloudHsmProvider createProvider(String clusterUniqueId,
String caFilePath,
String hostIp)
throws Exception {
final CloudHsmServer server = CloudHsmServer.builder()
.withHostIP(hostIp)
.build();
final CloudHsmCluster cluster = CloudHsmCluster.builder()
.withClusterUniqueIdentifier(clusterUniqueId)
.withHsmCAFilePath(caFilePath)
.withOptions(OptionalParameters.VALIDATE_KEY_AT_INIT, false)
.withServer(server)
.build();
final CloudHsmLoggingConfig loggingConfig = CloudHsmLoggingConfig.builder()
.withLogFile("/opt/cloudhsm/run/cloudhsm-jce.log")
.withLogInterval("daily")
.withLogType("file")
.withLogLevel("info")
.build();
final CloudHsmProviderConfig testConfig = CloudHsmProviderConfig.builder()
.withCluster(cluster)
.withCloudHsmLogging(loggingConfig)
.build();
return new CloudHsmProvider(testConfig);
}
private static void performRSAEncryptAndDecrypt() throws Exception {
String plainText = "This is a sample Plain Text Message!";
String transformation = "RSA/ECB/OAEPPadding";
String providerName = CLUSTER_RSA_PROVIDER_NAME;
KeyPair kp = AsymmetricKeys.generateRSAKeyPair(2048, "rsa encrypt decrypt test", providerName);
// RSA Encrypt
System.out.println("Performing RSA Encryption Operation");
Cipher encCipher = Cipher.getInstance(transformation, providerName);
encCipher.init(Cipher.ENCRYPT_MODE, kp.getPublic());
byte[] cipherText = encCipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
System.out.println("Encrypted plaintext = " + Base64.getEncoder().encodeToString(cipherText));
// RSA Decrypt
Cipher decCipher = Cipher.getInstance(transformation, providerName);
decCipher.init(Cipher.DECRYPT_MODE, kp.getPrivate());
byte[] decryptedText = decCipher.doFinal(cipherText);
String decryptedPlainText = new String(decryptedText, StandardCharsets.UTF_8);
System.out.println("Decrypted text = " + decryptedPlainText);
assert (java.util.Arrays.equals(plainText.getBytes(StandardCharsets.UTF_8), decryptedText));
}
private static void performRSASignAndVerify() throws Exception {
String plainText = "This is a sample Plain Text Message!";
String signingAlgorithm = "SHA512withRSA";
String providerName = CLUSTER_RSA_PROVIDER_NAME;
KeyPair kp = AsymmetricKeys.generateRSAKeyPair(2048, "rsa sign verify test", providerName);
// RSA Sign
Signature sig = Signature.getInstance(signingAlgorithm, providerName);
sig.initSign(kp.getPrivate());
sig.update(plainText.getBytes(StandardCharsets.UTF_8));
byte[] signature = sig.sign();
System.out.println("RSA signature = " + Base64.getEncoder().encodeToString(signature));
// RSA Verify
sig.initVerify(kp.getPublic());
sig.update(plainText.getBytes(StandardCharsets.UTF_8));
if (sig.verify(signature)) {
System.out.println("Signature verified");
} else {
System.out.println("Signature is invalid!");
}
}
private static void performDESedeEncryptAndDecrypt() throws Exception {
String providerName = CLUSTER_DESEDE_PROVIDER_NAME;
// Generate a new DES Key to use for encryption.
Key key = SymmetricKeys.doGenerateDESKey("DesEcbTest", providerName);
System.out.println(MessageFormat.format("Generated the DES key to use for encryption on cluster {0}", providerName));
// Generate some random data to encrypt
byte[] plainText = new byte[1024];
Random r = new Random();
r.nextBytes(plainText);
// Encrypt the plaintext
Cipher encCipher = Cipher.getInstance("DESede/ECB/NoPadding", providerName);
encCipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = encCipher.doFinal(plainText);
System.out.println(MessageFormat.format("Encrypted using the generated DES key on cluster {0}", providerName));
// Decrypt the ciphertext and verify with the original plaintext.
Cipher decCipher = Cipher.getInstance("DESede/ECB/NoPadding", providerName);
decCipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedText = decCipher.doFinal(cipherText);
assert (java.util.Arrays.equals(plainText, decryptedText));
System.out.println(MessageFormat.format("Decrypted successfully using the generated DES key on cluster {0}", providerName));
}
}