Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spec couche 1 — air-crypto (primitives cryptographiques)

Spécification technique — Version 1.0. Couche 1 « Primitives système ».

Position et méthode

air-crypto fournit les primitives cryptographiques : aléa, hachage, MAC, chiffrement authentifié (AEAD), dérivation de clés, signatures, échange de clés. S’appuie sur air-base-lib (AirError) et, pour l’aléa, sur getrandom (couche 0 system). API Rust d’abord (ABI C différée).

On ne réimplémente JAMAIS de crypto. C’est la crate où la philosophie « build-own / minimiser les deps » est délibérément renversée : rouler sa propre crypto est dangereux (attaques temporelles, bugs subtils, zéro audit). La sécurité vient de l’adoption massive et auditée. RustCrypto (constant-time, audité, accélération AES-NI/ARMv8 automatique) et zeroize/subtle sont des dépendances structurantes, exceptions nommées à la règle des 80 % — comme icu4x (ADR-016/ADR-024 ; docs/EXCEPTIONS.md).

La valeur d’air-crypto = une API Air anti-mésusage : clés type-safe qui se zeroize au Drop, AEAD qui gère les nonces (réutilisation impossible par construction), vérifications constant-time, AirRandom toujours via le CSPRNG kernel. On enrobe RustCrypto d’une surface qui rend les erreurs courantes difficiles.

Périmètre v1 : cœur moderne (RSA/legacy différés ; TLS séparé)

  • Inclus : AirRandom ; hachage (SHA-256/384/512, SHA3-256/512, BLAKE3) ; HMAC ; AEAD (AES-256-GCM, ChaCha20-Poly1305) ; KDF (HKDF, Argon2) ; signatures Ed25519 ; échange de clés X25519.
  • Différé : modes hérités (AES-CBC brut, PKCS#1v1.5 chiffrement)…, jamais ajoutés (omission = sécurité). TLS (rustls) = protocole de session sur une connexion → spec séparée (air-tls / framework de connexion), plus tard.
  • Additifs planifiés (descellement couche-1-v1.x) pour l’interop WebPKI de la contingence air-tls maison (spec air-tls, ADR-042 §Contingence) : AES-128-GCM, ECDSA P-256/P-384, RSA-PSS vérification seule, ML-KEM-768 hybride. Détail : §« Additifs planifiés » ci-dessous. Non encore dans l’API scellée ; chaque additif = RFC de descellement (modèle ADR-065/066/067) + KAT-gate (ADR-034).

Conventions

  • Aucune fonction unsafe exposée (peu d’unsafe interne — RustCrypto porte le constant-time/unsafe). Couverture 100 %.
  • Vecteurs de test connus (KAT) : tout primitif est testé contre des vecteurs de référence RFC/NIST (cf. §tests) — l’étalon-or de la correction crypto.
  • Secrets zeroize : les types de clé/secret effacent leur mémoire au Drop, n’implémentent ni Debug qui fuit, ni Clone gratuit.
  • Comparaisons constant-time (subtle) pour les tags/MAC (jamais == qui fuiterait par le timing).
  • Carte de consommation couche 0 : AirRandomsystem::getrandom (seul contact couche 0 ; le reste est du calcul pur RustCrypto).

Section 1 — Aléa : AirRandom

#![allow(unused)]
fn main() {
/// Source d'aléa **cryptographique**, toujours le CSPRNG kernel (`getrandom`),
/// **jamais** un PRNG userspace.
pub struct AirRandom;
impl AirRandom {
    /// Remplit `buffer` d'octets aléatoires sûrs. # Errors `AirError`.
    pub fn fill(buffer: &mut [u8]) -> AirResult<()>;
    /// Génère une clé symétrique de `N` octets (zeroize au Drop).
    pub fn generate_key<const N: usize>() -> AirResult<AirSymmetricKey<N>>;
    pub fn u64() -> AirResult<u64>;
}
}

Section 2 — Clés (type-safe, zeroize)

#![allow(unused)]
fn main() {
/// Clé symétrique de `N` octets. **Zeroize au `Drop`** ; pas de `Debug`/`Clone` qui fuit.
pub struct AirSymmetricKey<const N: usize> { /* [u8; N], zeroize */ }
impl<const N: usize> AirSymmetricKey<N> {
    pub fn from_bytes(bytes: [u8; N]) -> Self;
    pub fn expose(&self) -> &[u8; N];               // accès explicite, nommé « expose »
}
}

Décision. Toute matière secrète (clés sym., clés privées de signature/ECDH, sorties de KDF) est encapsulée dans des types zeroizés au Drop, sans Debug exposant les octets, l’accès se faisant par une méthode nommée expose (le nom signale qu’on touche du secret).


Section 3 — Hachage : AirHash

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AirHashAlgorithm { Sha256, Sha384, Sha512, Sha3_256, Sha3_512, Blake3 }

/// Hachage en flux (incrémental) ou one-shot.
pub struct AirHash { /* état RustCrypto selon l'algo */ }
impl AirHash {
    pub fn new(algorithm: AirHashAlgorithm) -> Self;
    pub fn update(&mut self, data: &[u8]);
    pub fn finalize(self) -> Vec<u8>;               // digest
    /// One-shot.
    pub fn digest(algorithm: AirHashAlgorithm, data: &[u8]) -> Vec<u8>;
}
}

