Introduction
Today, AWS IoT Core announces the general availability of self-managed client certificate signing for AWS IoT Core fleet provisioning. The new self-managed certificate signing capability allows you to integrate with an external certificate authority (CA), your own public key infrastructure (PKI), or popular CA services such as AWS Private CA, to sign certificate signing requests (CSRs) when provisioning your fleet. This integration enables you to customize attributes of X.509 client certificates while using fleet provisioning, which is particularly beneficial for security-sensitive scenarios. In this blog, you will learn how to setup self-managed client certificate signing capability using AWS Management Console and AWS CLI.
Benefits of self-managed certificate signing capability for fleet provisioning
Streamlined client certificate customization: With the self-managed client certificate signing capability, you can sign client certificates with any CA directly within fleet provisioning. This means you don’t need to set up a custom solution, saving you time on deployment and reducing maintenance costs.
Enhanced security and flexibility: By allowing you to use your private CA or other publicly trusted CAs, AWS IoT Core allows flexibility to your specific security requirements. The ability to choose validity periods, signing algorithms, issuers, and extensions gives you greater flexibility in managing certificates.
No firmware update required: No firmware updates are necessary to utilize the new self-managed certificate signing method. Enabling self-managed client certificate signing method via the AWS Management Console or AWS CLI will subsequently change the certificate signing behavior of the fleet provisioning CreateCertificateFromCsr MQTT API. In contrast, when you use AWS managed client certificate signing method, AWS IoT Core signs the CSRs using its own CAs.
Overview of AWS IoT Core fleet provisioning
With the AWS IoT Core fleet provisioning feature, you can generate and securely deliver client certificates and private keys when clients connect to AWS IoT Core for the first time. Notably, you get the flexibility to utilize client certificates signed by a CA authority beyond client certificates issued by AWS IoT Core. This functionality streamlines the device setup process and offers greater customization options.
There are two ways to provision your fleet:
Provision by claim
Device can be manufactured with a provisioning claim certificate and private key, which are very restrictive credentials meant only for provisioning. If these certificates are registered with AWS IoT Core, the service can exchange them for unique client certificates that the device can use for regular operations.
Provision by trusted user
When provisioning by trusted user in many cases, a device connects to AWS IoT Core for the first time when a trusted user, such as an end user or installation technician, uses a mobile app to configure the device in its deployed location, Provisioning by trusted user is frequently used when devices must be setup with a companion app, e.g. smart home devices.
Workflows to enable the feature
Pre-requisites
Permission to create certificate provider in your AWS account.
Permission to add or create a Lambda function.
Permission to add or update Lambda function variables
To enable self-managed client certificate signing, you need to follow these steps
Create an AWS Lambda function capable of signing certificates and grant AWS IoT permission to invoke the function.
Switch to the self-managed certificate signing method, which will create an account-level AWS IoT Core certificate provider resource that utilizes the AWS Lambda function Amazon Resource Names (ARN).
Shortly after the AWS IoT Core certificate provider is created, all subsequent calls to the fleet provisioning CreateCertificateFromCsr MQTT API will use the AWS Lambda function to sign certificate signing requests (CSRs) on this account. To revert to client certificates signed by AWS IoT Core’s own CAs, you can switch back to the AWS managed CAs, which will remove the certificate provider from the account.
Solution Overview
Let’s look at the self-managed client certificate signing for AWS IoT Core fleet provisioning solution overview in step-by-step pattern along with its architecture diagram.
The following steps demonstrates the behavior of CreateCertificateFromCsr when a user creates and switches to self-managed client certificate signing:
Device requests: CreateCertificateFromCsr.
AWS IoT Core signs the CSR using its own CA and issues a client certificate, as no AWS IoT Core certificate provider exists.
User changes client certificate signing method to self-managed, which creates a certificate provider.
Device requests: CreateCertificateFromCsr.
AWS IoT Core invokes the AWS Lambda function of the certificate provider to sign the client certificate.
User switches the client certificate signing method to AWS managed, which deletes the certificate provider and moves to AWS managed client certificate signing.
Device requests: CreateCertificateFromCsr.
AWS IoT Core signs the CSR, as no client certificate self-signing method exists.
Figure 1.0: AWS IoT Core fleet provisioning solution overview architecture diagram
Implementation walkthrough
Create a private CA
In this blog, the self-signing client certificate method uses AWS Private CA to sign certificates. See Creating a private CA for instructions on how to create a private CA. Save the ARN of the CA you have created.
Create AWS Lambda function
Before switching to self-managed client certificate signing method, you must create an AWS Lambda function which can sign CSRs. The function below calls AWS Private CA to sign the input CSR using a private CA and the SHA256WITHRSA signing algorithm. The returned client certificate will be valid for one year (you can alter the validity per your requirements, as sample code uses 365 days validity).
Step 1:
From AWS Lambda console:
Select Create function
Select ‘Author from scratch’
Give function a name, select the latest Python runtime, leaving the rest of the settings default
Select ‘Create function’
Once the function has been created, proceed to step 2.
Step 2:
Select the function and copy the sample code below into the editor.
import os
import time
import uuid
import boto3
def lambda_handler(event, context):
ca_arn = os.environ[‘CA_ARN’]
csr = (event[‘certificateSigningRequest’]).encode(‘utf-8’)
acmpca = boto3.client(‘acm-pca’)
cert_arn = acmpca.issue_certificate(
CertificateAuthorityArn=ca_arn,
Csr=csr,
Validity={“Type”: “DAYS”, “Value”: 365},
SigningAlgorithm=’SHA256WITHRSA’,
IdempotencyToken=str(uuid.uuid4())
)[‘CertificateArn’]
# Wait for certificate to be issued
time.sleep(1)
cert_pem = acmpca.get_certificate(
CertificateAuthorityArn=ca_arn,
CertificateArn=cert_arn
)[‘Certificate’]
return {
‘certificatePem’: cert_pem
}
The code references the ARN of the private CA you created, which must be set in the function’s configuration. Navigate to the Configuration tab, and select environment variables in the left-hand menu. Click edit and then add environment variable. Enter CA_ARN for the key and the ARN of your private CA for the value.
Grant AWS IoT permission to invoke the function
After creating your AWS Lambda function, you must grant AWS IoT permission to invoke the function.
Step 1:
Select Lambda function
Navigate to the Configuration tab
Select Permissions
Under ‘Resource-based’ policy statements
Select ‘Add permissions’
Select ‘AWS service’
From the Service drop-down menu, Select ‘AWS IoT’
For ‘Statement ID’, enter unique statement ID
For ‘Source ARN’, paste the ARN of the certificate provider (replacing the values of region, Account and certificate provider name) i.e. “arn:aws:iot:REGION:ACCOUNT_ID:certificateprovider:CERTIFICATE_PROVIDER_NAME”
Testing our AWS Lambda function
We can test our AWS Lambda function by selecting our newly created lambda function name, navigating to ‘Test’ tab, creating new ‘Test event action’, and populating the sample JSON below:
{
“certificateSigningRequest”: “—–BEGIN CERTIFICATE REQUEST—–\nMIICaTCCAVECAQAwJDEiMCAGA1UEAwwZQm9zdG9uQ2VydGlmaWNhdGVQcm92aWRl\ncjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALAlk4aEcoheqUPFOj17\ne8Qs9fhLXkNLhtmx/ePE6A0Tb6dFwWt+jwspITE96heBBQrMVCwVkI2C5tbtpx3a\n8+c5qlSZBGX7w9Tlz1Ik30rJQTeB/X7CIU068ld4b+xBNxQLJQw0eSmWCC8p+CD/\nkdxC8rGCkSia/Cd7Hp9pTdBL8nU1t+QDppv+c4dtYrRVDjPmRcwpv4dyvH5/R6aZ\nxJToKPlt3P6cpa5KEhWZvjVt7XvpbU4GMhP+ZeQL1bLFWZAJ+tAiz6qG5xr4X/2V\nWjmSQWsDnbSzWjdRtXJZZGucIR6Gif95G2E08/VJlRtBi3d3OnhdYbYBiNW4X5ck\nsqkCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCTqiW6qZ1nLW1pNt35wFVTpvzR\nUUkAdNLugmdNZIhqi4VWi0YXfhTPszOdnTcDAaoBTvSvmqCZHfPnRnt65XsMcNQO\nY+M5f/b1n5t0kKbzdFLu+GlK2eB+J8VtQqfAKw3q5a6Q0nu+OUOhm2PepdMkRoxw\n9tUDLTHiG/8zySxUo552hNlBz0wDVb1hjrEgLDi56mQ7FJKICzpkQAq5pMcJQj6t\nozWYrxzszGDa+ZFQ7H4DY/xl4acf1rownncF7mqQgVcAjTXchJ/ETIghJAO8qU1+\nz7ASTlm8Bm8Qov9YiISzss9i2z/78tVksvL3idZ5L0W2m6pnkVuQe3wknBYw\n—–END CERTIFICATE REQUEST—–“,
“clientId”: “221a6d10-9c7f-42f1-9153-e52e6fc869c1”,
“principalId”: “f2a33ae79323012c5f5b4250de3952568f1d81b2aa5bad1301b23b0991ba0ef4”
}
After populating the test event, save and test the AWS Lambda function.
Enabling self-managed client certificate signing using AWS IoT console
From AWS IoT console (see screenshots below):
Select ’Security’
Select ‘Certificate signing’
Select ‘Edit signing method’
Figure 1.1: Self-managed certificate signing for fleet provisioning
Select ‘Self-managed’
Under ‘Self-managed’ settings
For ‘Certificate provider name’, give a unique name
For AWS Lambda function, select our earlier created Lambda function
Select ‘Update certificate signing’.
Figure 1.2: Enabling self-signed certificate signing
Enter ‘confirm’ and select ‘Confirm’.
Figure 1.3: Confirm certificate signing method
Upon completion, we will see ‘Certificate signing details’ changed to ‘Self-managed’ (see figure 1.4 below).
Figure 1.4: Client certificate signing details
Self-managed client certificate signing AWS Lambda function input
AWS IoT Core sends the following JSON object to the AWS Lambda function when a device calls the CreateCertificateFromCsr MQTT API. The value of certificateSigningRequest is the CSR (in Privacy-Enhanced Mail (PEM) format) provided in the CreateCertificateFromCsr request made by the device. The principalId is the ID of the principal (client certificate) used to connect to AWS IoT Core when making the CreateCertificateFromCsr request. clientId is the client ID set for the MQTT connection.
{
“certificateSigningRequest”: “string”,
“principalId”: “string”,
“clientId”: “string”
}
Self-managed client certificate signing AWS Lambda function response
The AWS Lambda function must return a response that contains the certificatePem value. The following is an example of a successful response. AWS IoT uses the return value (certificatePem) to create a client certificate.
{
“certificatePem”: “string”
}
If the registration of the client certificate is successful, CreateCertificateFromCsr will return the same certificatePem in the CreateCertificateFromCsr response. For more information, see the response payload example of CreateCertificateFromCsr.
Important notes:
Client certificates returned by the AWS Lambda function must have the same subject name and public key as the Certificate Signing Request (CSR).
The AWS Lambda function must finish running within 5 seconds.
The AWS Lambda function must be in the same AWS account and Region where you enable self-managed client certificate signing, which creates the associated AWS IoT Core certificate provider resource.
For AWS IoT service principal, you must grant invoke permission to the AWS Lambda function. To avoid the confused deputy security issue (follow the linked guidance to avoid cross-deputy), we recommend that you set sourceArn and sourceAccount for the invoke permissions. For more information, see cross-service confused deputy prevention.
Enabling self-managed client certificate signing using AWS CLI
Self-managed client certificate signing requires you to create an account-level AWS IoT Core certificate provider. You can create a certificate provider using create-certificate-provider CLI command.
aws iot create-certificate-provider \
–certificateProviderName my-certificate-provider \
–lambdaFunctionArn arn:aws:lambda<<your-region>:<your-account-id>:function:my-function \
--accountDefaultForOperations CreateCertificateFromCsr
The following shows example output for this command:
{
“certificateProviderName”: “my-certificate-provider”,
“certificateProviderArn”: “arn:aws:iot: <your-region>:<your-account-id>:my-certificate-provider”
}
You can confirm the successful creation of your AWS IoT Core certificate provider by listing the provider in your account:
aws iot list