795 lines
57 KiB
Markdown
795 lines
57 KiB
Markdown
|
|
# Внешний аудит кода Montana — отчёт
|
|||
|
|
|
|||
|
|
**Аудитор:** Claude Opus 4.7 (1M context), модель `claude-opus-4-7[1m]`
|
|||
|
|
**Дата проведения:** 2026-04-26, T20:18:05 — T20:55:00 (московское время плюс)
|
|||
|
|
**Локация:** `/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код/`
|
|||
|
|
**Длительность:** ~37 минут активного аудита
|
|||
|
|
**Режим выполнения:** одно ядро / один процесс (соблюдён `.cargo/config.toml`: `jobs = 1`, `RUST_TEST_THREADS = 1`)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Аудиторская методология и доверие
|
|||
|
|
|
|||
|
|
### Что я доверял (источники истины)
|
|||
|
|
|
|||
|
|
- **Только исходный код:** `*.rs`, `*.c`, `*.h`, `Cargo.toml`, `Cargo.lock`, `build.rs`, `rust-toolchain.toml`, `clippy.toml`, `.cargo/config.toml`
|
|||
|
|
- **Тестовые fixtures** при условии независимой верификации против внешнего источника
|
|||
|
|
- **Внешние публичные стандарты** как основу истины:
|
|||
|
|
- NIST FIPS 204 (ML-DSA), FIPS 203 (ML-KEM), FIPS 180-4 (SHA-256)
|
|||
|
|
- RFC 5869 (HKDF), RFC 4231 (HMAC-SHA-256), RFC 7914 (PBKDF2 test vectors), RFC 8018 (PBKDF2)
|
|||
|
|
- BIP-39 (контрольная сумма мнемоники)
|
|||
|
|
- **Публичный репозиторий NIST CAVP** — `https://github.com/usnistgov/ACVP-Server` как независимый источник KAT-векторов
|
|||
|
|
- **RustSec Advisory DB** через `cargo audit` (1058 advisories, обновлено 2026-04-25)
|
|||
|
|
|
|||
|
|
### Что я НЕ доверял (исключённые источники)
|
|||
|
|
|
|||
|
|
- **Все markdown-файлы** в `/Users/kh./Python/Ничто/Монтана/Русский/Протокол/`:
|
|||
|
|
- `AUDIT.md` — рассматривался как **текст с заявлениями требующими проверки**
|
|||
|
|
- `CLAUDE.md`, `CRITIC.md` (роли архитектора и критика)
|
|||
|
|
- `ROADMAP.md`, `VERSION.md`, `README.md`
|
|||
|
|
- Спецификация `Montana v33.0.0.md` и приложения `Montana App v3.8.0.md`
|
|||
|
|
- Все исторические артефакты в `Архив/`
|
|||
|
|
- **Комментарии в коде** (`// SAFETY:`, `// spec, раздел "..."`) — рассматривались как **claims-to-verify**, не как авторитетные утверждения
|
|||
|
|
- **Хардкоженные тестовые expected hex значения** в `tests/` — где они self-derived (regression baseline), а не cross-checked против NIST/RFC, оценивались как regression-tests, не как conformance-proofs
|
|||
|
|
|
|||
|
|
### Источники ground truth, использованные в аудите
|
|||
|
|
|
|||
|
|
| Стандарт / источник | URL / ссылка | Применение |
|
|||
|
|
|---------------------|---------------|------------|
|
|||
|
|
| NIST FIPS 204 (ML-DSA) | csrc.nist.gov/pubs/fips/204/final | Размеры ключей, deterministic Sign Algorithm 2 |
|
|||
|
|
| NIST FIPS 203 (ML-KEM) | csrc.nist.gov/pubs/fips/203/final | Размеры ключей, KeyGen_internal(d, z) |
|
|||
|
|
| NIST FIPS 180-4 (SHA-256) | csrc.nist.gov/pubs/fips/180/4/final | Hash test vector "abc" |
|
|||
|
|
| NIST CAVP ACVP-Server | github.com/usnistgov/ACVP-Server | 51 KAT для ML-DSA-65, ML-KEM-768, ML-DSA SigGen |
|
|||
|
|
| RFC 5869 (HKDF) | rfc-editor.org/rfc/rfc5869 | Test vectors A.1, A.2, A.3 |
|
|||
|
|
| RFC 4231 (HMAC-SHA-256) | rfc-editor.org/rfc/rfc4231 | Test cases 1, 2, 4, 6 |
|
|||
|
|
| RFC 7914 (PBKDF2) §11 | rfc-editor.org/rfc/rfc7914 | Test vectors 1, 2 |
|
|||
|
|
| BIP-39 | github.com/bitcoin/bips/blob/master/bip-0039.mediawiki | Структура мнемоники, контрольная сумма |
|
|||
|
|
| RustSec Advisory DB | github.com/RustSec/advisory-db | 1058 advisories на 2026-04-25 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Объём аудита и фактическое состояние кода
|
|||
|
|
|
|||
|
|
### 2.1. Workspace inventory
|
|||
|
|
|
|||
|
|
Workspace содержит **14 crates** на момент начала аудита (изменилось до 14 в той же конфигурации к моменту окончания: один crate `mt-timechain` был замен на `mt-timechain` во время аудита — отмечено как finding F-12).
|
|||
|
|
|
|||
|
|
Crates присутствующие в workspace:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
mt-account mt-codec mt-consensus mt-crypto mt-crypto-native
|
|||
|
|
mt-entry mt-examples mt-genesis mt-lottery mt-merkle
|
|||
|
|
mt-mnemonic mt-timechain mt-state mt-store
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2. Audit scope (M1 — фундаментальный криптографический слой)
|
|||
|
|
|
|||
|
|
Согласно `AUDIT.md` (которому я не доверяю, но используется как scope-marker), audit-ready scope ограничен M1:
|
|||
|
|
|
|||
|
|
| Crate | Файл | Строк (фактически) | Назначение |
|
|||
|
|
|-------|------|---------------------|------------|
|
|||
|
|
| mt-crypto | crates/mt-crypto/src/lib.rs | **643** | Public Rust API (ML-DSA-65 + ML-KEM-768 + SHA-256) |
|
|||
|
|
| mt-crypto-native (Rust) | crates/mt-crypto-native/src/lib.rs | **40** | FFI декларации к Layer 2 |
|
|||
|
|
| mt-crypto-native (C) | crates/mt-crypto-native/csrc/mt_crypto.c | **375** | C-обёртка над OpenSSL EVP API |
|
|||
|
|
| mt-crypto-native (header) | crates/mt-crypto-native/csrc/mt_crypto.h | **56** | C декларации + 13 кодов ошибок |
|
|||
|
|
| mt-crypto-native build | crates/mt-crypto-native/build.rs | **45** | Сборка vendored OpenSSL + С |
|
|||
|
|
| mt-mnemonic core | crates/mt-mnemonic/src/lib.rs | 17 | Re-exports |
|
|||
|
|
| mt-mnemonic mnemonic | crates/mt-mnemonic/src/mnemonic.rs | **194** | Mnemonic ↔ master_seed ↔ per-role seeds |
|
|||
|
|
| mt-mnemonic pbkdf2 | crates/mt-mnemonic/src/pbkdf2.rs | **136** | PBKDF2-HMAC-SHA-256 |
|
|||
|
|
| mt-mnemonic hkdf | crates/mt-mnemonic/src/hkdf.rs | **180** | HKDF-Expand RFC 5869 |
|
|||
|
|
| mt-mnemonic hmac | crates/mt-mnemonic/src/hmac.rs | **143** | HMAC-SHA-256 RFC 2104 |
|
|||
|
|
| mt-mnemonic bit_packing | crates/mt-mnemonic/src/bit_packing.rs | **135** | 24×11 бит ↔ 33 байта |
|
|||
|
|
| mt-mnemonic wordlist | crates/mt-mnemonic/src/wordlist.rs | **132** | 2048-слов wordlist + fingerprint check |
|
|||
|
|
| mt-codec | crates/mt-codec/src/lib.rs | **351** | CanonicalEncode + Domain separators (32) |
|
|||
|
|
| mt-merkle | crates/mt-merkle/src/lib.rs | **474** | Sparse Merkle Tree depth=256 |
|
|||
|
|
|
|||
|
|
**Total M1 audit surface (без тестов): ~2 921 строка.**
|
|||
|
|
|
|||
|
|
### 2.3. Тестовая инфраструктура M1
|
|||
|
|
|
|||
|
|
| Test файл | Строк | Тестов | Назначение |
|
|||
|
|
|-----------|-------|---------|------------|
|
|||
|
|
| crates/mt-crypto/tests/security_invariants.rs | 246 | 13 | Security invariants (no Clone, heap, no log) |
|
|||
|
|
| crates/mt-crypto-native/tests/kat_independent.rs | 228 | 6 | Internal regression baselines |
|
|||
|
|
| crates/mt-crypto-native/tests/nist_acvp_kat.rs | 279 | 3 | **NIST CAVP cross-check (51/51 cases)** |
|
|||
|
|
| crates/mt-mnemonic/tests/keygen_vectors.rs | 170 | 7 | 5 KAT-векторов KeyGen + determinism |
|
|||
|
|
| crates/mt-mnemonic/tests/test_vectors.rs | 121 | 6 | M-1 binding векторы (mnemonic → master_seed) |
|
|||
|
|
| crates/mt-mnemonic/tests/e2e_recovery.rs | 161 | 3 | End-to-end recovery определённый идемпотентным |
|
|||
|
|
|
|||
|
|
NIST CAVP fixtures (verified против externally downloaded NIST source):
|
|||
|
|
|
|||
|
|
| Fixture | Тестов | Источник | Размер |
|
|||
|
|
|---------|---------|---------|---------|
|
|||
|
|
| ml_dsa_65_keygen.json | 25 | NIST ACVP-Server gen-val/json-files/ML-DSA-keyGen-FIPS204 | 302 940 байт |
|
|||
|
|
| ml_kem_768_keygen.json | 25 | NIST ACVP-Server gen-val/json-files/ML-KEM-keyGen-FIPS203 | 184 841 байт |
|
|||
|
|
| ml_dsa_65_siggen_det_external_pure_empty_ctx.json | 1 | NIST ACVP-Server gen-val/json-files/ML-DSA-sigGen-FIPS204 (tgId=3, deterministic, external, pure preHash) | 19 503 байта |
|
|||
|
|
|
|||
|
|
### 2.4. Unsafe blocks (фактический подсчёт)
|
|||
|
|
|
|||
|
|
В `crates/mt-crypto/src/lib.rs` найдено **7** unsafe-блоков на строках:
|
|||
|
|
|
|||
|
|
| Строка | Контекст | Содержит `// SAFETY:` |
|
|||
|
|
|--------|----------|------------------------|
|
|||
|
|
| 168 | `impl Drop for SecretKey` — `libc::munlock` | НЕТ |
|
|||
|
|
| 187 | `fn alloc_locked_secret_box` — `libc::mlock` | НЕТ |
|
|||
|
|
| 224 | `fn keypair_from_seed` — FFI в `mt_keypair_from_seed_mldsa` | ДА (lines 225-229) |
|
|||
|
|
| 267 | `fn sign` — FFI в `mt_sign_mldsa` | ДА (lines 268-272) |
|
|||
|
|
| 282 | `fn verify` — FFI в `mt_verify_mldsa` | ДА (lines 283-286) |
|
|||
|
|
| 351 | `impl Drop for MlkemSecretKey` — `libc::munlock` | НЕТ |
|
|||
|
|
| 365 | `fn keypair_from_seed_mlkem` — FFI в `mt_keypair_from_seed_mlkem` | ДА (lines 366-372) |
|
|||
|
|
|
|||
|
|
**Итог:** 7 unsafe-блоков, из которых только 4 имеют формальный `// SAFETY:` комментарий.
|
|||
|
|
|
|||
|
|
### 2.5. Зависимости (Cargo.lock)
|
|||
|
|
|
|||
|
|
Полное дерево production-зависимостей mt-crypto:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
mt-crypto
|
|||
|
|
├── libc =0.2.169
|
|||
|
|
├── sha2 =0.10.9
|
|||
|
|
│ ├── cfg-if =1.0.4
|
|||
|
|
│ ├── cpufeatures =0.2.17
|
|||
|
|
│ └── digest =0.10.7
|
|||
|
|
│ ├── block-buffer =0.10.4
|
|||
|
|
│ └── crypto-common =0.1.7
|
|||
|
|
│ └── generic-array =0.14.7
|
|||
|
|
│ ├── typenum =1.19.0
|
|||
|
|
│ └── version_check =0.9.5
|
|||
|
|
├── zeroize =1.8.1
|
|||
|
|
└── mt-crypto-native (path)
|
|||
|
|
├── libc =0.2.169
|
|||
|
|
└── (build) openssl-src =300.5.5+3.5.5
|
|||
|
|
└── (build) cc =1.2.16
|
|||
|
|
├── jobserver =0.1.32
|
|||
|
|
└── shlex =1.3.0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Все версии закреплены exact** (`=X.Y.Z`). Это правильно для воспроизводимости. Production-зависимостей в реальном release-билде — **8** на верхнем уровне (libc, sha2, zeroize, mt-crypto-native, и transitive cfg-if, cpufeatures, digest, block-buffer, crypto-common, generic-array, typenum, version_check).
|
|||
|
|
|
|||
|
|
OpenSSL версия: **3.5.5 LTS** (vendored через openssl-src). Это **production-grade** библиотека с FIPS 140-3 валидацией, многолетней эксплуатацией в TLS-стеке, поддержкой до апреля 2030 года.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Сильные стороны
|
|||
|
|
|
|||
|
|
### 3.1. NIST FIPS conformance подтверждена независимо
|
|||
|
|
|
|||
|
|
**Самый значимый positive finding аудита.** Я скачал источники NIST CAVP test vectors напрямую с публичного репозитория `https://github.com/usnistgov/ACVP-Server` и сравнил байт-в-байт с локальными fixtures:
|
|||
|
|
|
|||
|
|
| Тест | Байт-в-байт совпадение |
|
|||
|
|
|------|--------------------------|
|
|||
|
|
| ML-DSA-65 KeyGen (25 cases, tcId 26-50) | **25/25** ✅ |
|
|||
|
|
| ML-KEM-768 KeyGen (25 cases) | **25/25** ✅ |
|
|||
|
|
| ML-DSA-65 SigGen (1 case, tgId=3 deterministic external pure empty ctx) | **1/1** ✅ |
|
|||
|
|
|
|||
|
|
Canonical SHA-256 локального ML-DSA-65 fixture: `2cbfd5571eabd93255bfee654f97b5a29d61351e11d17024cabf726b4f864b67`
|
|||
|
|
Canonical SHA-256 NIST source ML-DSA-65 группы 2: `2cbfd5571eabd93255bfee654f97b5a29d61351e11d17024cabf726b4f864b67`
|
|||
|
|
|
|||
|
|
**Это значит:** код Montana (через OpenSSL 3.5.5 LTS backend) byte-exact производит pubkey/secretkey/signature такие же, как заявлено NIST как official correct output для этих PQ алгоритмов.
|
|||
|
|
|
|||
|
|
### 3.2. Постквантовая криптография через production-grade backend
|
|||
|
|
|
|||
|
|
Архитектурное решение использовать OpenSSL 3.5.5 LTS вместо pre-1.0 RustCrypto pure-Rust крейтов **корректное** для production audit readiness:
|
|||
|
|
|
|||
|
|
- OpenSSL 3.5 имеет встроенную поддержку ML-DSA и ML-KEM начиная с этой версии
|
|||
|
|
- FIPS 140-3 валидированный криптографический модуль
|
|||
|
|
- Десятилетия эксплуатации в TLS-стеке (Apache, nginx, OpenSSH, Linux ядро, AWS, Cloudflare)
|
|||
|
|
- Audit history: множественные публичные аудиты OpenSSL Foundation и партнёров
|
|||
|
|
|
|||
|
|
Layer 2 (own thin C wrapper) **корректно реализован** — 375 строк фокусированной обвязки EVP API, читаемых и аудируемых.
|
|||
|
|
|
|||
|
|
### 3.3. Hygiena секретного материала
|
|||
|
|
|
|||
|
|
Реализация хранения секретов реализована с двумя слоями защиты:
|
|||
|
|
|
|||
|
|
1. **Heap-allocation через `Box<[u8; SECRET_KEY_SIZE]>`** — секретные байты живут в одной heap-локации от создания до уничтожения, никаких stack memcpy при move-операциях.
|
|||
|
|
2. **`libc::mlock` на heap-странице** — best-effort защита от swap-out. На macOS использует kern.maxlockedmem, на Linux требует CAP_IPC_LOCK либо адекватного RLIMIT_MEMLOCK. При неудаче — fallback на non-locked Box (полагается на encrypted swap: FileVault / LUKS).
|
|||
|
|
|
|||
|
|
`Drop` реализация для `SecretKey` и `MlkemSecretKey` правильная:
|
|||
|
|
- Сначала `self.0.zeroize()` — перезапись байтов нулями
|
|||
|
|
- Затем `libc::munlock` — освобождение mlock'а перед dealloc
|
|||
|
|
|
|||
|
|
Compile-time проверки в `tests/security_invariants.rs`:
|
|||
|
|
- `secret_key_is_not_clone` — гарантия что `SecretKey` не может быть случайно склонирован через `#[derive(Clone)]`
|
|||
|
|
- `mlkem_secret_key_is_not_clone` — то же для ML-KEM
|
|||
|
|
- `secret_key_no_partial_eq_to_prevent_timing_leak` — нет `PartialEq` (защита от timing-leak через memcmp)
|
|||
|
|
- `secret_key_is_heap_allocated` — `size_of::<SecretKey>() == size_of::<usize>()` (1 указатель)
|
|||
|
|
- `secret_key_needs_drop` — `std::mem::needs_drop::<SecretKey>()` true
|
|||
|
|
|
|||
|
|
Также **file-content scan** в тесте `no_println_or_log_on_secret_bytes_in_lib_code` — runtime проверка что в `mt-crypto/src/` нет логирующих макросов с `sk.as_bytes()`/`sk.0`/`SecretKey` references.
|
|||
|
|
|
|||
|
|
### 3.4. Memory safety FFI границы
|
|||
|
|
|
|||
|
|
Все 4 unsafe блока, реально пересекающие FFI границу к C-коду (`mt_keypair_from_seed_mldsa`, `mt_sign_mldsa`, `mt_verify_mldsa`, `mt_keypair_from_seed_mlkem`), имеют:
|
|||
|
|
- `// SAFETY:` комментарий с объяснением валидности указателей
|
|||
|
|
- Указатели на стек или heap-buffer известного размера
|
|||
|
|
- Размеры буферов соответствуют объявленным C-константам
|
|||
|
|
|
|||
|
|
C-код в `mt_crypto.c` следует правильному pattern:
|
|||
|
|
- `goto cleanup` для error handling
|
|||
|
|
- NULL-checks для всех входных указателей
|
|||
|
|
- NULL-check для `msg` только когда `msg_len != 0` — корректная edge case
|
|||
|
|
- Memory cleanup на ВСЕХ путях (включая ошибки)
|
|||
|
|
- Размеры проверяются `actual_len != expected_len` после OpenSSL вызовов
|
|||
|
|
- Deterministic Sign явно установлен через `OSSL_SIGNATURE_PARAM_DETERMINISTIC=1`
|
|||
|
|
|
|||
|
|
### 3.5. Самостоятельные реализации криптопримитивов с RFC test vectors
|
|||
|
|
|
|||
|
|
Recovery flow (mnemonic → master_seed → per-role keys) реализован собственным кодом, не зависит от внешних crypto-крейтов:
|
|||
|
|
|
|||
|
|
- **`pbkdf2_hmac_sha256`** (136 строк) — реализация по RFC 8018 §5.2, с проверкой против:
|
|||
|
|
- RFC 7914 §11 vector 1 (passwd, salt, c=1, dkLen=64)
|
|||
|
|
- RFC 7914 §11 vector 2 (Password, NaCl, c=80000, dkLen=64)
|
|||
|
|
- Public CryptoJS vector (password, salt, c=4096, dkLen=32)
|
|||
|
|
- **`hkdf_expand`** (180 строк) — реализация по RFC 5869 §2.3, с проверкой против:
|
|||
|
|
- RFC 5869 §A.1 (basic case with SHA-256)
|
|||
|
|
- RFC 5869 §A.2 (long inputs)
|
|||
|
|
- RFC 5869 §A.3 (empty info)
|
|||
|
|
- **`hmac_sha256`** (143 строки) — реализация по RFC 2104, с проверкой против:
|
|||
|
|
- RFC 4231 §4.2 case 1 (key 0x0b×20, "Hi There")
|
|||
|
|
- RFC 4231 §4.3 case 2 ("Jefe", "what do ya want for nothing?")
|
|||
|
|
- RFC 4231 §4.5 case 4 (long key, repeated 0xCD)
|
|||
|
|
- RFC 4231 §4.7 case 6 (key longer than block size — triggers SHA-256 reduction)
|
|||
|
|
|
|||
|
|
Каждая реализация прошла RFC test vectors на момент аудита (через cargo test).
|
|||
|
|
|
|||
|
|
### 3.6. Anti-brute-force защита мнемоники
|
|||
|
|
|
|||
|
|
PBKDF2 итераций: **`KDF_ITER = 1_048_576 = 2²⁰`** — на 9 порядков сильнее BIP-39 стандарта (2²¹¹ = 2048). Это сознательное усиление защиты от brute-force.
|
|||
|
|
|
|||
|
|
Wordlist binding: SHA-256 fingerprint встроенного `Montana wordlist.txt` файла проверяется при инициализации (`init_wordlist`). Mismatch = panic при первом обращении к wordlist (paranoid integrity check).
|
|||
|
|
|
|||
|
|
### 3.7. Build infrastructure
|
|||
|
|
|
|||
|
|
- `rust-toolchain.toml` — pinned channel = stable, components = rustfmt + clippy
|
|||
|
|
- `clippy.toml` — `msrv = "1.70"`
|
|||
|
|
- `.cargo/config.toml` — single-thread/single-process для предотвращения перегрева (jobs=1, RUST_TEST_THREADS=1)
|
|||
|
|
- `Cargo.toml` — `[profile.release]` с `lto = "fat"`, `codegen-units = 1`, `panic = "abort"`, `overflow-checks = true`
|
|||
|
|
- `build.rs` корректно использует `CARGO_CFG_TARGET_OS` (а не `cfg!(target_os)`) для cross-compile
|
|||
|
|
|
|||
|
|
### 3.8. Cargo audit clean
|
|||
|
|
|
|||
|
|
`cargo audit` показал:
|
|||
|
|
- **0 уязвимостей** (`vulnerabilities.found = false`, `count = 0`)
|
|||
|
|
- **0 информационных warnings** (`warnings = {}`)
|
|||
|
|
- 39 транзитивных зависимостей просканированы
|
|||
|
|
- Advisory DB: 1058 advisories, последнее обновление 2026-04-25
|
|||
|
|
|
|||
|
|
### 3.9. Cargo clippy clean
|
|||
|
|
|
|||
|
|
`cargo clippy --all-targets -- -D warnings` прошёл успешно (exit 0). Все 14 crates checked, ни одного warning.
|
|||
|
|
|
|||
|
|
### 3.10. Все тесты M1 проходят
|
|||
|
|
|
|||
|
|
Прогон тестов M1 в одно ядро / один процесс:
|
|||
|
|
|
|||
|
|
| Crate | Тестов passed | Тестов failed | Время |
|
|||
|
|
|-------|----------------|----------------|--------|
|
|||
|
|
| mt-crypto unit | 23 | 0 | 0.06s |
|
|||
|
|
| mt-crypto security_invariants | 13 | 0 | 0.01s |
|
|||
|
|
| mt-crypto-native kat_independent | 6 | 0 | 0.14s |
|
|||
|
|
| mt-crypto-native nist_acvp_kat | 3 | 0 | 0.02s |
|
|||
|
|
| mt-mnemonic unit | 57 | 0 | 136s |
|
|||
|
|
| mt-mnemonic e2e_recovery | 3 | 0 | 175s |
|
|||
|
|
| mt-mnemonic keygen_vectors | 7 | 0 | 167s |
|
|||
|
|
| mt-mnemonic test_vectors | 6 | 0 | 215s |
|
|||
|
|
|
|||
|
|
**Итог: 118 тестов passed, 0 failed.**
|
|||
|
|
|
|||
|
|
Длительность mt-mnemonic тестов объясняется PBKDF2 итерациями 2²⁰ (необходимо для anti-brute-force защиты).
|
|||
|
|
|
|||
|
|
### 3.11. Detached-keys design
|
|||
|
|
|
|||
|
|
Архитектура recovery flow **не хранит** долгосрочно privkey:
|
|||
|
|
|
|||
|
|
- Источник истины — мнемоника (24 слова на устройстве пользователя)
|
|||
|
|
- master_seed выводится из мнемоники по требованию через PBKDF2-HMAC-SHA-256
|
|||
|
|
- Per-role keys (account_key, node_key, app_encryption_key) выводятся из master_seed через HKDF-Expand
|
|||
|
|
- Privkey материализуется в памяти только в момент подписи
|
|||
|
|
|
|||
|
|
Это **правильная** структура для recovery flow.
|
|||
|
|
|
|||
|
|
### 3.12. Domain separation
|
|||
|
|
|
|||
|
|
`mt-codec` определяет 32 различных domain separators (все начинающиеся с `mt-`). Используются для разделения contexts хеширования и derivation:
|
|||
|
|
|
|||
|
|
- Hashing: `mt-op`, `mt-proposal`, `mt-bundle`, `mt-merkle-leaf`, ...
|
|||
|
|
- Identity derivation: `mt-account-key`, `mt-node-key`, `mt-app-encryption-key`
|
|||
|
|
- PBKDF2 salt: `mt-seed`
|
|||
|
|
|
|||
|
|
Domain separation предотвращает cross-protocol confusion атаки на хеши.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Слабые стороны и Findings
|
|||
|
|
|
|||
|
|
Все findings нумерованы для трассировки. Severity:
|
|||
|
|
- **CRITICAL** — может привести к компрометации secret material или consensus break
|
|||
|
|
- **HIGH** — существенный риск безопасности или discipline
|
|||
|
|
- **MEDIUM** — требует устранения для production audit
|
|||
|
|
- **LOW** — minor / cosmetic / документация
|
|||
|
|
|
|||
|
|
### F-1 [LOW] — AUDIT.md устарел: расхождение line counts
|
|||
|
|
|
|||
|
|
**Описание.** AUDIT.md (line 16) заявляет что `mt-crypto/src/lib.rs` содержит **568 строк**. Фактически **643 строки** (расхождение 75 строк, 13.2%).
|
|||
|
|
|
|||
|
|
AUDIT.md также суммирует «Total own audit surface (Layer 1 + Layer 2): 1084 lines». Фактически: 643 + 40 + 375 + 56 + 45 = **1 159 строк**.
|
|||
|
|
|
|||
|
|
**Воспроизведение.**
|
|||
|
|
```
|
|||
|
|
wc -l crates/mt-crypto/src/lib.rs crates/mt-crypto-native/src/lib.rs \
|
|||
|
|
crates/mt-crypto-native/csrc/mt_crypto.c crates/mt-crypto-native/csrc/mt_crypto.h \
|
|||
|
|
crates/mt-crypto-native/build.rs
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Воздействие.** Аудитор может пропустить новый код добавленный после написания AUDIT.md. Внешний аудитор, читающий AUDIT.md как ground truth, получит неверную картину объёма работы.
|
|||
|
|
|
|||
|
|
**Рекомендация.** Установить CI gate: при любом PR верифицировать что line counts в AUDIT.md соответствуют реальным. Либо удалить hardcoded counts из AUDIT.md и оставить только команду для проверки.
|
|||
|
|
|
|||
|
|
### F-2 [HIGH] — `cargo fmt --check` FAILS, AUDIT.md заявляет «clean»
|
|||
|
|
|
|||
|
|
**Описание.** `AUDIT.md` (раздел 7) явно заявляет: `[x] cargo fmt --all -- --check clean`. Фактический прогон возвращает exit code 1 с 48 строк diff в `mt-crypto/src/lib.rs`.
|
|||
|
|
|
|||
|
|
**Воспроизведение.**
|
|||
|
|
```
|
|||
|
|
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo fmt --all -- --check; echo "EXIT=$?"
|
|||
|
|
```
|
|||
|
|
Результат: EXIT=1, diff в строках 137, 149, 166, 235.
|
|||
|
|
|
|||
|
|
**Воздействие.**
|
|||
|
|
- Нарушение discipline формализованной в роли архитектора (CLAUDE.md): «Все четыре — зелёные. Иначе не коммитить.»
|
|||
|
|
- Несоответствие задекларированному pre-audit self-attestation
|
|||
|
|
- Демонстрирует что AUDIT.md заявления не верифицированы автоматически перед публикацией
|
|||
|
|
|
|||
|
|
**Рекомендация.** Запустить `cargo fmt --all`, закоммитить, обновить AUDIT.md self-attestation только после реального прохождения проверки. Установить pre-commit hook, отвергающий коммиты при не-чистом fmt.
|
|||
|
|
|
|||
|
|
### F-3 [MEDIUM] — Stale references к pre-migration RustCrypto в example-коде
|
|||
|
|
|
|||
|
|
**Описание.** В `crates/mt-examples/examples/m1_crypto.rs`:
|
|||
|
|
- Line 127: `print_kv("library", "ml-dsa 0.1.0-rc.8 (RustCrypto pure-Rust)");`
|
|||
|
|
- Line 299: `print_kv("internal", "ml_dsa::ExpandedSigningKey::sign_deterministic (FIPS 204 Algorithm 2, deterministic variant)");`
|
|||
|
|
|
|||
|
|
Реальная library: OpenSSL 3.5.5 LTS через own thin C wrapper. RustCrypto `ml-dsa` крейт **не присутствует** в `Cargo.lock`.
|
|||
|
|
|
|||
|
|
**Воздействие.** Пользователь, запустивший пример, видит ложную информацию о backend. При external audit это вызывает confusion: «они мигрировали с RustCrypto на OpenSSL или нет?». Свидетельствует о неполной cleanup при миграции (M1-E phase migration).
|
|||
|
|
|
|||
|
|
**Рекомендация.** Обновить строки 127, 299 в `m1_crypto.rs` на актуальную информацию: `"OpenSSL 3.5.5 LTS via own C FFI wrapper"` и `"EVP_DigestSign with OSSL_SIGNATURE_PARAM_DETERMINISTIC=1"`.
|
|||
|
|
|
|||
|
|
### F-4 [MEDIUM] — 3 unsafe блока без `// SAFETY:` комментария
|
|||
|
|
|
|||
|
|
**Описание.** В `crates/mt-crypto/src/lib.rs` следующие unsafe-блоки **не имеют** формального `// SAFETY:` префикса:
|
|||
|
|
|
|||
|
|
| Строка | Контекст | Что делает |
|
|||
|
|
|--------|----------|------------|
|
|||
|
|
| 168 | `impl Drop for SecretKey` | `libc::munlock` heap-страницы |
|
|||
|
|
| 187 | `fn alloc_locked_secret_box` | `libc::mlock` heap-страницы |
|
|||
|
|
| 351 | `impl Drop for MlkemSecretKey` | `libc::munlock` heap-страницы |
|
|||
|
|
|
|||
|
|
Объяснения в обычных комментариях есть рядом, но не следуют требуемому формату `// SAFETY:` который установлен ролью архитектора (CLAUDE.md, Code Style: «`unsafe` блоки без архитектурного обоснования (комментарий формата `// SAFETY: ...`)`»).
|
|||
|
|
|
|||
|
|
**Воздействие.** AUDIT.md (line 16) явно указывает «Все `unsafe` blocks с `// SAFETY:` комментариями (4 блока: ...)». Это и неточно (фактически 7 блоков), и неверно по содержанию (3 из 7 без формального SAFETY).
|
|||
|
|
|
|||
|
|
Сами unsafe-операции (`mlock`/`munlock`) — простые системные вызовы с известной семантикой. Реального security-риска от отсутствия SAFETY-комментария нет, но это нарушение discipline и AUDIT.md utterance.
|
|||
|
|
|
|||
|
|
**Рекомендация.** Добавить `// SAFETY:` префикс к каждому из 3 блоков с явным обоснованием (например: «pointer valid for the lifetime of the Box; size matches allocated size»).
|
|||
|
|
|
|||
|
|
### F-5 [MEDIUM] — Best-effort `mlock` без runtime warning при failure
|
|||
|
|
|
|||
|
|
**Описание.** Функция `alloc_locked_secret_box` (строки 185-193 в `mt-crypto/src/lib.rs`):
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
fn alloc_locked_secret_box(size: usize) -> Box<[u8]> {
|
|||
|
|
let boxed = vec![0u8; size].into_boxed_slice();
|
|||
|
|
unsafe {
|
|||
|
|
let _ = libc::mlock(boxed.as_ptr() as *const libc::c_void, size);
|
|||
|
|
}
|
|||
|
|
boxed
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Return code `mlock` игнорируется через `let _ =`. При failure (например `RLIMIT_MEMLOCK` exceeded на Linux без CAP_IPC_LOCK, или `kern.maxlockedmem` exceeded на macOS) `mlock` возвращает `-1`, но код продолжает работу с **non-locked** Box.
|
|||
|
|
|
|||
|
|
Это означает: secret bytes могут быть выгружены в swap при memory pressure ОС. Защитой остаётся **только** encrypted swap (FileVault / LUKS) — оборона второй линии, которая зависит от настройки системы (не гарантирована).
|
|||
|
|
|
|||
|
|
Комментарий в коде (lines 177-184) корректно описывает это как «best-effort», но **никакой runtime сигнал** не идёт пользователю/администратору о fallback.
|
|||
|
|
|
|||
|
|
**Воздействие.**
|
|||
|
|
- На systems без CAP_IPC_LOCK (типичный Docker container, default user account на Linux) `mlock` будет fail silently
|
|||
|
|
- Администратор не узнает что secret material выгружается на диск
|
|||
|
|
- При unencrypted swap — реальная утечка privkey
|
|||
|
|
|
|||
|
|
**Рекомендация.** Один из двух вариантов:
|
|||
|
|
1. Логировать через telemetry/stderr при первом failure `mlock` («WARNING: secret memory не залочена в RAM, fallback на encrypted swap»)
|
|||
|
|
2. Делать `mlock` обязательным (panic при failure), force-ить администратора настроить ulimit/CAP_IPC_LOCK
|
|||
|
|
|
|||
|
|
CLAUDE.md упоминает «Failure сигнал документируется через future telemetry, не блокирует операцию» — finding закрывается этим в roadmap.
|
|||
|
|
|
|||
|
|
### F-6 [HIGH] — Test-only `keypair()` использует слабую энтропию
|
|||
|
|
|
|||
|
|
**Описание.** В `crates/mt-crypto/src/lib.rs` строки 244-263:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
#[cfg(any(test, feature = "testing"))]
|
|||
|
|
pub fn keypair() -> (PublicKey, SecretKey) {
|
|||
|
|
let mut seed = [0u8; KEYPAIR_SEED_SIZE];
|
|||
|
|
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)...;
|
|||
|
|
hasher.update(now.to_le_bytes());
|
|||
|
|
hasher.update(std::process::id().to_le_bytes());
|
|||
|
|
let addr = &seed as *const _ as usize;
|
|||
|
|
hasher.update(addr.to_le_bytes());
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Energy: `SystemTime::now()` (наносекунды UNIX epoch) + PID + stack address → SHA-256(64 байт). Это **не CSPRNG**.
|
|||
|
|
|
|||
|
|
**Воздействие.**
|
|||
|
|
- Привязка через `#[cfg(any(test, feature = "testing"))]` означает функция доступна **только** в test-сборке либо при явном включении feature flag.
|
|||
|
|
- Если разработчик ошибочно включит `--features testing` в production-бинарь — production identity будет генерироваться с низкоэнтропийным seed.
|
|||
|
|
- Atttacker наблюдающий время запуска + знающий PID + heuristics на typical stack address может narrow-down keyspace до brute-forceable
|
|||
|
|
- Используется внутри тестов **только как sanity-check** для primitive (real identity всегда через `keypair_from_seed` из HKDF)
|
|||
|
|
|
|||
|
|
**Рекомендация.** Заменить на CSPRNG через `getrandom` крейт (или `OsRng`) даже в test-сборке. Альтернативно: разместить `keypair()` в отдельном `mt-test-utils` крейте с `dev-dependencies`-only, чтобы исключить любой риск активации в production.
|
|||
|
|
|
|||
|
|
### F-7 [LOW] — Промежуточные buffers PBKDF2/HKDF/HMAC не zeroized
|
|||
|
|
|
|||
|
|
**Описание.** В `mt-mnemonic/src/pbkdf2.rs`:
|
|||
|
|
- `t_i: Hash32`, `u_prev: Hash32`, `u_k: Hash32` — массивы 32 байта на стеке
|
|||
|
|
- `salt_with_counter: Vec<u8>` — heap-allocated
|
|||
|
|
- `dk: Vec<u8>` — выходной buffer
|
|||
|
|
|
|||
|
|
Эти буферы содержат **производные значения от password** (entropy ≡ secret после M-1 шага). После функции return:
|
|||
|
|
- Stack-resident `t_i`/`u_prev`/`u_k` могут быть в неинициализированной stack-памяти до next stack-frame overwrite
|
|||
|
|
- `salt_with_counter`/`dk` Vec drop'аются БЕЗ zeroize (Rust default Drop для Vec не zeroize)
|
|||
|
|
- `dk` возвращается caller'у — caller отвечает за zeroize
|
|||
|
|
|
|||
|
|
В `mt-mnemonic/src/hkdf.rs`: то же для `hmac_input`, `t_prev`, `t_i`.
|
|||
|
|
|
|||
|
|
В `mt-mnemonic/src/hmac.rs`:
|
|||
|
|
- `key_block`, `key_ipad`, `key_opad` — массивы 64 байта на стеке (содержат key-derived material)
|
|||
|
|
- `combined: Vec<u8>` в `sha256_concat` — heap-allocated (содержит inner padding ⊕ key plus message)
|
|||
|
|
|
|||
|
|
**Воздействие.**
|
|||
|
|
- При side-channel атаке через memory inspection (например ядро crash-дамп, debugger attach) промежуточные buffer'ы могут быть найдены ещё некоторое время
|
|||
|
|
- Production risk низкий: kernel core dump unusable без kernel exploit; debugger требует root
|
|||
|
|
- Это hygiene-issue, не immediate vulnerability
|
|||
|
|
|
|||
|
|
**Рекомендация.** Импортировать `zeroize::Zeroizing<T>` для wrappе intermediate buffers, или explicit `.zeroize()` перед drop в коде. Не критично для current risk model, но повышает defense-in-depth.
|
|||
|
|
|
|||
|
|
### F-8 [MEDIUM] — Ограниченное покрытие SigGen NIST KAT
|
|||
|
|
|
|||
|
|
**Описание.** В `nist_acvp_kat.rs` функция `nist_acvp_ml_dsa_65_siggen_deterministic_external_pure_empty_context` тестирует только **1 case** из NIST CAVP коллекции. Это test от группы **tgId=3** ML-DSA-65 (deterministic, external interface, pure preHash, empty context), tcId=40, sk начинается с `EE564C44...`.
|
|||
|
|
|
|||
|
|
NIST CAVP содержит 8 групп для ML-DSA-65 SigGen:
|
|||
|
|
- tgId=3: deterministic + external + pure (15 tests) — **тестируется 1/15**
|
|||
|
|
- tgId=4: deterministic + external + preHash (15 tests) — не тестируется
|
|||
|
|
- tgId=9, 10: deterministic + internal interface (15 + 15 tests) — не тестируется
|
|||
|
|
- tgId=15, 16, 21, 22: nondeterministic variants — не тестируется (Montana только deterministic)
|
|||
|
|
|
|||
|
|
**Воздействие.**
|
|||
|
|
- ML-DSA SigGen byte-exact conformance подтверждена только для 1 узкой комбинации параметров
|
|||
|
|
- Если OpenSSL имеет bug в обработке non-empty context — Montana code это не поймает
|
|||
|
|
- AUDIT.md acknowledges это в "Known limitations 1" — известный gap
|
|||
|
|
|
|||
|
|
**Рекомендация.** Расширить fixture до всех 15 cases tgId=3 (тот же group, разные seeds). Это даёт стабильную coverage для current Montana usage pattern (deterministic + external + pure + empty context). Расширение на не-empty context — отдельная phase когда понадобится FIPS context support.
|
|||
|
|
|
|||
|
|
### F-9 [MEDIUM] — KAT-baselines в `kat_independent.rs` self-derived, не cross-checked
|
|||
|
|
|
|||
|
|
**Описание.** В `crates/mt-crypto-native/tests/kat_independent.rs` все hardcoded SHA-256 fingerprints — **own baseline**, derived при первом прогоне теста, а не из NIST или другой external source.
|
|||
|
|
|
|||
|
|
Имя файла «kat_independent» вводит в заблуждение: «independent» здесь означает «independent of HKDF-derivation-tested-elsewhere», а не «independent reference implementation». Это **regression-tests**, не conformance-tests.
|
|||
|
|
|
|||
|
|
Real conformance-tests находятся в `nist_acvp_kat.rs` (51 case verified).
|
|||
|
|
|
|||
|
|
**Воздействие.** Минорное. Для самой conformance-проверки это OK — `nist_acvp_kat.rs` покрывает основной path (KeyGen). Но naming `kat_independent` может ввести в заблуждение auditor-а, читающего этот файл первым.
|
|||
|
|
|
|||
|
|
**Рекомендация.** Переименовать `kat_independent.rs` → `regression_baselines.rs` либо `internal_baselines.rs` для ясности. Удалить из его docstring любые claims о cross-implementation conformance.
|
|||
|
|
|
|||
|
|
### F-10 [LOW] — Self-test не проверяет против NIST output
|
|||
|
|
|
|||
|
|
**Описание.** Функция `mt_crypto::self_test()` (lines 430-460) проверяет:
|
|||
|
|
1. Sizes match
|
|||
|
|
2. Determinism (двойной KeyGen с тем же seed)
|
|||
|
|
3. Sign/verify roundtrip
|
|||
|
|
4. KAT 1 byte-exact: `keypair_from_seed([0x00; 32])` → SHA-256(pk) == hardcoded `EXPECTED_KAT_1_PK_SHA256` (own baseline)
|
|||
|
|
|
|||
|
|
KAT 1 hardcoded — **own baseline**, не NIST-derived. Если OpenSSL когда-то поменяет implementation (например baseline changes между OpenSSL 3.5.5 → 3.5.6), self_test fails — но это regression detection, не conformance proof.
|
|||
|
|
|
|||
|
|
**Воздействие.** Self-test покрывает определённый corner case (zero seed) и ловит drift, но не проверяет NIST conformance. Hardcoded values **могли быть** скопированы из failing implementation и потом self-test passing — без cross-check невозможно отличить.
|
|||
|
|
|
|||
|
|
**Рекомендация.** В `self_test()` добавить минимум 1-2 NIST CAVP test cases byte-exact (не self-derived hashes, а реальные NIST expected pk/sk). Это превратит self_test из pure-regression в **mini-conformance** check.
|
|||
|
|
|
|||
|
|
### F-11 [MEDIUM] — Cargo.lock divergence от Cargo.toml в момент аудита
|
|||
|
|
|
|||
|
|
**Описание.** В начале аудита (T20:18) Cargo.lock содержал запись `mt-timechain` (version 0.0.0). В конце аудита (T20:48) `Cargo.toml` workspace.members содержит `mt-timechain` вместо `mt-timechain`. Папка `crates/mt-timechain/` появилась во время аудита.
|
|||
|
|
|
|||
|
|
Это означает что **состояние репозитория изменилось во время аудита** — кто-то (вероятно агент архитектора в фоне или manual edit) переименовал/добавил crate.
|
|||
|
|
|
|||
|
|
**Воздействие.**
|
|||
|
|
- Аудит провёл на снимке кода в T20:18
|
|||
|
|
- Cargo build в момент T20:48 (cached) уже работает с новой структурой
|
|||
|
|
- Cargo.lock на момент чтения был **out-of-sync** с Cargo.toml — `cargo build` обновит его при следующем запуске
|
|||
|
|
|
|||
|
|
Не security finding, но методологическая нота для аудита.
|
|||
|
|
|
|||
|
|
**Рекомендация.** Внешний аудит должен проводиться на **frozen branch** (release tag), не на active development branch. Подписать audit branch git tag перед началом, audit на этот tag.
|
|||
|
|
|
|||
|
|
### F-12 [LOW] — `mnemonic.split(' ')` строгий single-space parsing
|
|||
|
|
|
|||
|
|
**Описание.** В `mt-mnemonic/src/mnemonic.rs` line 41:
|
|||
|
|
```rust
|
|||
|
|
let words: Vec<&str> = mnemonic.split(' ').collect();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Если пользователь введёт мнемонику с двойным пробелом, табом, или newline — `split(' ')` даст пустые элементы или wrong tokens, что приведёт к `MnemonicError::WordCount` либо `MnemonicError::UnknownWord`. Многие BIP-39 wallets используют `split_whitespace()` для большей user-friendly.
|
|||
|
|
|
|||
|
|
**Воздействие.**
|
|||
|
|
- UX issue: пользователь может думать что мнемоника не работает, хотя просто скопировал её с лишним whitespace
|
|||
|
|
- Не security risk: malformed input correctly rejected
|
|||
|
|
- Может быть intentional strict mode
|
|||
|
|
|
|||
|
|
**Рекомендация.** Проверить design intent. Если строгость намеренна (anti-tampering) — задокументировать в API docs. Если нет — заменить на `split_whitespace()`.
|
|||
|
|
|
|||
|
|
### F-13 [LOW] — 13 vs 12 error codes — semantic ambiguity
|
|||
|
|
|
|||
|
|
**Описание.** AUDIT.md (line 24) заявляет «13 error codes» в `mt_crypto.h`. Фактически:
|
|||
|
|
- `MT_OK = 0` (success, не error)
|
|||
|
|
- `MT_ERR_INVALID_INPUT = 1` ... `MT_ERR_SIGN_LENGTH_MISMATCH = 12` (12 error codes)
|
|||
|
|
|
|||
|
|
Итого **13 кодов** total, **12 errors**. Семантическая неоднозначность в documentation: «13 error codes» vs «12 errors + 1 ok = 13 total status codes».
|
|||
|
|
|
|||
|
|
Также: `from_code()` функция в `mt-crypto/src/lib.rs` обрабатывает 10 error variants явно + Other(c) catch-all. Не обрабатывает явно `MT_ERR_VERIFY_FAILED` (5) и `MT_ERR_KAT_MISMATCH` (6) — попадают в `Other(c)`. Не проблема (Verify возвращает bool, KAT-mismatch только из self_test), но также не отражено в error display.
|
|||
|
|
|
|||
|
|
**Рекомендация.** Уточнить AUDIT.md: «13 status codes (1 success + 12 errors)». Опционально: добавить явные variants для `VerifyFailed` и `KatMismatch` в `CryptoError` enum для полноты.
|
|||
|
|
|
|||
|
|
### F-14 [INFO] — Side-channel свойства не verified конструкцией
|
|||
|
|
|
|||
|
|
**Описание.** Constant-time свойства cryptographic operations (защита от timing-based extraction privkey) **не доказаны** для Montana code:
|
|||
|
|
|
|||
|
|
- ML-DSA/ML-KEM internal — ответственность OpenSSL (документировано как constant-time для production-grade builds)
|
|||
|
|
- Montana FFI wrapper — простой проброс, без data-dependent branches
|
|||
|
|
- HMAC/PBKDF2/HKDF собственные реализации в `mt-mnemonic` — XOR/SHA-256 операции, **в принципе constant-time** для текущей реализации, но без формального verification
|
|||
|
|
- `memcmp` нигде не используется на user-controlled secret material (хорошо)
|
|||
|
|
- Compile-time `!PartialEq` на SK types — защита от случайного `==` use
|
|||
|
|
|
|||
|
|
**Воздействие.** Без formal verification (через `subtle` crate, `dudect` testing, F* / hax) constant-time property — assumption based on code reading, не proof. На VPS (cloud neighbour shared cache) timing-based extraction теоретически возможна для не constant-time operations.
|
|||
|
|
|
|||
|
|
**Рекомендация.**
|
|||
|
|
1. Документировать threat model явно: Montana не предполагает физический доступ или cloud-neighbour atтак (single-tenant deployment)
|
|||
|
|
2. Опционально: добавить `subtle::ConstantTimeEq` для всех critical comparisons
|
|||
|
|
3. Опционально: dudect testing harness для hot path операций
|
|||
|
|
|
|||
|
|
### F-15 [INFO] — Нет fuzzing infrastructure
|
|||
|
|
|
|||
|
|
**Описание.** В репозитории нет `fuzz/` директории, нет `cargo fuzz` setup, нет AFL/libFuzzer harness'ов. AUDIT.md упоминает fuzzing как possibly-applicable в pre-prerequisite checklist, но ни один harness не присутствует.
|
|||
|
|
|
|||
|
|
**Воздействие.** FFI entry points (`mt_keypair_from_seed_mldsa`, `mt_sign_mldsa`, `mt_verify_mldsa`, `mt_keypair_from_seed_mlkem`) не fuzzed. Malformed input через FFI может вызвать crash в C-коде или OpenSSL. C-wrapper делает NULL checks, но boundary conditions (например `msg_len = SIZE_MAX`) не explicitly tested.
|
|||
|
|
|
|||
|
|
Также: `mnemonic_to_master_seed` принимает `&str` от пользователя — fuzzing на это не настроен.
|
|||
|
|
|
|||
|
|
**Рекомендация.** Установить `cargo-fuzz` harness:
|
|||
|
|
- `fuzz/fuzz_targets/fuzz_sign.rs` — fuzz `mt_crypto::sign(sk, msg)` с various sk corruption + various msg
|
|||
|
|
- `fuzz/fuzz_targets/fuzz_verify.rs` — fuzz `mt_crypto::verify(pk, msg, sig)`
|
|||
|
|
- `fuzz/fuzz_targets/fuzz_mnemonic.rs` — fuzz `mnemonic_to_master_seed`
|
|||
|
|
|
|||
|
|
С учётом доступа к серверам Moscow/Frankfurt — запустить 24-48 часов fuzzing на каждом.
|
|||
|
|
|
|||
|
|
### F-16 [INFO] — Отсутствует signature aggregation / threshold infrastructure
|
|||
|
|
|
|||
|
|
**Описание.** Заметка по архитектуре: M1 покрывает только individual key generation + sign + verify. Threshold signatures, multi-signature, signature aggregation — отсутствуют в текущем коде (что соответствует scope M1 по AUDIT.md).
|
|||
|
|
|
|||
|
|
**Воздействие.** Это **не bug** и **не finding** в M1 scope. Просто scope acknowledgment.
|
|||
|
|
|
|||
|
|
**Рекомендация.** Если будущие phases требуют threshold ML-DSA signatures (или ML-KEM-based encapsulation) — учесть что OpenSSL EVP API не предоставляет эти примитивы напрямую. Потребуется отдельный crate либо C extension.
|
|||
|
|
|
|||
|
|
### F-17 [LOW] — `serde_json` зависимость только для test fixtures parse
|
|||
|
|
|
|||
|
|
**Описание.** `mt-crypto-native/Cargo.toml` имеет `dev-dependencies`:
|
|||
|
|
```toml
|
|||
|
|
serde = { version = "=1.0.219", features = ["derive"] }
|
|||
|
|
serde_json = "=1.0.140"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Используется **только** в `tests/nist_acvp_kat.rs` для парсинга NIST JSON fixtures. Это `dev-dependencies` — не попадает в production builds.
|
|||
|
|
|
|||
|
|
`serde_json` имеет довольно большое transitive deps (proc-macro2, quote, syn, serde_derive — auxiliary build crates). Не угроза но adds dependency surface.
|
|||
|
|
|
|||
|
|
**Воздействие.** Минимальное (dev-only). Acknowledgment.
|
|||
|
|
|
|||
|
|
**Рекомендация.** Опционально: заменить на ручной JSON parser через `std::str::Lines` для NIST fixtures (avoid serde dependency). Не приоритетно.
|
|||
|
|
|
|||
|
|
### F-18 [LOW] — `parallel` feature `cc` крейта противоречит single-thread политике
|
|||
|
|
|
|||
|
|
**Описание.** В `mt-crypto-native/Cargo.toml`:
|
|||
|
|
```toml
|
|||
|
|
cc = { version = "=1.2.16", features = ["parallel"] }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`.cargo/config.toml` устанавливает `jobs = 1` глобально. Feature `parallel` для `cc` крейта позволяет parallel компиляцию C файлов. У нас единственный C файл (`mt_crypto.c`), так что фича не активирует параллелизм. Но **противоречит** заявленной политике «single-process / single-thread».
|
|||
|
|
|
|||
|
|
**Воздействие.** Поведенчески — никакого (один C файл). Документально — рассогласование с .cargo/config.toml comment.
|
|||
|
|
|
|||
|
|
**Рекомендация.** Удалить `features = ["parallel"]` из `cc` dependency для consistency.
|
|||
|
|
|
|||
|
|
### F-19 [LOW] — `OSSL_PARAM_construct_octet_string` имплицитный const-cast
|
|||
|
|
|
|||
|
|
**Описание.** В `mt_crypto.c` line 62:
|
|||
|
|
```c
|
|||
|
|
params[0] = OSSL_PARAM_construct_octet_string(
|
|||
|
|
seed_param_name, (void*)seed, seed_len
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`seed` — declared as `const uint8_t*`. Cast `(void*)seed` теоретически удаляет const. Это **convention** OpenSSL API: `OSSL_PARAM_construct_octet_string` принимает `void*`, но не модифицирует данные — но тип API не выражает immutability. Не bug, но C compiler без `-Wcast-qual` это не ловит.
|
|||
|
|
|
|||
|
|
**Воздействие.** Никакого. `OSSL_PARAM_construct_octet_string` documented как read-only on input data. Это OpenSSL API limitation, не Montana bug.
|
|||
|
|
|
|||
|
|
**Рекомендация.** Acknowledgment в SAFETY-комментарии или документация. Альтернативно: добавить C-flag `-Wno-cast-qual` в build.rs если warning ловится.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Известные ограничения этого аудита
|
|||
|
|
|
|||
|
|
Что я **не мог** проверить даже с серверным доступом:
|
|||
|
|
|
|||
|
|
1. **Side-channel attacks через физическое оборудование** — нет осциллографа, измерителя мощности, EM-зонда
|
|||
|
|
2. **Cryptanalysis самих ML-DSA / ML-KEM** — академическая работа NIST PQC competition, out of scope
|
|||
|
|
3. **Formal verification** через F\* / hax / EasyCrypt / Coq — toolchain недоступен в среде, требует переписывания кода под proof framework
|
|||
|
|
4. **Корректность OpenSSL внутри (Layer 3)** — миллионы строк C-кода, отдельный аудит OpenSSL Foundation
|
|||
|
|
5. **Bugs в компиляторе rustc/cc** — атака «Trusting Trust» (Ken Thompson 1984), требует второго независимого compilers
|
|||
|
|
6. **Документ-уровневая legal certification** — нет печати NCC Group / Trail of Bits / Quarkslab / Cure53 / Kudelski
|
|||
|
|
|
|||
|
|
Что я мог бы сделать с серверами но **не делал** в этой сессии (ограничение 1 ядро / 1 процесс + рамки времени):
|
|||
|
|
|
|||
|
|
7. **Двойная независимая Docker сборка** для верификации reproducible builds (Mac + Moscow + Frankfurt)
|
|||
|
|
8. **Long-running fuzzing** 24-48 часов через `cargo fuzz` на сервере
|
|||
|
|
9. **Cross-platform smoke testing** на Linux x86_64 vs macOS ARM64
|
|||
|
|
10. **Supply chain audit** OpenSSL bytes из openssl-src vs openssl.org official tarball SHA-256
|
|||
|
|
11. **Statistical timing measurement** на серверах для weak constant-time signal
|
|||
|
|
|
|||
|
|
Эти 5 пунктов **не были выполнены** в текущем аудите но **доступны** для расширения.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Рекомендации по приоритетам
|
|||
|
|
|
|||
|
|
### Приоритет «закрыть до production audit» (HIGH severity)
|
|||
|
|
|
|||
|
|
1. **F-2** — `cargo fmt --all`, обновить AUDIT.md self-attestation после real-prog проверки
|
|||
|
|
2. **F-6** — заменить `keypair()` test helper на CSPRNG-based, либо вынести в `mt-test-utils` крейт
|
|||
|
|
3. **F-5** — runtime warning при `mlock` failure, либо makemandatory с graceful error
|
|||
|
|
|
|||
|
|
### Приоритет «закрыть до v1.0 release» (MEDIUM severity)
|
|||
|
|
|
|||
|
|
4. **F-3** — обновить stale comments в `m1_crypto.rs` (line 127, 299)
|
|||
|
|
5. **F-4** — добавить `// SAFETY:` комментарии к 3 unused-marker блокам (lines 168, 187, 351)
|
|||
|
|
6. **F-8** — расширить SigGen NIST KAT до 15 cases tgId=3
|
|||
|
|
7. **F-9** — переименовать `kat_independent.rs` → `regression_baselines.rs`
|
|||
|
|
|
|||
|
|
### Приоритет «закрыть для документационного качества» (LOW severity)
|
|||
|
|
|
|||
|
|
8. **F-1** — sync line counts в AUDIT.md
|
|||
|
|
9. **F-10** — добавить NIST CAVP byte-exact в `self_test()` функцию
|
|||
|
|
10. **F-13** — уточнить «13 status codes (1 success + 12 errors)»
|
|||
|
|
11. **F-12** — design intent strict whitespace mode mnemonic
|
|||
|
|
12. **F-7** — `Zeroizing<T>` для PBKDF2/HKDF/HMAC intermediate state
|
|||
|
|
13. **F-17** — опционально удалить `serde_json` dev-dependency
|
|||
|
|
14. **F-18** — удалить `cc` parallel feature
|
|||
|
|
15. **F-19** — комментарий OpenSSL API const-cast convention
|
|||
|
|
|
|||
|
|
### Приоритет «infrastructure improvement» (INFO)
|
|||
|
|
|
|||
|
|
16. **F-14** — формализовать constant-time свойства (subtle crate / dudect)
|
|||
|
|
17. **F-15** — установить `cargo-fuzz` harness
|
|||
|
|
18. **F-11** — audit на frozen git tag
|
|||
|
|
|
|||
|
|
### Приоритет «требуется external auditor с физическим/легальным доступом»
|
|||
|
|
|
|||
|
|
- Side-channel hardware testing (осциллограф, power meter)
|
|||
|
|
- Formal verification ML-DSA/ML-KEM internal — ответственность OpenSSL Foundation / NIST
|
|||
|
|
- Audit firm signature (NCC Group / Trail of Bits / Quarkslab / Cure53 / Kudelski)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Итоговая оценка уровня безопасности
|
|||
|
|
|
|||
|
|
### Шкала 1-10:
|
|||
|
|
|
|||
|
|
- **10** — formally verified, audit firm signed, side-channel proven, multi-vendor reviewed, deployed at scale years
|
|||
|
|
- **9** — audited by recognized firm, side-channel constant-time documented, NIST FIPS validated implementation
|
|||
|
|
- **8** — strong cryptographic foundation, NIST conformance independently verified, minimal attack surface, comprehensive testing, documented threat model, with minor discipline/documentation findings
|
|||
|
|
- **7** — strong foundation but multiple medium-severity findings outstanding
|
|||
|
|
- **6** — code reads well but missing critical infrastructure (fuzzing, formal verification, external audit)
|
|||
|
|
- **≤5** — security-critical issues found
|
|||
|
|
|
|||
|
|
### Оценка Montana M1: **8 / 10**
|
|||
|
|
|
|||
|
|
**Обоснование оценки 8:**
|
|||
|
|
|
|||
|
|
**За что ставлю 8 (положительное):**
|
|||
|
|
1. ✅ NIST FIPS 204/203 byte-exact conformance **независимо подтверждена** (51/51 KAT)
|
|||
|
|
2. ✅ Production-grade backend — OpenSSL 3.5.5 LTS (не pre-1.0 RustCrypto)
|
|||
|
|
3. ✅ Heap-allocated SK с mlock + Drop+zeroize — правильная hygiene
|
|||
|
|
4. ✅ Compile-time security invariants (No Clone/Copy/PartialEq на SK)
|
|||
|
|
5. ✅ 0 уязвимостей в `cargo audit` (39 deps scanned, 1058 advisories)
|
|||
|
|
6. ✅ Все RFC test vectors PBKDF2/HKDF/HMAC проходят
|
|||
|
|
7. ✅ 118 тестов passed, 0 failed
|
|||
|
|
8. ✅ Clippy clean (`-D warnings`)
|
|||
|
|
9. ✅ Strict version pinning (все exact `=X.Y.Z`)
|
|||
|
|
10. ✅ Cross-platform build correctness через `CARGO_CFG_TARGET_OS`
|
|||
|
|
11. ✅ Reproducible build infrastructure prepared (Cargo.lock + rust-toolchain.toml)
|
|||
|
|
12. ✅ Thoughtful threat model (heap+mlock, deterministic Sign, no logging SK)
|
|||
|
|
|
|||
|
|
**За что снимаю 2 (отрицательное):**
|
|||
|
|
1. ❌ `cargo fmt --check` FAILS (F-2) — нарушение discipline которая декларируется
|
|||
|
|
2. ❌ AUDIT.md заявления не сверены с фактом (F-1, F-4, F-13, F-18) — документация устарела
|
|||
|
|
3. ❌ Stale references к pre-migration RustCrypto (F-3) — confusion для auditor
|
|||
|
|
4. ❌ Test-only `keypair()` использует weak entropy (F-6) — теоретический risk при misuse
|
|||
|
|
5. ❌ Best-effort `mlock` без runtime warning (F-5) — silent fallback на encrypted swap
|
|||
|
|
6. ❌ Coverage SigGen NIST KAT — только 1/15 cases в supported group (F-8)
|
|||
|
|
7. ❌ Нет fuzzing infrastructure (F-15) — FFI boundary не tested на malformed input
|
|||
|
|
8. ❌ Constant-time свойства не verified конструкцией (F-14)
|
|||
|
|
9. ❌ Нет внешней audit firm signature (требование regulator/insurer)
|
|||
|
|
10. ❌ Нет formal verification (F\* / hax / Coq)
|
|||
|
|
|
|||
|
|
**Чтобы поднять до 9:** закрыть F-2, F-3, F-4, F-5, F-6, F-8 + sync AUDIT.md (F-1) + установить fuzzing harness (F-15).
|
|||
|
|
|
|||
|
|
**Чтобы поднять до 10:** + audit firm signature + formal verification критических путей + side-channel hardware testing + multi-tenant deployment hardening.
|
|||
|
|
|
|||
|
|
### Заключение
|
|||
|
|
|
|||
|
|
Кодовая база Montana M1 (foundational crypto + identity recovery) демонстрирует **сильную инженерную дисциплину** и **независимо подтверждённую NIST FIPS 204/203 conformance**. Архитектурный выбор использовать OpenSSL 3.5.5 LTS вместо pre-1.0 RustCrypto — правильный для production audit readiness.
|
|||
|
|
|
|||
|
|
Найденные findings — преимущественно **документационная drift** (AUDIT.md устарел) и **minor security hygiene** issues. Критические уязвимости отсутствуют в audited scope.
|
|||
|
|
|
|||
|
|
Код **готов к external audit firm review** после закрытия HIGH-severity findings (F-2, F-5, F-6). До закрытия этих — рекомендую дополнительный pass самокритики со стороны команды.
|
|||
|
|
|
|||
|
|
Я **не подменяю** аудит recognized firm, не предоставляю legal certification, не закрываю side-channel и formal verification gaps. Этот отчёт — **подготовительный аудит** уровня внутренней проверки качества, который сэкономит платный audit firm часы на очевидное и предоставит им более чистую базу для критического обзора.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. Метаданные воспроизведения
|
|||
|
|
|
|||
|
|
**Команды для проверки findings (одной строкой каждая):**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && wc -l crates/mt-crypto/src/lib.rs
|
|||
|
|
```
|
|||
|
|
```
|
|||
|
|
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo fmt --all -- --check
|
|||
|
|
```
|
|||
|
|
```
|
|||
|
|
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo clippy --all-targets -- -D warnings
|
|||
|
|
```
|
|||
|
|
```
|
|||
|
|
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo audit --json
|
|||
|
|
```
|
|||
|
|
```
|
|||
|
|
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo test -p mt-crypto-native --test nist_acvp_kat -- --nocapture
|
|||
|
|
```
|
|||
|
|
```
|
|||
|
|
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo test -p mt-crypto -p mt-crypto-native -p mt-mnemonic
|
|||
|
|
```
|
|||
|
|
```
|
|||
|
|
cd /tmp && curl -sL "https://raw.githubusercontent.com/usnistgov/ACVP-Server/master/gen-val/json-files/ML-DSA-keyGen-FIPS204/internalProjection.json" -o nist_mldsa_keygen.json
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Среда выполнения:**
|
|||
|
|
- Платформа: Darwin 24.6.0 (macOS)
|
|||
|
|
- Архитектура: ARM64 (Apple Silicon)
|
|||
|
|
- rustc: 1.92.0 (Homebrew, ded5c06cf 2025-12-08)
|
|||
|
|
- cargo: 1.92.0 (Homebrew)
|
|||
|
|
- cargo-audit: установлен в `/Users/kh./.cargo/bin/cargo-audit`
|
|||
|
|
|
|||
|
|
**Доступные но не использованные ресурсы (для будущего расширения):**
|
|||
|
|
- montana-moscow (176.124.208.93, Linux x86_64)
|
|||
|
|
- montana-frankfurt (89.19.208.158, Linux x86_64)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Аудитор:** Claude Opus 4.7 (1M context)
|
|||
|
|
**Подпись модели:** `claude-opus-4-7[1m]`
|
|||
|
|
**Дата создания отчёта:** 2026-04-26
|
|||
|
|
**Идентификатор аудита:** claude-opus-4-7_2026-04-26_T201805
|