Custom authentication with X.509 client certificates
When connecting devices to AWS IoT Core, you have multiple authentication types available. You can use X.509 client certificates that can be used to authenticate client and device connections, or define custom authorizers to manage your own client authentication and authorization logic. This topic covers how to use custom authentication with X.509 client certificates.
Using custom authentication with X.509 certificates can be helpful if you've already authenticated your devices using X.509 certificates and want to perform additional validation and custom authorization. For example, if you store your devices' data such as their serial numbers in the X.509 client certificate, after AWS IoT Core authenticated the X.509 client certificate, you can use a custom authorizer to identify specific devices based on the information stored in the certificate's CommonName field. Using custom authentication with X.509 certificates can enhance your device security management when connecting devices to AWS IoT Core and provides more flexibility to manage the authentication and authorization logic. AWS IoT Core supports custom authentication with X.509 certificates using the X.509 certificate and custom authorizer authentication type, which works with both the MQTT protocol and the HTTPS protocol. For more information about the authentication types and application protocols that AWS IoT Core device endpoints support, see Device communication protocols.
Note
Custom authentication with X.509 client certificates is not supported in the AWS GovCloud (US) Regions.
Important
You must use an endpoint created using domain configurations. In
addition, clients must provide the Server Name
Indication (SNI)
The process to authenticate devices using custom authentication with X.509 client certificates consists of the following steps.
Step 1: Register your X.509 client certificates with AWS IoT Core
If you haven't done this already, register and activate your X.509 client certificates with AWS IoT Core. Otherwise, skip to the next step.
To register and activate your client certificates with AWS IoT Core, follow the steps:
-
If you create client certificates directly with AWS IoT. These client certificates will be automatically registered with AWS IoT Core.
-
If you create your own client certificates, follow these instructions to register them with AWS IoT Core.
-
To activate your client certificates, follow these instructions.
Step 2: Create a Lambda function
AWS IoT Core uses custom authorizers to implement custom authentication and authorization schemes. A custom authorizer is associated with a Lambda function that determines whether a device is authenticated and what operations the device is allowed to perform. When a device connects to AWS IoT Core, AWS IoT Core retrieves the authorizer details including authorizer name and associated Lambda function, and invokes the Lambda function. The Lambda function receives an event that contains a JSON object with the device's X.509 client certificate data. Your Lambda function uses this event JSON object to evaluate the authentication request, decide the actions to take, and send a response back.
Lambda function event example
The following example JSON object contains all possible fields that can be included. The actual JSON object will only contain fields relevant to the specific connection request.
{ "token": "aToken", "signatureVerified": true, "protocols": [ "tls", "mqtt" ], "protocolData": { "tls": { "serverName": "serverName", "x509CertificatePem": "x509CertificatePem", "principalId": "principalId" }, "mqtt": { "clientId": "myClientId", "username": "myUserName", "password": "myPassword" } }, "connectionMetadata": { "id": "UUID" } }
signatureVerified
-
A Boolean value that indicates whether the token signature configured in the authorizer is verified or not before invoking the authorizer's Lambda function. If the authorizer is configured to disable token signing, this field will be false.
protocols
-
An array that contains the protocols to expect for the request.
protocolData
-
An object that contains information of the protocols used in the connection. It provides protocol-specific details that can be useful for authentication, authorization, and more.
tls
- This object holds information related to the TLS (Transport Layer Security) protocol.-
serverName
- The Server Name Indication (SNI)hostname string. AWS IoT Core requires devices to send the SNI extension to the Transport Layer Security (TLS) protocol and provide the complete endpoint address in the host_name
field. -
x509CertificatePem
- The X.509 certificate in PEM format, which is used for client authentication in the TLS connection. -
principalId
- The principal identifier associated with the client in the TLS connection.
mqtt
- This object holds information related to the TLS (Transport Layer Security) protocol.-
clientId
- A string only needs to be included in the event that the device sends this value. -
username
- The user name provided in the MQTT Connect packet. -
password
- The password provided in the MQTT Connect packet.
-
connectionMetadata
-
Metadata of the connection.
id
- The connection ID, which you can use for logging and troubleshooting.
Note
In this event JSON object, x509CertificatePem
and
principalId
are two new fields in the request. The
value of principalId
is the same as the value of
certificateId
. For more information, see Certificate.
Lambda function response example
The Lambda function should use information from the event JSON object to authenticate the incoming connection and decide what actions are permitted in the connection.
The following JSON object contains an example response that your Lambda function can send.
{ "isAuthenticated": true, "principalId": "xxxxxxxx", "disconnectAfterInSeconds": 86400, "refreshAfterInSeconds": 300, "policyDocuments": [ { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "iot:Publish", "Resource": "arn:aws:iot:us-east-1:123456789012:topic/customauthtesting" } ] } ] }
In this example, this function should send a response that contains the following values.
isAuthenticated
-
A Boolean value that indicates whether the request is authenticated.
principalId
-
An alphanumeric string that acts as an identifier for the token sent by the custom authorization request. The value must be an alphanumeric string with at least one, and no more than 128, characters. It identifies the connection in logs. The value of
principalId
must be the same as the value ofprincipalId
in the event JSON object (i.e. certificateId of the X.509 certificate). policyDocuments
-
A list of JSON-formatted AWS IoT Core policy documents. The value is optional and supports thing policy variables and certificate policy variables. The maximum number of policy documents is 10. Each policy document can contain a maximum of 2,048 characters. If you have multiple policies attached to your client certificate and the Lambda function, the permission is a collection of all policies. For more information about creating AWS IoT Core policies, see Policies.
disconnectAfterInSeconds
-
An integer that specifies the maximum duration (in seconds) of the connection to the AWS IoT Core gateway. The minimum value is 300 seconds, and the maximum value is 86,400 seconds.
disconnectAfterInSeconds
is for the lifetime of a connection and doesn't get refreshed on consecutive policy refreshes. refreshAfterInSeconds
-
An integer that specifies the interval between policy refreshes. When this interval passes, AWS IoT Core invokes the Lambda function to allow for policy refreshes. The minimum value is 300 seconds, and the maximum value is 86,400 seconds.
Example Lambda function
The following is a sample Node.js Lambda function. The function examines the client's X.509 certificate and extracts relevant information such as the serial number, fingerprint, and subject name. If the extracted information matches the expected values, the client is granted access to connect. This mechanism ensures that only authorized clients with valid certificates can establish a connection.
const crypto = require('crypto'); exports.handler = async (event) => { // Extract the certificate PEM from the event const certPem = event.protocolData.tls.x509CertificatePem; // Parse the certificate using Node's crypto module const cert = new crypto.X509Certificate(certPem); var effect = "Deny"; // Allow permissions only for a particular certificate serial, fingerprint, and subject if (cert.serialNumber === "7F8D2E4B9C1A5036DE8F7C4B2A91E5D80463BC9A1257" // This is a random serial && cert.fingerprint === "F2:9A:C4:1D:B5:E7:08:3F:6B:D0:4E:92:A7:C1:5B:8D:16:0F:E3:7A" // This is a random fingerprint && cert.subject === "allow.example.com") { effect = "Allow"; } return generateAuthResponse(event.protocolData.tls.principalId, effect); }; // Helper function to generate the authorization response. function generateAuthResponse(principalId, effect) { const authResponse = { isAuthenticated: true, principalId, disconnectAfterInSeconds: 3600, refreshAfterInSeconds: 300, policyDocuments: [ { Version: "2012-10-17", Statement: [ { Action: ["iot:Connect"], Effect: effect, Resource: [ "arn:aws:iot:
us-east-1
:123456789012
:client/myClientName
" ] }, { Action: ["iot:Publish"], Effect: effect, Resource: [ "arn:aws:iot:us-east-1
:123456789012
:topic/telemetry/myClientName
" ] }, { Action: ["iot:Subscribe"], Effect: effect, Resource: [ "arn:aws:iot:us-east-1
:123456789012
:topicfilter/telemetry/myClientName
" ] }, { Action: ["iot:Receive"], Effect: effect, Resource: [ "arn:aws:iot:us-east-1
:123456789012
:topic/telemetry/myClientName
" ] } ] } ] }; return authResponse; }
The preceding Lambda function returns the following JSON when it receives
a certificate with the expected serial, fingerprint, and subject. The value
of x509CertificatePem
will be the client certificate provided in the TLS handshake. For
more information, see Defining your Lambda function.
{ "isAuthenticated": true, "principalId": "principalId in the event JSON object", "policyDocuments": [ { "Version": "2012-10-17", "Statement": [ { "Action": "iot:Connect", "Effect": "Allow", "Resource": "arn:aws:iot:
us-east-1
:123456789012
:client/myClientName
" }, { "Action": "iot:Publish", "Effect": "Allow", "Resource": "arn:aws:iot:us-east-1
:123456789012
:topic/telemetry/myClientName
" }, { "Action": "iot:Subscribe", "Effect": "Allow", "Resource": "arn:aws:iot:us-east-1
:123456789012
:topicfilter/telemetry/myClientName
" }, { "Action": "iot:Receive", "Effect": "Allow", "Resource": "arn:aws:iot:us-east-1
:123456789012
:topic/telemetry/myClientName
" } ] } ], "disconnectAfterInSeconds": 3600, "refreshAfterInSeconds": 300 }
Step 3: Create a custom authorizer
After you define the Lambda function, create a custom authorizer to manage your own client authentication and authorization logic. You can follow the detailed instructions in Step 3: Create a customer authorizer resource and its authorization. For more information, see Creating an authorizer.
In the process of creating the custom authorizer, you must grant AWS IoT permission to invoke the Lambda function after it's created. For detailed instructions, see Authorizing AWS IoT to invoke your Lambda function.
Step 4: Set authentication type and application protocol in a domain configuration
To authenticate devices using custom authentication with X.509 client
certificates, you must set the authentication type and application protocol in a
domain configuration, and you must send the SNI extension. The value of
authenticationType
must be CUSTOM_AUTH_X509
, and
the value of applicationProtocol
can either be
SECURE_MQTT
or HTTPS
.
Set authentication type and application protocol in domain configuration (CLI)
If you don't have a domain configuration, use the create-domain-configuration command to
create one. The value of authenticationType
must be
CUSTOM_AUTH_X509
, and the value of
applicationProtocol
can either be SECURE_MQTT
or HTTPS
.
aws iot create-domain-configuration \ --domain-configuration-name
domainConfigurationName
\ --authentication-typeCUSTOM_AUTH_X509
\ --application-protocolSECURE_MQTT
\ --authorizer-config '{ "defaultAuthorizerName":my-custom-authorizer
}'
If you already have a domain configuration, use the update-domain-configuration command
update authenticationType
and applicationProtocol
if needed. Note that you can't change the authentication type or protocol on
the default endpoint (iot:Data-ATS
).
aws iot update-domain-configuration \ --domain-configuration-name
domainConfigurationName
\ --authentication-typeCUSTOM_AUTH_X509
\ --application-protocolSECURE_MQTT
\ --authorizer-config '{ "defaultAuthorizerName":my-custom-authorizer
}'
domain-configuration-name
-
The name of the domain configuration.
authentication-type
-
The authentication type of the domain configuration. For more information, see choosing an authentication type.
application-protocol
-
The application protocol which devices use to communicate with AWS IoT Core. For more information, see choosing an application protocol.
--authorizer-config
-
An object that specifies the authorizer configuration in a domain configuration.
defaultAuthorizerName
-
The name of the authorizer for a domain configuration.
For more information, see CreateDomainConfiguration and UpdateDomainConfiguration from the AWS IoT API Reference. For more information about domain configuration, see Domain configurations.