Skip to content
CNMLCertificat Numérique de Métrologie Légale
← Docs

Guides

Signing a certificate

1 min read · Guides

Signing a certificate

This guide covers the CNML signing flow: key generation, certificate creation, and XMLDSig signing.

Key generation

CNML signing keys are ECDSA P-256 keypairs generated in the browser via WebCrypto. The private key is encrypted at rest with AES-256-GCM, using a PBKDF2-derived key from your passphrase.

import { generateKey, getKey, loadCryptoKey } from "@oiml/cnml-crypto";

const { id, fingerprint } = await generateKey({
  alias: "My IA signing key",
  algorithm: "ECDSA",
  passphrase: "my-strong-passphrase",
});

// Later: load the key for signing
const stored = await getKey(id);
const privateKey = await loadCryptoKey(stored, "my-strong-passphrase");
import { generateKey, getKey, loadCryptoKey } from "@oiml/cnml-crypto";

const { id, fingerprint } = await generateKey({
  alias: "My IA signing key",
  algorithm: "ECDSA",
  passphrase: "my-strong-passphrase",
});

// Later: load the key for signing
const stored = await getKey(id);
const privateKey = await loadCryptoKey(stored, "my-strong-passphrase");

The key is stored in IndexedDB under the cnml-crypto database. It never leaves the browser.

Certificate creation

The web app renders a schema-driven form from the per-Recommendation JSON Schema. Fill in the evaluation results, then sign:

import { certToCnmlXml } from "@oiml/cnml-xml";
import { signCnmlXml, issueSelfSignedCert } from "@oiml/cnml-crypto";

// Build the CNML XML from the form data
const xml = certToCnmlXml(certData);

// Issue a self-signed X.509 v3 cert for KeyInfo
const certPem = await issueSelfSignedCert(
  publicKey, privateKey, "O=My IA, CN=Signer 2026, C=NL",
);

// Sign with enveloped XMLDSig + Exclusive C14N
const signedXml = await signCnmlXml(xml, privateKey, certPem);
import { certToCnmlXml } from "@oiml/cnml-xml";
import { signCnmlXml, issueSelfSignedCert } from "@oiml/cnml-crypto";

// Build the CNML XML from the form data
const xml = certToCnmlXml(certData);

// Issue a self-signed X.509 v3 cert for KeyInfo
const certPem = await issueSelfSignedCert(
  publicKey, privateKey, "O=My IA, CN=Signer 2026, C=NL",
);

// Sign with enveloped XMLDSig + Exclusive C14N
const signedXml = await signCnmlXml(xml, privateKey, certPem);

The signed XML contains:

  • <ds:Signature> inside the root element (enveloped)
  • Exclusive C14N canonicalization
  • ECDSA-SHA256 signature method
  • The X.509 certificate in <ds:X509Certificate> for chain verification

Composite signatures (post-quantum)

For post-quantum readiness, CNML supports composite signatures combining Ed25519 with ML-DSA-65:

import { generateCompositeKeyMaterial, compositeSign } from "@oiml/cnml-crypto";

const material = await generateCompositeKeyMaterial(passphrase);
const composite = await compositeSign(message, material);
import { generateCompositeKeyMaterial, compositeSign } from "@oiml/cnml-crypto";

const material = await generateCompositeKeyMaterial(passphrase);
const composite = await compositeSign(message, material);

A composite signature is valid only when both components verify.

Next steps