Section 4 — MAC : AirHmac

#![allow(unused)]
fn main() {
pub struct AirHmac { /* HMAC-SHA256/384/512 */ }
impl AirHmac {
    pub fn new(algorithm: AirHashAlgorithm, key: &[u8]) -> AirResult<Self>;
    pub fn update(&mut self, data: &[u8]);
    pub fn finalize(self) -> Vec<u8>;               // tag
    /// Vérification **constant-time** (jamais `==`). `true` ssi le tag est valide.
    pub fn verify(self, expected_tag: &[u8]) -> bool;
}
}

Section 5 — AEAD (chiffrement authentifié) : AirAead

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AirAeadAlgorithm { Aes256Gcm, ChaCha20Poly1305 }

/// Chiffrement authentifié. **Anti-mésusage : la façade gère les nonces** —
/// l'appelant n'en manipule jamais (la réutilisation de nonce, catastrophique,
/// est impossible par construction).
pub struct AirAead<const N: usize> { algorithm: AirAeadAlgorithm, key: AirSymmetricKey<N> }
impl AirAead<32> {
    pub fn new(algorithm: AirAeadAlgorithm, key: AirSymmetricKey<32>) -> Self;

    /// Chiffre : génère un **nonce aléatoire** (via `AirRandom`) et rend
    /// `nonce || ciphertext || tag`. `aad` = données authentifiées non chiffrées.
    /// # Errors `AirError`.
    pub fn seal(&self, plaintext: &[u8], aad: &[u8]) -> AirResult<Vec<u8>>;

    /// Déchiffre `nonce || ciphertext || tag`. **Échoue** si le tag ne valide pas
    /// (intégrité/authenticité) — `Err(AirError { InvalidData })`, **pas** de
    /// plaintext partiel rendu.
    pub fn open(&self, sealed: &[u8], aad: &[u8]) -> AirResult<Vec<u8>>;
}
}

Décision nonce (anti-mésusage). seal génère le nonce en interne et le préfixe au message ; l’appelant ne le voit ni ne le choisit → réutilisation impossible. Limite documentée : avec AES-256-GCM (nonce 96 bits), le nonce aléatoire est sûr jusqu’à ~2³² messages par clé (borne d’anniversaire) ; ChaCha20-Poly1305 est recommandé pour les très gros volumes. Une variante à compteur de session (pour streamer beaucoup de messages sous une clé) est prévue ultérieurement. Aucune donnée perdue sur erreur (ADR-032) : open rend tout le plaintext ou rien.


