Gestion des certificats IoT en mode auto-service sans serveur - Partie 1

Ce fichier a ete traduit automatiquement par IA, des erreurs peuvent survenir
J'ai travaillé sur plusieurs solutions IoT différentes au fil des années. Une chose qu'elles ont toutes en commun est le besoin de certificats de confiance qui peuvent être utilisés à la fois pour établir la connexion, mais aussi les identités des appareils. Les appareils et les serveurs doivent se faire confiance pour établir une communication sécurisée, garantir l'intégrité des données et prévenir les attaques malveillantes. Une partie importante de cette confiance est l'Infrastructure à Clés Publiques (PKI), où les certificats et les Autorités de Certification (AC) jouent des rôles vitaux, il est également nécessaire de gérer une grande quantité de certificats de manière simple.
Dans certains projets, nous avons développé notre propre solution PKI interne, ce qui entraîne une complexité et des exigences de sécurité. Dans d'autres, nous avons utilisé des solutions SaaS, comme DigiCert IoT Trust Manager, AWS IoT Core et Amazon Private CA.
Cependant, lorsqu'il s'agit de développement, et parfois même d'environnements de test, il n'était pas rare que nous utilisions des certificats auto-signés, avec un portail auto-service simple pour créer différents certificats.
Dans cet article, qui est la première partie d'une série de deux, nous allons présenter quelques bases autour des certificats et du PKI. Nous allons également commencer à créer les bases d'une API sans serveur qui peut être utilisée pour créer un portail auto-service pour la génération de certificats.
AVERTISSEMENT
La solution que je construis dans cette série d'articles n'est PAS adaptée à une configuration de production. Ceci est uniquement destiné aux environnements de développement et à des fins d'apprentissage !
Dans un environnement de production, nous avons besoin de :
Pour ces besoins, les services gérés comme AWS Private CA, DigiCert IoT Trust Manager et Let’s Encrypt sont idéaux.
Dans cette première version de l'API, nous n'utilisons aucune forme d'autorisation ! Cela sera ajouté dans la prochaine partie !
Pourquoi construire une API auto-service ?
Comme dit, dans un système IoT, des milliers d'appareils doivent interagir avec des serveurs, des courtiers IoT et entre eux. Chacune de ces interactions doit être sécurisée, ce qui signifie :
- Les serveurs doivent authentifier les appareils pour s'assurer qu'ils sont légitimes.
- Les appareils doivent authentifier les serveurs pour s'assurer qu'ils se connectent à une source de confiance.
Les certificats sont donc délivrés aux serveurs et aux appareils, créant une chaîne de confiance ancrée à une AC racine.
Une API auto-service pour la gestion des certificats permet :
- L'automatisation : Les appareils et les serveurs peuvent demander et renouveler les certificats par programme.
- La scalabilité : Au fur et à mesure que notre environnement IoT se développe, l'API peut gérer la demande croissante de certificats.
- L'apprentissage et les tests : Avant d'adopter un service géré, la construction de votre propre système de certificats vous aide à comprendre le fonctionnement du PKI.
Aperçu des certificats, des AC et de la confiance
Qu'est-ce qu'une Autorité de Certification (AC) ?
Une AC est une entité de confiance responsable de la délivrance des certificats numériques. Ces certificats lient une clé publique à une identité (par exemple, un serveur, un appareil ou un utilisateur), permettant la confiance entre les entités.
Les AC sont structurées dans une hiérarchie :
AC racine
- L'ancre de confiance ultime.
- Auto-signée et hautement sécurisée.
- Ne doit jamais être utilisée pour délivrer directement des certificats d'entités finales.
AC intermédiaire
- Livrée et signée par l'AC racine.
- Délègue la responsabilité de la délivrance des certificats aux entités finales (par exemple, les appareils et les serveurs).
- Limite l'exposition de l'AC racine.
Certificats d'entités finales, feuilles
- Certificats pour les serveurs, les appareils IoT ou les utilisateurs.
- Livrés par une AC intermédiaire et utilisés dans la communication client-serveur ou l'authentification mutuelle.
Chaînes de certificats et confiance
Une chaîne de certificats est une séquence de certificats, où chaque certificat de la chaîne est signé par le certificat suivant. Elle représente la relation hiérarchique entre un certificat et son émetteur.

