AWS IoT - Just in Time Registration of Client Certificates using Lambda functions

How to automatically generate a client certificate, register your own CA, and authenticate a device to establish a TLS connection to AWS IoT


AWS IoT enforces a certificate-based system for client authentication. This means that any action involving AWS IoT requires the client to present a certificate that is registered and activated with their AWS account; the client must also be able to prove they have the private key associated with the presented certificate.

Just In Time Registration of Clients

When a user wishes to add a new client certificate to their account they have to first register it with their AWS account before it can be used. To simplify large scale deployments, AWS provides an automated process called Just-In-Time-Registration that allows a user to bypass the manual process of registering every new certificate before it can be used. As long as your new certificate is signed by a Certificate Authority that is registered with your AWS account, it will be instantly activated by a lambda-function and be ready to use after your first connection.


  • Have a Certificate Authority registered in your AWS account. We will briefly go over how to do so in this post, but more information can be found here in my previous post.
  • Make sure you have a bound Zymkey and have installed all the software specified in the Getting Started guide.

Given that you have registered a Certificate Authority with AWS, you can set up and test JITR with the following steps

  1. Generate a Certificate Signing Request(CSR) with Zymkey’s private key.
  2. Sign the CSR with a Certificate Authority registerd on your AWS account.
  3. Create a new Lambda function on your AWS account that will automatically register new certificates.
  4. Create an AWS IoT rule to trigger the Lambda function on connection to AWS IoT with new certificate.
  5. Connect to your AWS IoT endpoint via TLS.

Manual vs. Programmatic Setup


All AWS settings can be configured both manually through the AWS web interface or programatically through AWS’ boto 3 module in Python. If you would like to use scripts to set everything up, there are a couple more things to set up.

Follow these instructions on the boto3 page to set up the boto3 module for Python:

The boto3 module authenticates with AWS based on a IAM Access ID and Secret Key. The boto3 tutorial will ask you to setup an IAM user, here are some instructions on how to do so:

  1. From the AWS console, choose the IAM service.
  2. Go to Users and select Add User
  3. Choose a username and check the Programmatic access box
  4. For simplicity, choose Attach existing policies directly and select AdministratorAccess
  5. If you wish to better manage your IAM credentials, feel free to customize your Access Policy.
  6. Click Review and then Create User
  7. Save the Access ID and Secret Key and follow the boto3 guide.

Register Certificate Authority with AWS

The first step is to make sure that you have a Certificate Authority registered with AWS IoT. This Certificate Authority will sign your Zymkey certificate and any future device certificates signed by this CA will automatically registered with AWS IoT after the first TLS connection. The following section will show how to generate your own example CA using OpenSSL, and getting it registered with your AWS account.


We will start by showing how to generate an example CA using OpenSSL using a simple bash script. Alternatively you can simply run the commands in this script individually on the command line.

Creating an example CA with OpenSSL

set -e
mkdir CA_files
cd CA_files

openssl ecparam -genkey -name prime256v1 -out zk_ca.key
OPENSSL_CONF=/etc/ssl/openssl.cnf openssl req \
  -x509 -new -SHA256 -nodes -key zk_ca.key \
  -days 3650 -out zk_ca.crt \
  -subj "/C=US/ST=California/L=Santa Barbara/O=Zymkey/"

cp zk_ca.crt zk_ca.pem

Copy the above lines into a script called You can then run the script in the command line by being in the same directory with the following command:


The script will create a directory called CA_files and a couple of files:
zk_ca.key: Private key for the created CA, will be supplied to OpenSSL for signing CSRs.
zk_ca.pem: PEM formatted certificate for the CA
zk_ca.crt: Same file as zk_ca.pem

Registering Certificate Authority with AWS

Next we will go over the actual registration of the certificate authority with Amazon’s IoT service so that AWS IoT will accept certificates signed by those Certifcate Authroities.


  1. From the AWS IoT console select Secure then CA and then click Register

  2. Click register CA

  3. Follow the directions on the following screen to create a verification certificate.

  4. When signing the verification certificate with your CA in Step 4 run the following command:

     openssl x509 -req -in verificationCert.csr -CA CA_files/zk_ca.pem -CAkey CA_files/zk_ca.key -CAcreateserial -out verificationCert.crt -days 500 -sha256

    Note that if you a different CA and not the demo one we generated, to change the -CA and -CAkey paths appropriately.

  5. Click Select CA certificate and point to the correct .pem file. If you use the OpenSSL generated SSL point to CA_files/zk_ca.pem

  6. Click Select verification certificate and point to verificationCert.crt which was created in Step 4.

  7. Select Active CA certificate and Enable auto-registration of device certificates

The following python script will automatically create a verification cert with a registration code and automatically active your Certificate Authority. While it may look a bit intimidating, all you have to worry about is the very last line, where you can change to point to your CA files.

import OpenSSL
import boto3
import os

