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.

HSM Cluster

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.

Initialized CloudHSM Cluster


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.

hsm-client Instance


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

CloudHSM CLI

2) Login as PRECO user:

loginHSM PRECO admin password

PRECO user login

3) Reset PRECO user password:

changePswd PRECO admin <NewPassword>

PRECO user password reset

4) (Optional) List users:

listUsers

Users list

5) Exit out of HSM node:

quit

And your cluster should now be in an active state.

Activated CloudHSM cluster

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>

Create KMS User

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

HA CloudHSM cluster

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.

KMS Custom Key Store

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.

KMS Custom Key Store in Connected State

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

CMK stored on HSM

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.

Vimal Paliwal

Vim is a DevSecOps Practitioner with over seven years of professional experience. Over the years, he has architected and implemented full fledged solutions for clients using AWS, K8s, Terraform, Python, Shell, Prometheus, etc keeping security as an utmost priority. Along with this, during his journey as an AWS Authorised Instructor he has trained thousands of professionals ranging from startups to fortune companies for over 2 years.

Previous
Previous

Secure database authentication using AWS IAM

Next
Next

Encrypt an existing Kubernetes Persistent Volume running on AWS EKS