Une chaîne peut consister en un ou plusieurs certificats intermédiaires. Dans l'image ci-dessus, la chaîne est illustrée avec deux AC intermédiaires.
Pendant une négociation TLS
Simplifiée, la négociation serait :
- Le serveur présente son certificat au client.
- Le client valide le certificat du serveur en retraçant la chaîne jusqu'à une AC racine de confiance dans son magasin de certificats. Lorsque vous utilisez une AC racine auto-signée, vous devez vous assurer que le paquet est présent dans le magasin de confiance ou inclus dans la tentative de connexion.
Dans un scénario où l'authentification mutuelle est requise, le serveur effectue le même processus pour le certificat du client.
Confiance à travers plusieurs AC intermédiaires
Dans une configuration IoT, il est courant d'avoir une AC intermédiaire délivrant des certificats de serveur (par exemple, pour les courtiers IoT), et une autre AC intermédiaire délivrant des certificats de client (par exemple, pour les appareils). Pour qu'un certificat de client (signé par une AC intermédiaire) fasse confiance à un certificat de serveur (signé par une autre AC intermédiaire), les deux doivent :
Faire partie de la même hiérarchie de confiance.
- Les deux AC intermédiaires doivent être signées par la même AC racine.
- L'AC racine est l'ancre de confiance commune.
Être validés contre la chaîne.
- Le client vérifie la chaîne de certificats du serveur, la retraçant jusqu'à l'AC racine.
- Le serveur vérifie la chaîne de certificats du client de manière similaire.
Cette configuration assure la scalabilité et la séparation des responsabilités. L'AC intermédiaire des serveurs se concentre sur les serveurs et les courtiers. L'AC intermédiaire des appareils se concentre sur les appareils IoT.
Mise en œuvre
Maintenant, commençons à mettre en œuvre cette API auto-service. Nous allons la construire entièrement sans serveur en utilisant Amazon API Gateway et des fonctions Lambda, avec le stockage des certificats dans S3 et Certificate Manager. Cette première partie de la série de blogs ne créera que la première partie très basique de l'API, que nous étendrons dans la deuxième partie. Vous pouvez trouver tout le code source sur Serverless-Handbook Self Service IoT Certificate management

