in src/main/java/com/example/AwsKmsPqTlsExample.java [60:212]
public static void main(String[] args) throws Exception {
/*
* Check preconditions before continuing. The AWS CRT supports hybrid post-quantum TLS on Linux systems only.
*/
if (TlsCipherPreference.TLS_CIPHER_PREF_KMS_PQ_TLSv1_0_2020_07.isSupported()) {
LOG.info(() -> "Hybrid post-quantum ciphers are supported and will be used");
} else {
throw new UnsupportedOperationException("Hybrid post-quantum cipher suites are supported only on Linux systems");
}
/*
* Set up a PQ TLS HTTP client that will be used in the rest of the example.
*/
SdkAsyncHttpClient awsCrtHttpClient = AwsCrtAsyncHttpClient.builder()
.tlsCipherPreference(TlsCipherPreference.TLS_CIPHER_PREF_KMS_PQ_TLSv1_0_2020_07)
.build();
/*
* Set up a Java SDK 2.0 KMS Client which will use hybrid post-quantum TLS for all connections to KMS.
*/
KmsAsyncClient asyncKMSClient = KmsAsyncClient.builder()
.httpClient(awsCrtHttpClient)
.build();
/*
* Import key material workflow with hybrid post-quantum TLS
*
* Step 1: Create an external CMK with no key material
*/
CreateKeyRequest createRequest = CreateKeyRequest.builder()
.origin(OriginType.EXTERNAL)
.description("Test key for aws-kms-pq-tls-example. Feel free to delete this.")
.build();
CreateKeyResponse createResponse = asyncKMSClient.createKey(createRequest).get();
String keyId = createResponse.keyMetadata().keyId();
LOG.info(() -> "Created CMK " + keyId);
/*
* Step 2: Get the wrapping key and token required to import the local key material. The AlgorithmSpec determines
* how we must wrap the local key material using the public key from KMS.
*/
GetParametersForImportRequest getParametersRequest = GetParametersForImportRequest.builder()
.keyId(keyId)
.wrappingAlgorithm(AlgorithmSpec.RSAES_OAEP_SHA_1)
.wrappingKeySpec(WrappingKeySpec.RSA_2048)
.build();
GetParametersForImportResponse getParametersResponse =
asyncKMSClient.getParametersForImport(getParametersRequest).get();
/*
* Step 3: Prepare the parameters for the ImportKeyMaterial call.
*/
SdkBytes importToken = getParametersResponse.importToken();
byte[] publicKeyBytes = getParametersResponse.publicKey().asByteArray();
/*
* Create an ephemeral AES key. You should never do this in production. With KMS ImportKeyMaterial, you are
* responsible for keeping a durable copy of the key.
* https://docs.aws.amazon.com/kms/latest/developerguide/importing-keys.html
*
* The plaintextAesKey exists only for the lifetime of this function. This example key material will expire from
* KMS in 10 minutes. This is the 'validTo(Instant.now().plusSeconds(600))' in the ImportKeyMaterial call below.
*/
byte[] plaintextAesKey = new byte[AES_KEY_SIZE_BYTES];
SECURE_RANDOM.nextBytes(plaintextAesKey);
/*
* Use the wrapping key to encrypt the local key material. Then use the token to import the wrapped key
* material into KMS.
*
* This RSA wrapped key material is protected in transit with PQ TLS. If you use classic TLS, a large-scale
* quantum computer would be able to decrypt the TLS session data and recover the RSA-wrapped key material. Then
* it could decrypt the RSA-wrapped key to recover your plaintext AES key.
*/
RSAPublicKey rsaPublicKey = RSAUtils.decodeX509PublicKey(publicKeyBytes);
byte[] encryptedAesKey = RSAUtils.encryptRSA(rsaPublicKey, plaintextAesKey);
/*
* Step 4: Import the key material using the CMK ID, wrapped key material, and import token. This is the
* important call to protect. Your AES key is leaving your computer and traveling over the network wrapped by an
* RSA public key and encrypted with PQ TLS.
*
* This AES key will be used for all KMS cryptographic operations when you use this CMK. If this key is
* compromised, all ciphertexts that use this CMK are also compromised.
*/
ImportKeyMaterialRequest importRequest = ImportKeyMaterialRequest.builder()
.keyId(keyId)
.encryptedKeyMaterial(SdkBytes.fromByteArray(encryptedAesKey))
.importToken(importToken)
.expirationModel(ExpirationModelType.KEY_MATERIAL_EXPIRES)
.validTo(Instant.now().plusSeconds(600))
.build();
LOG.info(() -> String.format("Importing key material into CMK %s. Using PQ TLS to protect RSA-wrapped AES key " +
"in transit", keyId));
asyncKMSClient.importKeyMaterial(importRequest).get();
/*
* Sensitive cryptographic operations workflow. Use a KMS CMK to encrypt and decrypt data. The CMK can have any
* origin (AWS_KMS, EXTERNAL, or AWS_CLOUDHSM). This example reuses the CMK with imported key material that we
* created in the previous step.
*
* Step 1: Generate a data key. KMS GenerateDataKey returns the plaintext data key and a copy of that data key
* encrypted under the CMK using AES-GCM with 256-bit keys. It is your responsibility to keep the ciphertext so
* the plaintext data key can be decrypted in the future.
*/
GenerateDataKeyRequest generateDataKeyRequest = GenerateDataKeyRequest.builder()
.keyId(keyId)
.keySpec(DataKeySpec.AES_256)
.build();
LOG.info(() -> String.format("Generating a data key. Using PQ TLS to protect the plaintext data key in transit. " +
"The encrypted data key is encrypted under the CMK %s", keyId));
GenerateDataKeyResponse generateDataKeyResponse = asyncKMSClient.generateDataKey(generateDataKeyRequest).get();
/*
* Step 2: Use the plaintext data key for client-side encryption. You can get the plaintext data key by calling
* generateDataKeyResponse.plaintext(). This step is omitted and will depend on your use case.
*/
/*
* Step 3: Decrypt the encrypted data key.
*/
SdkBytes encryptedDataKey = generateDataKeyResponse.ciphertextBlob();
DecryptRequest decryptRequest = DecryptRequest.builder()
.ciphertextBlob(encryptedDataKey)
.build();
LOG.info(() -> "Decrypting a KMS ciphertext. Using PQ TLS to protect the plaintext data in transit");
DecryptResponse decryptResponse = asyncKMSClient.decrypt(decryptRequest).get();
/*
* Step 4: Use the plaintext data key to decrypt your client-side data. You can get the plaintext data key by
* calling decryptResponse.plaintext(). This step is omitted and will depend on your use case.
*/
/*
* Clean up resources from this demo.
*
* Schedule deletion of the CMK that contains imported key material. Because this CMK was created only for this
* test, we will delete it as part of cleanup. After the CMK is deleted, any ciphertexts encrypted under
* this CMK are permanently unrecoverable.
*/
ScheduleKeyDeletionRequest deletionRequest = ScheduleKeyDeletionRequest.builder()
.keyId(keyId)
.pendingWindowInDays(7)
.build();
ScheduleKeyDeletionResponse deletionResult = asyncKMSClient.scheduleKeyDeletion(deletionRequest).get();
LOG.info(() -> String.format("CMK %s is schedule to be deleted at %s", keyId, deletionResult.deletionDate()));
/*
* Shut down the SDK and HTTP client. This will free any Java and native resources created for the demo.
*/
asyncKMSClient.close();
awsCrtHttpClient.close();
}