La chain of trust de bout en bout

Une archive logicielle non sécurisée est un vecteur d'attaque. Si un attaquant intercepte l'archive en transit ou compromet le serveur de distribution, il peut substituer le contenu sans que l'utilisateur s'en aperçoive. Universal Installer V2 implémente une chain of trust complète : chiffrement + manifest SHA-256 + signature ECDSA.

# Chain of trust UXC v2
Source code → build → archive.uxc
  [1] SHA-256 par fichier → manifest.json (intégré dans archive)
  [2] AES-256-GCM sur le contenu (clé PBKDF2 600 000 itérations)
  [3] Signature ECDSA P-384 de l'archive → archive.uxc.sig
  [4] À l'extraction : vérif signature → vérif SHA-256 → déchiffrement

Sans chain of trust, une archive peut être modifiée en transit ou sur le serveur sans que personne ne le détecte. Chaque couche de sécurité résiste à un vecteur d'attaque différent.

Chiffrement AES-256-GCM

Le contenu de l'archive est chiffré avant stockage sur disque. Le tag GCM authentifie chaque chunk — toute altération est détectée avant déchiffrement :

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

# Chiffrement chunk par chunk (64 KB)
def encrypt_chunk(chunk: bytes, key: bytes, chunk_id: int) -> bytes:
    aesgcm = AESGCM(key)
    # Nonce = chunk_id (4B) + random (8B) → unique par chunk
    nonce = chunk_id.to_bytes(4, 'big') + os.urandom(8)
    ciphertext = aesgcm.encrypt(nonce, chunk, None)
    return nonce + ciphertext  # nonce(12) + ciphertext + tag(16)

Manifest SHA-256 par fichier

Avant chiffrement, un manifest est généré avec le SHA-256 de chaque fichier dans l'archive. Le manifest est lui-même inclus dans l'archive chiffrée :

# manifest.json (intégré dans l'archive chiffrée)
{
  "version": "2.0",
  "created_at": "2026-08-15T10:00:00Z",
  "files": [
    {
      "path": "app/main.exe",
      "size": 84934567,
      "sha256": "a3f8c2e1d4b7...",
      "modified": "2026-08-14T22:30:00Z"
    },
    {
      "path": "app/assets/icon.ico",
      "size": 12345,
      "sha256": "f2e9c3a1..."
    }
  ],
  "manifest_version": "1",
  "total_files": 47,
  "total_size": 87654321
}

À l'extraction, chaque fichier est vérifié contre son SHA-256 du manifest. Un fichier corrompu ou manquant arrête l'extraction avec une erreur explicite.

Signature ECDSA de l'archive

from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
import hashlib

def sign_archive(archive_path: str, private_key) -> bytes:
    # Calculer le SHA-256 de l'archive entière
    sha256 = hashlib.sha256()
    with open(archive_path, 'rb') as f:
        for chunk in iter(lambda: f.read(65536), b''):
            sha256.update(chunk)
    archive_hash = sha256.digest()

    # Signer le hash avec ECDSA P-384
    signature = private_key.sign(archive_hash, ec.ECDSA(hashes.SHA384()))
    return signature

# La signature est stockée dans archive.uxc.sig (fichier séparé)
# ou dans le header étendu de l'archive

Vérification à l'extraction

def verify_and_extract(archive_path: str, sig_path: str, public_key):
    # Étape 1 : vérifier la signature ECDSA
    archive_hash = hashlib.sha256(open(archive_path, 'rb').read()).digest()
    signature = open(sig_path, 'rb').read()
    try:
        public_key.verify(signature, archive_hash, ec.ECDSA(hashes.SHA384()))
    except:
        raise SecurityError("Signature ECDSA invalide — archive compromise")

    # Étape 2 : déchiffrer et vérifier les chunks GCM
    # (toute altération lève InvalidTag automatiquement)

    # Étape 3 : vérifier chaque fichier contre le manifest SHA-256
    for entry in manifest['files']:
        actual = hashlib.sha256(open(entry['path'], 'rb').read()).hexdigest()
        if actual != entry['sha256']:
            raise IntegrityError(f"Fichier corrompu : {entry['path']}")

Dérivation de clé PBKDF2

La clé AES-256 est dérivée du mot de passe avec PBKDF2-SHA256, 600 000 itérations :

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes as h

kdf = PBKDF2HMAC(
    algorithm=h.SHA256(),
    length=32,
    salt=salt,           # 32 bytes aléatoires, stocké en clair dans le header
    iterations=600_000  # recommandation OWASP 2026
)
key = kdf.derive(password.encode())

Pipeline complet en une commande

# Compress + chiffrer + signer en une commande
universal-installer compress ./dist   --output app_v1.1.uxc   --encrypt --password vault:ARCHIVE_PASSWORD   --sign --privkey keys/release.pem   --manifest   --algo auto

# Vérifier avant distribution
universal-installer verify app_v1.1.uxc   --sig app_v1.1.uxc.sig   --pubkey keys/public.pem

Conclusion

La chain of trust UXC v2 (chiffrement AES-256-GCM + manifest SHA-256 + signature ECDSA P-384) couvre trois vecteurs d'attaque indépendants : interception en transit, compromission du serveur de distribution, corruption accidentelle. Universal Installer V2 génère l'ensemble de cette chaîne en une commande.

📦
PRODUIT LIÉ
Universal Installer V2.0
← Article précédent Article suivant →