Section 6 — KDF : AirHkdf / AirArgon2

#![allow(unused)]
fn main() {
/// HKDF (dérivation de clé à partir d'un secret + sel + info).
pub struct AirHkdf;
impl AirHkdf {
    /// # Errors `AirError`. Rend une clé dérivée de `output_len` octets (zeroize).
    pub fn derive(algorithm: AirHashAlgorithm, secret: &[u8], salt: &[u8], info: &[u8],
                  output_len: usize) -> AirResult<AirDerivedKey>;
}

/// Argon2id — hachage de **mot de passe** (lent, résistant GPU/ASIC).
pub struct AirArgon2;
impl AirArgon2 {
    /// Hache un mot de passe (sel aléatoire interne) → chaîne PHC vérifiable.
    pub fn hash_password(password: &[u8], params: AirArgon2Params) -> AirResult<String>;
    /// Vérifie un mot de passe contre une chaîne PHC (**constant-time**).
    pub fn verify_password(password: &[u8], phc: &str) -> AirResult<bool>;
}
}

Section 7 — Signatures (Ed25519) & échange de clés (X25519)

#![allow(unused)]
fn main() {
/// Paire de signature Ed25519. La clé privée est zeroizée au Drop.
pub struct AirSigningKey { /* ed25519 secret, zeroize */ }
pub struct AirVerifyingKey { /* ed25519 public */ }
impl AirSigningKey {
    pub fn generate() -> AirResult<Self>;
    pub fn verifying_key(&self) -> AirVerifyingKey;
    pub fn sign(&self, message: &[u8]) -> [u8; 64];
}
impl AirVerifyingKey {
    pub fn from_bytes(bytes: [u8; 32]) -> AirResult<Self>;   // # Errors si point invalide
    /// `true` ssi la signature est valide pour `message`.
    pub fn verify(&self, message: &[u8], signature: &[u8; 64]) -> bool;
}

/// Échange de clés X25519 (Diffie-Hellman sur courbe). Secret partagé → KDF.
pub struct AirEcdhPrivateKey { /* zeroize */ }
pub struct AirEcdhPublicKey { /* [u8;32] */ }
impl AirEcdhPrivateKey {
    pub fn generate() -> AirResult<Self>;
    pub fn public_key(&self) -> AirEcdhPublicKey;
    /// Secret partagé (à passer **toujours** dans un KDF, jamais utilisé brut).
    pub fn diffie_hellman(&self, peer: &AirEcdhPublicKey) -> AirDerivedKey;
}
}

Additifs planifiés — support air-tls (descellement couche-1-v1.x, RFC)

Statut. air-crypto est scellée (couche-1). Les primitives ci-dessous ne sont pas dans l’API figée : elles sont instruites ici pour l’interop WebPKI réelle de la contingence air-tls maison (ADR-042 §Contingence). Leur entrée exige un RFC de descellement additif (modèle ADR-065/066/067) et un KAT-gate (ADR-034). Elles restent pur Rust, zéro C, zéro unsafe exposé, sous la même discipline que le cœur v1 (exception 80 % nommée, no_std-strict, pins =, fast-lane sécurité). Aucune réécriture de crypto — on enrobe des crates RustCrypto auditées.

A.1 — AES-128-GCM (extension d’AirAeadAlgorithm)

#![allow(unused)]
fn main() {
pub enum AirAeadAlgorithm { Aes256Gcm, ChaCha20Poly1305, Aes128Gcm /* additif */ }
// `AirAead<16>` : même façade anti-mésusage (nonce géré), clé 128 bits.
}
  • Backend : aes-gcm (déjà dépendance, RustCrypto) — seule la variante 128 bits s’ajoute. KAT : vecteurs NIST GCM.
  • Motif : suite TLS 1.3 TLS_AES_128_GCM_SHA256 (mandatory-to-implement, RFC 8446 §9.1) — indispensable à l’interop.

