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.