Aller au contenu

Module certs

Le module certs fournit une gestion générique de slots de certificats aux scripts Lua : génération de clé, création de CSR (Certificate Signing Request), stockage atomique des certificats émis, et inspection de leur validité.

Il est agnostique au protocole : les échanges d'enrollment (HTTP, CMP, EST, etc.) sont réalisés en Lua, tandis que le module se charge de la cryptographie et du stockage. La clé privée ne quitte jamais les fichiers du module (elle n'est jamais exposée à Lua).

Ce module fait partie des modules chargés par défaut : il est disponible sans aucune configuration.

Import

Pour importer le module en Lua :

local certs = require("certs")

Aucun suffixe d'instance n'est nécessaire : le module est toujours enregistré sous le nom certs.

Configuration

Le module ne nécessite aucune configuration. Chaque appel identifie son slot à partir de la table d'options passée en argument.

Un slot est un jeu de fichiers nommé, stocké dans un dossier :

Fichier Contenu
<name>.key Clé privée (jamais exposée à Lua), permissions 0600
<name>.crt Certificat émis (PEM)
<name>-ca.pem Chaine CA (optionnelle)
<name>.json Métadonnées (marqueur de slot complet)
  • Le dossier par défaut est /etc/alemca/certs, surchargeable via le champ dir de la table d'options.
  • Le nom du slot doit correspondre à ^[A-Za-z0-9._-]+$.

API Lua

Toutes les fonctions prennent une table d'options en argument et retournent (résultat, err), ou err vaut nil en cas de succès.

Liste des fonctions

Fonction Signature Lua Rôle
csr csr_pem, err = certs.csr(opts) Génère (ou réutilise) une clé et retourne un CSR.
store ok, err = certs.store(opts) Stocke un certificat émis dans le slot.
info info, err = certs.info(opts) Retourne les infos du slot (validité, CN...).
delete ok, err = certs.delete(opts) Supprime les fichiers du slot.
Champs certs.name, certs.type Métadonnées du module.

certs.csr(opts)

Génère la clé privée du slot (si absente) et retourne un CSR au format PEM. Si la clé existe déjà, elle est réutilisée.

  • Options :
  • name (string, requis) : nom du slot.
  • cn (string, requis) : Common Name du sujet.
  • dir (string, optionnel) : dossier du slot (défaut: /etc/alemca/certs).
  • type (string, optionnel) : type de clé, "ecdsa-p256" (défaut) ou "rsa-2048".
  • san (table, optionnel) : liste de noms DNS pour les Subject Alternative Names.
  • Retour : le CSR au format PEM, ou nil, "msg" en cas d'échec.
local certs = require("certs")

local csr_pem, err = certs.csr({
    name = "gateway",
    cn = "gateway-001.alemca.io",
    type = "ecdsa-p256",
    san = { "gateway-001.alemca.io", "gw001.local" },
})
if not csr_pem then
    printError("Génération CSR échouée : " .. err)
    return
end
-- csr_pem est ensuite envoyé à l'autorité de certification (via http, etc.)

certs.store(opts)

Persiste un certificat émis dans le slot, apres avoir vérifié qu'il correspond bien à la clé privée du slot. Les fichiers sont écrits de manière atomique ; le fichier .json est écrit en dernier (marqueur de complétude).

  • Options :
  • name (string, requis) : nom du slot.
  • cert (string, requis) : certificat émis au format PEM.
  • ca (string, optionnel) : chaine CA au format PEM.
  • meta (table, optionnel) : métadonnées libres (clé → valeur) à conserver.
  • dir (string, optionnel) : dossier du slot.
  • Retour : true, nil en cas de succès ; nil, "msg" si le certificat ne correspond pas à la clé ou en cas d'erreur.
local ok, err = certs.store({
    name = "gateway",
    cert = cert_pem,   -- reçu de l'autorité de certification
    ca = ca_pem,
    meta = { issuer = "alemca-ca", profile = "gateway" },
})
if not ok then
    printError("Stockage certificat échoué : " .. err)
end

certs.info(opts)

Retourne les informations d'un slot complet (clé + certificat + métadonnées présents).

  • Options :
  • name (string, requis) : nom du slot.
  • dir (string, optionnel) : dossier du slot.
  • Retour : une table, ou nil, "raison" si le slot est incomplet.
{
  key_file = "/etc/alemca/certs/gateway.key",
  cert_file = "/etc/alemca/certs/gateway.crt",
  ca_file = "/etc/alemca/certs/gateway-ca.pem", -- présent seulement si stocké
  not_after = 1776247200,    -- expiration (horodatage Unix)
  cn = "gateway-001.alemca.io",
  meta = { issuer = "alemca-ca", profile = "gateway" }
}

Le champ not_after permet de décider d'un renouvellement avant expiration.

certs.delete(opts)

Supprime tous les fichiers du slot (clé, certificat, CA, métadonnées). Les fichiers absents sont ignorés.

  • Options :
  • name (string, requis) : nom du slot.
  • dir (string, optionnel) : dossier du slot.
  • Retour : true, nil en cas de succès.

Exemple complet : enrollment et renouvellement

local certs = require("certs")
local http = require("http1")

local SLOT = "gateway"
local RENEW_BEFORE = 30 * 24 * 3600  -- renouveler 30 jours avant expiration

-- 1) Vérifier si un renouvellement est nécessaire
local info = certs.info({ name = SLOT })
if info and (info.not_after - os.time()) > RENEW_BEFORE then
    printInfo("Certificat encore valide, rien à faire")
    return
end

-- 2) Générer un CSR (la clé est créée/réutilisée par le module)
local csr_pem, err = certs.csr({
    name = SLOT,
    cn = "gateway-001.alemca.io",
    type = "ecdsa-p256",
})
if not csr_pem then
    printError("CSR échoué : " .. err)
    return
end

-- 3) Envoyer le CSR à l'autorité de certification (flow HTTP en Lua)
local resp, err = http.post("https://ca.example.com/enroll", csr_pem)
if not resp then
    printError("Enrollment échoué : " .. tostring(err))
    return
end

-- 4) Stocker le certificat émis
local ok, err = certs.store({
    name = SLOT,
    cert = resp,  -- certificat PEM retourné par la CA
})
if not ok then
    printError("Stockage échoué : " .. err)
end

Champs Lua associés

  • certs.name : Retourne le nom de l'instance du module ("certs").
  • certs.type : Retourne le type du module ("certs").