A.2 — Signatures ECDSA P-256 / P-384

#![allow(unused)]
fn main() {
pub enum AirEcCurve { P256, P384 }
/// Clé de signature ECDSA. Privée zeroizée au Drop. **Nonce déterministe RFC 6979.**
pub struct AirEcdsaSigningKey { /* p256/p384 secret, zeroize */ }
pub struct AirEcdsaVerifyingKey { /* point public */ }
impl AirEcdsaSigningKey {
    pub fn generate(curve: AirEcCurve) -> AirResult<Self>;
    pub fn verifying_key(&self) -> AirEcdsaVerifyingKey;
    pub fn sign(&self, message: &[u8]) -> AirResult<Vec<u8>>;   // ASN.1 DER (r,s)
}
impl AirEcdsaVerifyingKey {
    pub fn from_sec1_bytes(curve: AirEcCurve, bytes: &[u8]) -> AirResult<Self>;
    pub fn verify(&self, message: &[u8], signature: &[u8]) -> bool;
}
}
  • Backend : p256 / p384 (RustCrypto, pur Rust, no_std+alloc, arithmétique de corps pure Rust — pas d’asm/cpufeatures, donc moins de réserve aarch64 que aes-gcm). KAT : NIST CAVP (FIPS 186-4).
  • Décision nonce : RFC 6979 (déterministe) — élimine par construction la classe de failles « nonce ECDSA faible/réutilisé » (désastres PS3/Bitcoin). Jamais de nonce ECDSA tiré d’un PRNG.
  • Motif : schémas TLS ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384 (RFC 8446 §4.2.3) — vérif. des chaînes serveur ECDSA (fréquentes) ; sign. côté mTLS.

A.3 — ECDH P-256 / P-384 (groupes secp256r1/secp384r1)

#![allow(unused)]
fn main() {
pub struct AirEcdhNistPrivateKey { /* p256/p384 scalar, zeroize */ }
pub struct AirEcdhNistPublicKey  { /* point SEC1 */ }
// Même contrat qu'`AirEcdhPrivateKey` (X25519) : `diffie_hellman` rend un
// `AirDerivedKey` à passer TOUJOURS dans un KDF, jamais brut.
}
  • Backend : p256/p384 (feature ecdh, RustCrypto). KAT : NIST.
  • Motif : groupes d’échange TLS secp256r1/secp384r1 (interop avec pairs sans X25519). X25519 reste le défaut ; NIST en repli d’interop.

A.4 — RSA-PSS vérification seule (jamais d’opération privée)

#![allow(unused)]
fn main() {
/// Clé PUBLIQUE RSA — **vérification uniquement**. Pas de clé privée RSA dans Air.
pub struct AirRsaPssVerifyingKey { /* modulus + exposant public */ }
impl AirRsaPssVerifyingKey {
    pub fn from_pkcs1_der(der: &[u8]) -> AirResult<Self>;      // borné, jamais de panic
    /// Vérifie une signature RSA-PSS (hash SHA-256/384/512). `true` ssi valide.
    pub fn verify(&self, hash: AirHashAlgorithm, message: &[u8], signature: &[u8]) -> bool;
}
}
  • Backend : rsa (RustCrypto, pur Rust). On n’expose AUCUNE génération, signature, ni déchiffrement RSA — seule la vérification (opération publique).
  • Note sécurité (importante) : l’advisory RUSTSEC-2023-0071 « Marvin » est un canal temporel sur l’opération PRIVÉE RSA (déchiffrement/signature). Air ne fait aucune opération privée RSAcette faille ne s’applique pas. On vérifie seulement des signatures de certificats serveur (WebPKI, où RSA domine encore le parc). PKCS#1 v1.5 chiffrement (Bleichenbacher) : omis ; RSA-PSS vérif. seule.
  • Motif : schémas TLS rsa_pss_rsae_sha256/384/512 (RFC 8446 §4.2.3) — sans eux, la majorité des certificats WebPKI actuels ne validerait pas.

