Easiest way to achieve FIPS 140-2 Level 3 compliance on AWS
There are 4 levels of FIPS 140-2 compliance where the fourth level being the most secure one providing the highest degree of protection. In this article, we will focus on achieving FIPS 140-2 Level 3 compliance using a cloud-based dedicated hardware security module (HSM) provided by AWS for storing encryption keys. FIPS 140-2 Level 3 compliance is mostly required for companies that deal with sensitive data such as health and financial records so if your project does not fall under these categories then you might just be good with using the AWS KMS service by itself for managing encryption keys.
Prerequisites:
For a deep dive we will need the following services set up in our AWS account:
VPC: with a minimum of 2 private subnets and a NAT
EC2: to interact with the CloudHSM device
AWS CLI: to spin up the required AWS services
Note: Creating a VPC is not covered in this article. You can either refer to the official AWS doc or use a Terraform module to setup the VPC.
CloudHSM
Note: CloudHSM service does not have a free tier option and is an expensive service to run. Please refer to the pricing page before launching the service.
Note: Please make sure the AWS CLI is configured with the appropriate permissions to successfully run all the below mentioned aws commands.
Let’s start with creating the cluster first. At a later stage, we will attach two HSM nodes/modules for high availability to this cluster.
aws cloudhsmv2 create-cluster --hsm-type hsm1.medium --subnet-ids "subnet-xxxxx" "subnet-xxxxx"
From the response output, note down the cluster id and availability zone information because we will need them going forward.
After the HSM cluster is created and the state turns to Uninitialized state we need to add an HSM module/node to the cluster:
aws cloudhsmv2 create-hsm --cluster-id cluster-xxxxxxxx --availability-zone xxxxxxx
It’s now time to initialise the cluster and for that, we will sign the CSR generated by the HSM cluster using a self-signed certificate. Let’s move on.
1) Download the CSR generated by the HSM cluster
aws cloudhsmv2 describe-clusters --filters clusterIds=cluster-xxxxxx --output text --query 'Clusters[].Certificates.ClusterCsr' > cluster-xxxxxx_ClusterCsr.csr
Now that we have downloaded the CSR, let’s generate a self-signed certificate and for that, we need a private key.
2) Creating a private key
openssl genrsa -aes256 -out customerCA.key 2048
3) Generating a self-signed certificate
openssl req -new -x509 -days 3652 -key customerCA.key -out customerCA.crt
4) Sign the CSR
openssl x509 -req -days 3652 -in cluster-xxxxx_ClusterCsr.csr -CA customerCA.crt -CAkey customerCA.key -CAcreateserial -out cluster-xxxxx_CustomerHsmCertificate.crt
Time to initialise the HSM cluster and for this, we need to upload both the self-signed certificate and the signed HSM certificate that we generated in the previous step.
aws cloudhsmv2 initialize-cluster --cluster-id cluster-xxxxx --signed-cert file://cluster-xxxxx_CustomerHsmCertificate.crt --trust-anchor file://customerCA.crt
This starts the initialisation process for the HSM cluster which takes a few minutes to complete. Once the process is complete your HSM cluster will enter into an INITIALIZED state. The next step is to activate the cluster, for which, we need to interact with the HSM module/node and since it is hosted in a private subnet we cannot activate it over the public internet so we need to launch an EC2 instance in the same VPC as of the HSM cluster so while we are waiting for the cluster to complete initialisation process let’s launch the instance.
EC2 Instance
Instead of opening port 22 or 3389 depending on the OS, for connecting to the instance we will use the AWS SSM Session Manager feature to securely connect to the EC2 instance. Before launching the instance, we need to create an IAM instance profile with the required permissions to use SSM Session Manager for connecting to the instance.
1) Create IAM instance profile
aws iam create-instance-profile --instance-profile-name hsm-client
2) Create IAM role
hsm-client-assume-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow"
}
]
}
aws iam create-role --role-name hsm-client --assume-role-policy-document file://hsm-client-assume-policy.json
3) Attach policy to the role
aws iam attach-role-policy --role-name hsm-client --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
4) Attaching IAM role to the instance profile
aws iam add-role-to-instance-profile --instance-profile-name hsm-client --role-name hsm-client
5) Create security group for the instance
aws ec2 create-security-group --group-name hsm-client --description "Security group for hsm client" --vpc-id vpc-xxxxx
Note: By default, an outbound rule will be added to the security allowing all traffic and because we will be using SSM Session Manager for connecting to the EC2 instance we do not need to add any ingress rule but in case you want to connect to the instance via key-pair, you need to allow traffic on port 22. You can open port 22 to the world (not recommended) or restrict it to your own IP.
6) Update the HSM cluster’s security group to allow connections from the EC2 instance security group
aws ec2 authorize-security-group-ingress --group-id sg-xxxxx --protocol tcp --port 2223-2225 --source-group sg-xxxxx
Note: --group-id is the ID of security group attached to the HSM cluster and the --source-group is the ID of security group attached to the hsm-client instance.
7) Launch an EC2 instance
aws ec2 run-instances --image-id ami-xxxxx --instance-type t3a.micro --security-group-ids sg-xxxxx --subnet-id subnet-xxxxx --iam-instance-profile Name=hsm-client
Note: I have launched an Amazon Linux 2 instance in private subnet but you can also launch it in a public subnet if you are planning on using key-pair for SSH. In case, you prefer to launch the instance in a private subnet as well, make sure to have a NAT gateway/instance associated with the private subnet otherwise you will not be able to SSH into the instance via SSM Session Manager.
Activating the cluster
If you too have launched an Amazon Linux 2 instance then you can run the below-mentioned download and install commands as it is else you need to visit to the official AWS doc and install the appropriate package for your Linux distribution.
Download:
wget https://s3.amazonaws.com/cloudhsmv2-software/CloudHsmClient/EL7/cloudhsm-client-latest.el7.x86_64.rpm
Install:
yum install ./cloudhsm-client-latest.el7.x86_64.rpm -y
Before we activate the cluster, we need to copy the issuing certificate (customerCA.crt) that we created earlier on our local machine to the client-hsm instance at /opt/cloudhsm/etc/customerCA.crt path and update the CloudHSM CLI tool config.
To copy the customerCA.crt file to the client-hsm instance you can either copy the file content from your local machine and paste it into a new file at the specified location or you can use SFTP service to upload the file if you have launched the instance in a public subnet with key-pair attached.
Once the file is copied to the correct location, we need to grab the private IP of the HSM module/node and update the HSM CLI tool config so that we can connect to the HSM node at a later stage.
Fetch HSM node private IP:
aws cloudhsmv2 describe-clusters --filters clusterIds=cluster-xxxxx --query 'Clusters[0].Hsms[0].EniIp'
Update config:
/opt/cloudhsm/bin/configure -a <IP address>
It is finally time to activate the cluster. To do so, we need to log in to the HSM with precrypto officer (PRECO) credentials and reset it. Once we reset the password, the PRECO user becomes the crypto officer (CO) user and our CloudHSM cluster gets activated.
1) Connect to HSM node:
/opt/cloudhsm/bin/cloudhsm_mgmt_util /opt/cloudhsm/etc/cloudhsm_mgmt_util.cfg
2) Login as PRECO user:
loginHSM PRECO admin password
3) Reset PRECO user password:
changePswd PRECO admin <NewPassword>
4) (Optional) List users:
listUsers
5) Exit out of HSM node:
quit
And your cluster should now be in an active state.
Next, we need to create a crypto user (CU) so that we can connect the KMS custom key store with the CloudHSM cluster in the later stage. For that, we need to connect back to our HSM cluster and run the following command.
createUser CU kmsuser <kmsPswd>
Note: It does not matter if we choose a strong or weak password for kmsuser because once we connect KMS custom key store with CloudHSM it will rotate the password for kmsuser automatically.
For high availability and for connecting the CloudHSM cluster to the KMS custom key store, a minimum of two HSM nodes are required so let’s create the other one too in a different availability zone.
aws cloudhsmv2 create-hsm --cluster-id cluster-xxxxxxxx --availability-zone xxxxxxx
It’s time to associate KMS with CloudHSM for easy management of encryption keys and use these keys to enable server-side encryption for services that support the use of CMK via KMS such as RDS, S3, SQS, EBS, etc.
KMS Custom Key Store
Note: Before you create a custom key store make sure both your HSM modules are in an active state.
You can execute these commands either from your local machine or the hsm-client instance. Just make sure appropriate permissions are attached to the IAM user or role and the customerCA.crt file is available on the machine.
aws kms create-custom-key-store --custom-key-store-name hsm --cloud-hsm-cluster-id cluster-xxxxx --key-store-password <kmsPswd> --trust-anchor-certificate file://customerCA.crt
Note down the custom key store ID received in the response as we will need it in the next step.
Once the KMS custom key store is created you will notice that it is in a DISCONNECTED state by default. We need to explicitly connect it to the CloudHSM cluster that we created.
aws kms connect-custom-key-store --custom-key-store-id cks-xxxxx
Note: Connecting custom key store to the CloudHSM cluster can take upto 20 minutes.
Once the custom key store is connected to the CloudHSM cluster you can create a Customer-managed key that stores your encryption keys on a dedicated HSM device rather than a shared HSM device.
aws kms create-key --description "hsm key" --key-usage ENCRYPT_DECRYPT --key-spec SYMMETRIC_DEFAULT --origin AWS_CLOUDHSM --custom-key-store-id cks-xxxxx
Awesome. You now have a FIPS 140-2 Level 3 compliant architecture that stores your encryption keys on a dedicated HSM device.
Note: Make sure to delete all the components that were created as a part of this article and especially CloudHSM since it is an expensive service to run.
Caveats
With the integration of CloudHSM and KMS managing a FIPS 140-2 Level 3 compliant encryption architecture has become very straightforward but it still isn’t a full-fledged system. At the time of writing this article, with the help of a custom key store you can only create symmetric encryption keys with the key material generated by AWS KMS. This means you cannot create a KMS key with imported key material and manage asymmetric keys using a custom key store.
Covering the basics
-
AWS CloudHSM is a cloud-based HSM that lets you store your encryption keys on a FIPS 140-2 Level 3 compliant dedicated HSM device. It supports industry-standard APIs, such as PKCS#11, Java Cryptography Extensions (JCE), and Microsoft CryptoNG (CNG) libraries that allow you to easily integrate CloudHSM with your application. Being a fully-managed service it relieves you from administrative tasks such as hardware provisioning, software patching and backups.
-
AWS KMS allows you to manage your encryption keys on a FIPS 140-2 Level 2 compliant HSM device that are shared with other customers too whereas in the case of CloudHSM your keys are stored on a FIPS 140-2 Level 3 compliant HSM device which is dedicated for your account. Along with that, KMS has native integration with many AWS services that allows you to use CMKs for SSE whereas CloudHSM does not support direct integration with the other AWS services. But, with the help of KMS custom key store you can use encryption keys stored on a dedicated CloudHSM device to enable SSE.
-
FIPS stands for Federal Information Processing Standards. These standards are developed by the National Institute of Standards and Technology (NIST). FIPS 140-2 is one of the many standards that specify the security requirements for a cryptographic module. There are four levels under FIPS 140-2 which are Level 1, Level 2, Level 3, and Level 4 where Level 4 being the most secure and provides the highest level of protection.