Nous allons configurer un API Gateway avec trois points de terminaison pour créer notre AC racine, notre AC intermédiaire et notre certificat de serveur. Tout sera stocké dans un bucket S3, et le certificat de serveur sera également importé dans Certificate Manager.
API REST
Voici d'abord un aperçu de l'API REST et de sa structure.
| Point de terminaison | Méthode | Description |
|---|---|---|
/certificates/root | POST | Créer une nouvelle AC racine. |
/certificates/intermediate | POST | Créer une nouvelle AC intermédiaire. |
/certificates/server | POST | Créer un nouveau certificat de serveur. |
J'ai décidé d'utiliser des chemins séparés (par exemple, /certificates/root, /certificates/intermediate, /certificates/server) plutôt qu'un seul point de terminaison avec un paramètre de type (par exemple, /certificates avec type en entrée) car je pense que cela s'aligne mieux avec les principes REST et améliore la lisibilité et l'utilisabilité de l'API.
Il est plus facile d'étendre l'API avec un nouveau type de certificat (par exemple, appareil), car nous pouvons créer un nouveau chemin comme /certificates/device sans impacter les chemins existants. Les chemins séparés segmentent naturellement les ressources, réduisant le besoin de filtrage côté client. Je pense que récupérer toutes les AC racines est plus simple avec GET /certificates/root que GET /certificates?type=root.
Infrastructure commune
Tout d'abord, déployons l'infrastructure commune, dans ce cas, il s'agit simplement du bucket S3, nous ajouterons plus de choses dans ce modèle plus tard.
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Infrastructure commune pour la gestion des certificats auto-service
Parameters:
ApplicationName:
Type: String
Description: Nom de l'application propriétaire
Default: image-moderation
Resources:
StorageBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Properties:
BucketName: !Sub ${ApplicationName}-storage-bucket
Outputs:
StorageBucketName:
Description: Le nom du bucket de certificats
Value: !Ref StorageBucket
Export:
Name: !Sub ${AWS::StackName}:certificate-bucket-nameEnsuite, nous pouvons configurer les points de terminaison en utilisant SAM et AWS::Serverless::Api et les trois fonctions Lambda qui soutiennent l'API.
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Créer l'API pour la gestion des certificats auto-service
Parameters:
ApplicationName:
Type: String
Description: Nom de l'application propriétaire
CommonInfraStackName:
Type: String
Description: Le nom de la pile commune qui contient le bus EventBridge et plus encore
Globals:
Function:
Timeout: 30
MemorySize: 2048
Runtime: python3.12
Environment:
Variables:
CERTIFICATE_BUCKET_NAME:
Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
Resources:
LambdaGenerateRootCA:
Type: AWS::Serverless::Function
Properties:
CodeUri: Lambda/API/GenerateRootCA
Handler: handler.handler
Policies:
- S3FullAccessPolicy:
BucketName:
Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
Events:
CreateRootCAApi:
Type: Api
Properties:
Path: /certificates/root
Method: post
RestApiId: !Ref GenerateCertificatesApi
LambdaGenerateIntermediateCA:
Type: AWS::Serverless::Function
Properties:
CodeUri: Lambda/API/GenerateIntermediateCA
Handler: handler.handler
Policies:
- S3FullAccessPolicy:
BucketName:
Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
Events:
CreateIntermediateCAApi:
Type: Api
Properties:
Path: /certificates/intermediate
Method: post
RestApiId: !Ref GenerateCertificatesApi
LambdaGenerateDeviceCertificate:
Type: AWS::Serverless::Function
Properties:
CodeUri: Lambda/API/GenerateDeviceCert
Handler: handler.handler
Policies:
- S3FullAccessPolicy:
BucketName:
Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
LambdaGenerateServerCertificate:
Type: AWS::Serverless::Function
Properties:
CodeUri: Lambda/API/GenerateServerCert
Handler: handler.handler
Policies:
- S3FullAccessPolicy:
BucketName:
Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
- Version: "2012-10-17"
Statement:
Action:
- acm:*
Effect: Allow
Resource: "*"
Events:
CreateServerCAApi:
Type: Api
Properties:
Path: /certificates/server
Method: post
RestApiId: !Ref GenerateCertificatesApi
GenerateCertificatesApi:
Type: AWS::Serverless::Api
Properties:
Description: API pour créer et gérer les certificats
Name: !Sub ${ApplicationName}-api
StageName: prod
OpenApiVersion: '3.0.1'
AlwaysDeploy: true
EndpointConfiguration: REGIONALNos fonctions Lambda sont en Python et nous utilisons la bibliothèque cryptography pour créer les certificats, voici l'implémentation pour créer un certificat de serveur. Pour une implémentation complète, visitez Serverless-Handbook Self Service IoT Certificate management.
import boto3
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization, hashes
import datetime
import os
import json
s3_client = boto3.client("s3")
acm_client = boto3.client("acm")
def create_server_certificate(
intermediate_private_key_pem,
intermediate_cert_pem,
fqdn,
country,
state,
organization,
validity_days,
):
# Charger la clé privée et le certificat de l'AC intermédiaire
intermediate_private_key = serialization.load_pem_private_key(
intermediate_private_key_pem, password=None
)
intermediate_cert = x509.load_pem_x509_certificate(intermediate_cert_pem)
# Générer une clé privée pour le certificat de serveur
server_private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
server_private_key_pem = server_private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, country),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
x509.NameAttribute(NameOID.COMMON_NAME, fqdn),
]
)
server_certificate = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(intermediate_cert.subject) # Signé par l'AC intermédiaire
.public_key(server_private_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.now(datetime.timezone.utc))
.not_valid_after(
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=validity_days)
)
.add_extension(
x509.SubjectAlternativeName([x509.DNSName(fqdn)]),
critical=False,
)
.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
)
.add_extension(
x509.KeyUsage(
digital_signature=True,
key_encipherment=True,
key_cert_sign=False,
crl_sign=False,
content_commitment=False,
data_encipherment=False,
encipher_only=False,
decipher_only=False,
key_agreement=False,
),
critical=True,
)
.sign(intermediate_private_key, hashes.SHA256())
)
server_cert_pem = server_certificate.public_bytes(serialization.Encoding.PEM)
return server_private_key_pem, server_cert_pem
def handler(event, context):
body = json.loads(event["body"])
bucket_name = os.environ.get("CERTIFICATE_BUCKET_NAME")
# Récupérer la clé privée et le certificat de l'AC intermédiaire depuis S3
intermediate_private_key = s3_client.get_object(
Bucket=bucket_name, Key="intermediate_ca/private_key.pem"
)["Body"].read()
intermediate_cert = s3_client.get_object(
Bucket=bucket_name, Key="intermediate_ca/certificate.pem"
)["Body"].read()
# Récupérer la chaîne de certificats depuis S3
cert_chain_pem = s3_client.get_object(
Bucket=bucket_name, Key="intermediate_ca/certificate_chain.pem"
)["Body"].read()
# Créer le certificat de serveur
server_private_key_pem, server_cert_pem = create_server_certificate(
intermediate_private_key,
intermediate_cert,
body["fqdn"],
body["country"],
body["state"],
body["organization"],
body["validity_days"],
)
s3_folder = f"server_certificates/{body["fqdn"]}/"
# Télécharger le certificat de serveur et la clé privée sur S3
s3_client.put_object(
Bucket=bucket_name,
Key=f"{s3_folder}private_key.pem",
Body=server_private_key_pem,
)
s3_client.put_object(
Bucket=bucket_name, Key=f"{s3_folder}certificate.pem", Body=server_cert_pem
)
# Importer le certificat dans ACM
response = acm_client.import_certificate(
Certificate=server_cert_pem,
PrivateKey=server_private_key_pem,
CertificateChain=cert_chain_pem,
)
return {
"statusCode": 200,
"body": json.dumps(
{
"message": "Le certificat de serveur a été créé, téléchargé sur S3 et importé dans ACM.",
"fqdn": body["fqdn"],
"s3_folder": s3_folder,
"acm_certificate_arn": response["CertificateArn"],
}
),
}Conclusion
Cet article a couvert tout, des chaînes de certificats à la validation de la confiance dans les systèmes IoT. La construction de votre propre API auto-service est un bon moyen d'apprendre sur les certificats et le PKI. En production, cependant, optez toujours pour des solutions gérées qui offrent automatisation, conformité et scalabilité.
Les certificats sont le fondement de la sécurité IoT
Les certificats et les AC assurent une communication sécurisée et authentifiée dans les écosystèmes IoT.
Le rôle des AC intermédiaires
La délégation de la délivrance des certificats aux AC intermédiaires améliore la scalabilité et limite l'exposition.
La confiance est construite sur des hiérarchies
La confiance entre les appareils et les serveurs repose sur des AC racines partagées et des chaînes de certificats validées.
Construisez pour apprendre, utilisez des services gérés pour la production
Bien que cette API soit un excellent outil d'apprentissage, des services comme AWS Private CA ou Let’s Encrypt sont mieux adaptés à la production.
Pour obtenir le code source complet et le déployer vous-même, visitez Serverless-Handbook Self Service IoT Certificate management
Derniers mots
N'oubliez pas de me suivre sur LinkedIn et X pour plus de contenu, et lisez le reste de mes Blogs
Comme le dit Werner ! Maintenant, allez construire !