A.5 — ML-KEM-768 (hybride post-quantique X25519MLKEM768)

#![allow(unused)]
fn main() {
/// KEM post-quantique ML-KEM-768 (FIPS 203). **Utilisé UNIQUEMENT en hybride.**
pub struct AirMlKem768;
pub struct AirMlKemEncapsulation { /* ciphertext */ }
impl AirMlKem768 {
    pub fn generate_keypair() -> AirResult<(AirMlKemPublicKey, AirMlKemSecretKey /*zeroize*/)>;
    pub fn encapsulate(peer: &AirMlKemPublicKey) -> AirResult<(AirMlKemEncapsulation, AirDerivedKey)>;
    pub fn decapsulate(secret: &AirMlKemSecretKey, ct: &AirMlKemEncapsulation) -> AirResult<AirDerivedKey>;
}
}
  • Backend : ml-kem (RustCrypto, FIPS 203, pur Rust no_std). KAT : vecteurs NIST ACVP / FIPS 203.
  • Décision hybride-only : ML-KEM n’est jamais utilisé seul. Le groupe TLS X25519MLKEM768 combine le secret X25519 et le secret ML-KEM (concaténation → HKDF). La sécurité est ≥ celle du plus fort des deux : si ML-KEM (récent, moins éprouvé) tombait, X25519 protège encore ; réciproquement face à un ordinateur quantique. C’est le consensus de déploiement 2024-2025 (Chrome/Cloudflare). ML-KEM seul refusé tant que la primitive n’a pas l’ancienneté d’X25519.

Discipline & gouvernance de ces additifs

AdditifCrate (RustCrypto)Pur Rust / zéro-CSchéma(s) TLS 1.3 débloqué(s)
AES-128-GCMaes-gcm (déjà dép.)TLS_AES_128_GCM_SHA256
ECDSA P-256/P-384p256, p384ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384
ECDH P-256/P-384p256, p384 (ecdh)groupes secp256r1/secp384r1
RSA-PSS (vérif.)rsarsa_pss_rsae_sha256/384/512
ML-KEM-768ml-kemgroupe X25519MLKEM768 (hybride)
  • Zéro-C : les 5 crates sont pur Rustcargo xtask check-c-surface reste vert (aucune n’introduit cc/*-sys). À re-vérifier au bump (notamment la réserve aarch64/cpufeatures déjà documentée pour aes-gcm, docs/EXCEPTIONS.md).
  • no_std-strict : default-features = false, features aléa exclues (l’aléa vient toujours d’AirRandomgetrandom(2) couche 0), comme le cœur v1.
  • KAT-gated (ADR-034) : chaque additif entre avec ses vecteurs officiels (NIST GCM/CAVP/ACVP, FIPS 203) avant merge ; bumps re-passent les KAT.
  • Exception 80 % nommée : ajouter p256/p384/rsa/ml-kem à l’entrée air-crypto de docs/EXCEPTIONS.md et au DEPENDENCIES.md de la crate.
  • Gate de descellement : RFC additif (ADR-015, modèle ADR-065/066/067) — le BDFL scelle couche-1-v1.x après validation 2 arches.

Additifs planifiés — support air-quic / air-ssh (ADR-081)

Complète les additifs air-tls (ci-dessus, ADR-078). Révélés par air-quic (header protection) et air-ssh (signatures RSA v1.5, cipher custom). Même discipline (RFC de descellement, KAT-gate ADR-034, ratification BDFL — surface crypto). Zéro-C, zéro unsafe exposé.

B.1 — Module bas niveau air_crypto::lowlevel (opt-in, expert-only)

Primitives nues requises par les implémenteurs de protocole (QUIC/SSH), séparées de la façade anti-mésusage (qui reste le défaut) :

#![allow(unused)]
fn main() {
pub mod lowlevel {   // documenté expert-only ; JAMAIS le chemin par défaut applicatif
    /// Bloc AES brut (single-block) — QUIC header protection (RFC 9001 §5.4.3).
    pub struct AirAesBlock { /* aes */ }
    impl AirAesBlock {
        pub fn new_128(key: &AirSymmetricKey<16>) -> Self;
        pub fn new_256(key: &AirSymmetricKey<32>) -> Self;
        pub fn encrypt_block(&self, block: [u8; 16]) -> [u8; 16];
    }
    /// Keystream ChaCha20 (RFC 8439 §2.4) — QUIC HP (suite ChaCha) + SSH cipher custom.
    pub struct AirChaCha20 { /* chacha20 */ }
    /// Poly1305 one-shot (RFC 8439 §2.5) — SSH chacha20-poly1305@openssh.
    pub struct AirPoly1305 { /* poly1305 */ }
}
}
  • Backends : aes, chacha20, poly1305 (RustCrypto) — déjà transitifs (via aes-gcm/chacha20poly1305), promus directs. KAT : RFC 8439 / NIST.
  • Décision anti-mésusage : module lowlevel séparé, opt-in ; la façade AirAead (nonce géré) reste le défaut. La construction (header protection, chacha20-poly1305@openssh) vit en couche 2 (air-quic/air-ssh), pas ici.