def gen_AWS_verification_csr(registrationCode):
	key = OpenSSL.crypto.PKey()
	key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
	req = OpenSSL.crypto.X509Req()
	req.get_subject().CN = registrationCode
	req.sign(key, "sha256")	
	return OpenSSL.crypto.dump_certificate_request(OpenSSL.crypto.FILETYPE_PEM, req)

def sign_CSR_with_CA(verification_csr, CA_cert_path, CA_key_path):
	ca_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, open(CA_cert_path).read())
	ca_key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, open(CA_key_path).read())
	req = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, verification_csr)
	cert = OpenSSL.crypto.X509()
	cert.gmtime_adj_notAfter(24 * 60 * 60)
	cert.sign(ca_key, "sha256")
	return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)

def register_CA_AWS(CA_cert_path, CA_key_path):
	client = boto3.client('iot')
	response = client.get_registration_code()
	registration_key = response['registrationCode']
	verification_pem = gen_AWS_verification_csr(registrationCode=registration_key)
	verification_cert = sign_CSR_with_CA(verification_csr=verification_pem, CA_cert_path=CA_cert_path, CA_key_path=CA_key_path)
	response = client.register_ca_certificate(

	return response

register_CA_AWS(CA_cert_path='CA_files/zk_ca.crt', CA_key_path='CA_files/zk_ca.key')	

Copy the above lines into a file called and run with the following command:


Generate Zymkey Certificate

The first thing we will do is generate a device certificate using Zymkey’s private key. We will watch as this certificate gets activated on your AWS IoT console automatically on first connect. Make sure that you do not already have a Zymkey certificate registered on your AWS IoT console.

Generate a Certificate Signing Request with Zymkey’s private key using the following command:

openssl req -key nonzymkey.key -new -out zymkey.csr -engine zymkey_ssl -keyform e -subj "/C=US/ST=California/L=Santa Barbara/O=Zymbit/OU=Zymkey/"

Note that the -subj line can be omitted or modified with your own information. If it is omitted, you will be prompted to enter your information on the command line.

Signing the CSR to get a valid Zymkey certificate:

Next we’ll sign this CSR with your Certificate Authority. Save the following script in a file called Make sure to change the -CA and -CAkey paths to point to the private key and certificate file for your certificate authority:

set -e

SCRIPT_NAME=$(basename $0)

[ -z $2 ] && echo "${SCRIPT_NAME} <csr filename> <crt filename>" 1>&2 && exit 1

openssl x509 -req -SHA256 -days 3650 \
  -CA CA_files/zk_ca.crt -CAkey CA_files/zk_ca.key -CAcreateserial \
  -in ${csr} -out ${crt}

Now run the script with the following command, where the first argument is the path to your CSR and the second argument the name you wish to give the signed Zymkey Certificate file.

bash zymkey.csr zymkey.crt

Creating the Lambda function

Now we need to create and register the lambda function that will activate new certificates on your AWS account. This can be done either manually or programatically in Python. We’ll start with the Manual method as it is simpler and more straightforward.



  1. From your AWS console, sign in here, choose the Lambda service.
  2. From the Lambda console, click on the orange Create Function button.
  3. Click the orange Author from Scratch button.
  4. Leave the trigger function empty, it’ll be configured later. Click next.
  5. Choose an appropriate function name and description. We will be using Node.js 4.3 for the runtime environment.
  6. Copy the default JITR code from AWS located here into the in-line console. Make sure that you change the region defined in the code to the appropriate value.
  7. For role, choose to create a custom role.
  8. Choose to create a new IAM role and give it an appropriate name.
  9. Modify the policy document by copying the Role Policy I define a couple sections below, and hit allow
  10. Click the next button and then hit Create function.

The creation of an AWS lambda function through python scripts is a little more intricate than the manual proccess and will need to be divided into numerous parts. The creation of the lambda function will be divided into these steps:

  1. Creating a new IAM role for the lambda function.
  2. Creating a new Policy for this IAM role.
  3. Attaching the Policy to the IAM role.
  4. Create the lambda function, attaching the IAM role to this function.

Creating IAM Role for the Lambda function

The first thing we need to do is create an IAM Role. This role gives the Lambda function permission to execute and perform its registration of new certificates.

Creating this IAM role furthermore requires you to specify 2 things:

IAM Trust Document: This is a document that details what AWS resources are allowed to assume this IAM role. Below is an example IAM trust document we will use that allows lambda services to assume this JITR role. Save the document in a file called trust_document.txt.

  "Version": "2012-10-17",
  "Statement": [
      "Effect": "Allow",
      "Principal": {
        "Service": ""
      "Action": "sts:AssumeRole"

Role Policy: This is a document describing what actions an IAM role may take. This policy is created and then attached to whatever role you wish. The following policy will allow the JITR role to create AWS logs as well as register/activate new certificates and attach policies to them. Save the document in a file called policy_document.txt.


Finally, this is the python code that will take care of the creation of the IAM role. The first thing it does is create an IAM role with the specified Trust settings. Then it creates an IAM policy with the aforementioned policy settings. Finally it will attach the policy to the role. This role will be used by the JITR lambda. By default it’ll read the two documents trust_document.txt and policy_document.txt from the same directory that the code is executed in. So save the python script in a file called in the same directory as these files, or modify the path to these files in the code. They can be either relative or absolute

import boto3
iam_client = boto3.client('IAM')
with open('trust_document.txt') as trust_role:
	trust_document =

with open('policy_document.txt') as policy:
	policy_document =

# Creating the IAM role with the specified Trust 
create_role_response = iam_client.create_role(
	Description='AWS Role given to the JITR lambda'

# Creating the IAM policy with the specified P
create_policy_resopnse = iam_client.create_policy(
	Description'Policy that allows JITR lambda to execute actions.'
# Attaching the IAM policy to the IAM role	
attach_response = iam_client.attach_role_policy(

After saving the code in a file called, you can execute by running the following command:

Creating Lambda function

The lambda function will then be created with the following script. The code for the lambda function will be in a zipped file named Download the lambda code here and make sure to modify the region-name in the code to your approrpiate region.

Next, zip up the code in a file called and keep it in the same directory as the following python script.

#Download the zip file with the lambda code and save it in the same directory as this script.
with open('', mode='rb') as file:   
	filecontent =

lambda_client = boto3.client('lambda')
create_lambda_response = lambda_client.create_function(
	#By appending this script unto you do not need to find the role_ARN, as it will already be stored in this object.
	Handler='index.handler	',
		'ZipFile': filecontent
	Description='Lambda function for Just-in-time-Registration',

Note that this script requires the Role ARN for the IAM role you just created. If you append this script to the file, it will already be included in the response from attaching the policy to the jitr_role, and you won’t have to do anything.

#Do not copy and run these lines, this is just showing where the role_ARN was taken from
attach_response = iam_client.attach_role_policy(
role_ARN = attach_response['Arn']

Otherwise, if you don’t wish to append the following script to the, here is how you can find the appropriate role_arn:

  1. From your AWS Console choose the IAM service.
  2. On the left hand bar, click on Roles
  3. Select the role titled jitr_role.
  4. Copy the section following Role ARN
  5. In the following python script, replace: Role=attach_resopnse[‘Arn’] with Role=the_correct_role_arn

Save the script in an appropriate location, and run it.

Creating the AWS IoT Rule

Finally we will be setting up an AWS IoT Rule to forward all requests with an unregistered certificate to the lambda function that will activate the certificate.

  1. From your AWS Console, click on the AWS IoT service.
  2. On the left hand side, select Act and then click the blue Create button.
  3. Give it an appropriate Name and Description.
  4. Using SQL version 2016-03-23 use the following settings:
Attribute: *
Topic Filter: $aws/events/certificates/registered/<caCertificateID> 

Find your caCertificateARN under Security, Certificate Authroities, it should look like this:


Your certificateID is the part following cert/
So the correct topic filter for this CA would be the following:

Topic Filter: $aws/events/certificates/registered/c47db8c0a8c075fee5313bc7cf89a7e77eb9350907598d37843daf556edafbdd

Now click on the add action button, and choose to “Invoke a Lambda function passing the message data.” Choose your JITR lambda function and select “Add action.” Finally click "Create rule."

Testing JITR with TLS Connection

You can now test your JITR setup by doing a TLS connection with your AWS IoT endpoint and presenting your Zymkey device certificate.


The first thing to do is to look for your AWS endpoint.

  1. From the AWS IoT console screen, click on the gear that says Settings on the left hand bar.

  2. Copy the link in the Custom Endpoint box.

  3. Now on the left hand bar, click on the Test option.

  4. Under Subscribe and Subscription Topic, type in hello/world.

  5. Test your TLS connection with the following CURL command pointing to your CA file and your Zymkey certificate:

  6. Use CURL to test your TLS connection, pointing to your CA file:

    	curl --tlsv1.2 --cacert zk_ca.pem --cert zymkey.crt --key nonzymkey.key --engine zymkey_ssl --key-type ENG -v -X POST -d "{ \"hello\": \"world\"}" ""

The TLS connection should go through, and you should see something like this in your command line:

On first connection TLS connection, the TLS handshake should finish, but you should receive empty response from server:

On second TLS connection, you will see that the certifiacte is registered and you will get a correct response:

You can also check under your certifiactes in your AWS IoT console to see that the new certifciacte is indeed registered. Getting a non-empty response indicates that the certificate is indeed registerd on you AWS account. However, even though the certificate is activated, it does not have a valid policy granting it permission to publish data to your AWS IoT account, which results in the forbidden response. Fortunately you can always modify the jitr lambda function to attach an appropriate policy to the certificate upon registration/activation if you wish to do so.