B.2 — RSA PKCS#1 v1.5 — vérification seule (SSH rsa-sha2-256/512)

#![allow(unused)]
fn main() {
/// Vérif. RSASSA-PKCS#1 v1.5 — clé PUBLIQUE only. Distinct du RSA-PSS (ADR-078).
pub struct AirRsaPkcs1VerifyingKey { /* rsa (déjà ADR-078) */ }
}
  • Aucune opération privée RSAhors Marvin (RUSTSEC-2023-0071). PKCS#1 v1.5 signature ≠ chiffrement : la vérif. v1.5 est sûre ; le chiffrement v1.5 (Bleichenbacher) reste omis.

B.3 — Kex PQ SSH : réutilise ML-KEM-768 (ADR-078)

air-ssh utilise mlkem768x25519-sha256 (défaut OpenSSH ≥ 9.9) → réutilise ml-kem-768 déjà instruit (ADR-078). sntrup761x25519 différé (pas de crate pure-Rust vettée). Aucun additif crypto nouveau pour le PQ SSH.

Discipline

AdditifCrate (RustCrypto)Zéro-CConsommateur
lowlevel::AirAesBlockaes (direct)QUIC header protection
lowlevel::AirChaCha20chacha20 (direct)QUIC HP + SSH cipher
lowlevel::AirPoly1305poly1305 (direct)SSH chacha20-poly1305@openssh
AirRsaPkcs1VerifyingKeyrsa (déjà ADR-078)SSH rsa-sha2-256/512
(PQ SSH)ml-kem (déjà ADR-078)SSH mlkem768x25519

Exceptions 80 % : aes/chacha20/poly1305 promues directes dans EXCEPTIONS.md

  • DEPENDENCIES.md. KAT-gated (ADR-034). Ratification BDFL (surface crypto).

Additifs planifiés — AEAD nonce explicite, BLAKE2s, PBKDF2 (ADR-082)

Complète ADR-078/081. Révélés par air-wireguard/air-mail et une relecture des piles TLS/QUIC/SSH. Même discipline (ratification BDFL, KAT-gate ADR-034). Pur Rust, zéro-C.

C.1 — lowlevel::AirAeadExplicitNonce (AEAD à nonce fourni) — le point important

#![allow(unused)]
fn main() {
pub mod lowlevel {
    /// AEAD (AES-128/256-GCM | ChaCha20-Poly1305) où **l'appelant fournit le nonce**.
    /// Pour les protocoles à nonce DÉTERMINISTE (nonce = IV ⊕ compteur/séquence).
    pub struct AirAeadExplicitNonce { /* aes-gcm | chacha20poly1305 (déjà présents) */ }
    impl AirAeadExplicitNonce {
        pub fn seal(&self, nonce: &[u8; 12], plaintext: &[u8], aad: &[u8]) -> AirResult<Vec<u8>>;
        pub fn open(&self, nonce: &[u8; 12], sealed: &[u8], aad: &[u8]) -> AirResult<Vec<u8>>;
    }
}
}

Pourquoi c’est nécessaire. La façade AirAead (§5) génère un nonce aléatoire interne et le préfixe — parfait pour l’applicatif, inutilisable pour un protocole de session : TLS 1.3 record (RFC 8446 §5.3), QUIC packet (RFC 9001 §5.3), WireGuard, SSH aes-gcm dérivent un nonce déterministe IV ⊕ compteur, non préfixé. air-tls/air-quic/air-ssh/air-wireguard utilisent donc AirAeadExplicitNonce (dans lowlevel, opt-in expert-only), pas la façade. Contrat : l’appelant garantit l’unicité du nonce par clé (les key schedules l’assurent par construction — compteur jamais réutilisé). La façade à nonce géré reste le défaut applicatif.

C.2 — BLAKE2s (WireGuard)

#![allow(unused)]
fn main() {
pub enum AirHashAlgorithm { /* … */ Blake2s /* additif */ }
pub struct AirBlake2sMac { /* blake2 : MAC keyed (MAC1/MAC2) + HMAC-BLAKE2s (KDF Noise) */ }
}
  • Backend : blake2 (RustCrypto) — pur Rust, ZÉRO-C (≠ blake3 qui tire cc). KAT : RFC 7693.

C.3 — PBKDF2-HMAC-SHA256 (SASL SCRAM)

#![allow(unused)]
fn main() {
pub struct AirPbkdf2;
impl AirPbkdf2 { pub fn derive_sha256(password: &[u8], salt: &[u8], iterations: u32, out: &mut [u8]) -> AirResult<()>; }
}
  • Backend : pbkdf2 (RustCrypto), pur Rust. KAT : RFC 6070. Requis pour SCRAM-SHA-256 (sinon OAUTHBEARER token, sans PBKDF2).

Discipline

AdditifCrate (RustCrypto)Zéro-CConsommateur
lowlevel::AirAeadExplicitNonceaes-gcm/chacha20poly1305 (déjà)record/packet TLS/QUIC/SSH/WireGuard
AirHashAlgorithm::Blake2s + AirBlake2sMacblake2 (nouveau)WireGuard (Noise)
AirPbkdf2pbkdf2 (nouveau)mail SASL SCRAM-SHA-256

Exceptions 80 % : blake2/pbkdf2 ajoutées (EXCEPTIONS.md + DEPENDENCIES.md). KAT-gated (ADR-034). Ratification BDFL (surface crypto).

Récapitulatif air-crypto

DomaineAPIBackend (exception 80 %)
AléaAirRandomgetrandom (couche 0)
Clés/secretsAirSymmetricKey, AirDerivedKey (zeroize)zeroize
HachageAirHash, AirHashAlgorithmsha2/sha3/blake3
MACAirHmac (verify constant-time)hmac + subtle
AEADAirAead (nonces gérés)aes-gcm, chacha20poly1305
KDFAirHkdf, AirArgon2hkdf, argon2
Signatures / ECDHAirSigningKey/AirVerifyingKey, AirEcdh*ed25519-dalek, x25519-dalek

Différé : RSA & modes hérités ; variante AEAD à compteur de session ; TLS (rustls) → spec séparée ; ABI C.

Dépendances (règle des 80 %, ADR-024 — exceptions nommées)

La discipline de ces dépendances crypto (exemption de vendoring, fast-lane sécurité, veille des upstreams, KAT-gating des bumps) est régie par ADR-034. Cette spec ne la redétaille pas — s’y reporter et la respecter.

Trois upstreams (purs Rust, sans C ; licences Apache-2.0 / MIT / BSD-3-Clause ; blake3 en dual CC0-1.0 OR Apache-2.0 → option Apache dans deny.toml) :

  • RustCrypto : sha2, sha3, blake2, hmac, aes-gcm, chacha20poly1305, hkdf, argon2, les trait-crates, et zeroize ;
  • dalek-cryptography : ed25519-dalek, x25519-dalek, curve25519-dalek, et subtle (à ne pas attribuer à RustCrypto) ;
  • équipe BLAKE3 : blake3.

Toutes sont des exceptions structurantes à la règle des 80 % (comme icu4x) — à documenter nommément dans docs/EXCEPTIONS.md (« crypto auditée, sécurité par adoption massive », ADR-016 par analogie) et exemptées de vendoring (ADR-034). getrandom = couche 0. Pas de réécriture de primitive.

Stratégie de tests

  • Couverture 100 % lignes + branches.
  • Vecteurs de test connus (KAT) — impératif : chaque primitif comparé à des vecteurs RFC/NIST officiels (SHA, HMAC RFC 4231, AES-GCM NIST, ChaCha20-Poly1305 RFC 8439, HKDF RFC 5869, Ed25519 RFC 8032, X25519 RFC 7748, Argon2). C’est la preuve de correction d’une primitive crypto.
  • Round-trips : seal/open, sign/verify, ECDH des deux côtés → même secret, KDF déterministe.
  • Tests négatifs (sécurité) : ciphertext/tag altéréopen échoue (pas de plaintext) ; mauvaise clé ; signature falsifiée → verify false ; mot de passe faux → verify_password false.
  • zeroize : vérifier que la mémoire d’un secret est effacée au Drop (test mémoire dédié).
  • Fuzzing (cargo-fuzz) : décodage des entrées externesAirVerifyingKey::from_bytes, AirEcdhPublicKey, open/verify sur octets arbitraires, parsing PHC Argon2. Zéro panique.
  • Doctests.

Décisions de fond

  1. Zéro crypto maison — adoption de RustCrypto/rustls auditées ; exceptions 80 % nommées (EXCEPTIONS.md), comme icu4x.
  2. API anti-mésusage — clés zeroize, AEAD à nonces gérés (réutilisation impossible), vérifications constant-time, accès secret via expose nommé.
  3. AirRandom toujours getrandom kernel — jamais de PRNG userspace.
  4. Cœur moderne (Ed25519/X25519/AEAD/Argon2…) ; RSA & legacy différés (plus sûrs par omission).
  5. TLS hors périmètre (protocole de session → spec séparée).
  6. KAT contre vecteurs officiels — étalon de correction crypto.

Travail à reprendre

  • TLS — spec séparée : production air-tls (rustls, ADR-042) ; contingence maison docs/specs/layer-2/air-tls.md (TLS 1.3 pur Rust).
  • Additifs air-tls (descellement couche-1-v1.x, RFC)AES-128-GCM, ECDSA P-256/P-384 (sign+ECDH, RFC 6979), RSA-PSS vérif. seule (pas d’opération privée RSA → hors Marvin), ML-KEM-768 hybride ; cf. §« Additifs planifiés ». KAT-gated (ADR-034), exception 80 % à nommer (EXCEPTIONS.md).
  • PKCS#1 v1.5 chiffrement, RSA privé, AES-CBCjamais (omission = sécurité).
  • Variante AEAD à compteur de session (gros volumes sous une clé).
  • ABI C d’air-crypto ; dérivation app-specific de l’ID machine (AirId128, consommateur d’air-crypto, cf. air-base-lib services).
  • Dernière crate couche 1 : air-device.

Licence du document : MPL 2.0 Statut : Spécification technique d’air-crypto (couche 1). API Rust ; ABI C différée. Cœur moderne sur RustCrypto (exceptions 80 %) ; TLS séparé.