{"id":50548109,"url":"https://github.com/karpeleslab/purecrypto","last_synced_at":"2026-06-04T01:00:14.781Z","repository":{"id":360221401,"uuid":"1249198402","full_name":"KarpelesLab/purecrypto","owner":"KarpelesLab","description":"A pure-Rust, no_std cryptography toolkit: constant-time primitives, classical \u0026 post-quantum public-key, X.509, and the TLS/DTLS/QUIC stack — no foreign code, no C dependencies.","archived":false,"fork":false,"pushed_at":"2026-06-01T22:09:51.000Z","size":2872,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-01T23:23:50.193Z","etag":null,"topics":["constant-time","crypto","cryptography","no-std","post-quantum-cryptography","pure-rust","rust","tls","x509"],"latest_commit_sha":null,"homepage":"https://docs.rs/purecrypto","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/KarpelesLab.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["MagicalTux"]}},"created_at":"2026-05-25T12:59:56.000Z","updated_at":"2026-06-01T22:09:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/KarpelesLab/purecrypto","commit_stats":null,"previous_names":["karpeleslab/purecrypto"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/KarpelesLab/purecrypto","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KarpelesLab%2Fpurecrypto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KarpelesLab%2Fpurecrypto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KarpelesLab%2Fpurecrypto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KarpelesLab%2Fpurecrypto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KarpelesLab","download_url":"https://codeload.github.com/KarpelesLab/purecrypto/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KarpelesLab%2Fpurecrypto/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33886153,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["constant-time","crypto","cryptography","no-std","post-quantum-cryptography","pure-rust","rust","tls","x509"],"created_at":"2026-06-04T01:00:13.528Z","updated_at":"2026-06-04T01:00:14.596Z","avatar_url":"https://github.com/KarpelesLab.png","language":"Rust","funding_links":["https://github.com/sponsors/MagicalTux"],"categories":[],"sub_categories":[],"readme":"# purecrypto\n\n[![CI](https://github.com/KarpelesLab/purecrypto/actions/workflows/ci.yml/badge.svg)](https://github.com/KarpelesLab/purecrypto/actions/workflows/ci.yml)\n[![crates.io](https://img.shields.io/crates/v/purecrypto.svg)](https://crates.io/crates/purecrypto)\n[![docs.rs](https://img.shields.io/docsrs/purecrypto)](https://docs.rs/purecrypto)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nA cryptography toolkit written **entirely in Rust**, depending on no foreign\ncode. `purecrypto` is built from the ground up — starting at constant-time\nprimitives and working up through hashing, ciphers, bignum arithmetic, the\nclassical and post-quantum asymmetric stacks, ASN.1, X.509 and TLS — and is\nusable three ways:\n\n- as a **Rust library**,\n- as a **C library** (`cdylib` with a C ABI), and\n- as a **standalone command-line tool** (`purecrypto`: hashing, randomness, key\n  generation including PQ, CSRs, a small CA, a TLS 1.3 test client, …).\n\n\u003e Status: **work in progress.** Everything below is implemented and validated\n\u003e against published test vectors (RFCs, NIST FIPS ACVP, OpenSSL interop), but\n\u003e APIs are unstable and nothing here has been audited — do not use it for\n\u003e anything real yet.\n\n## Design principles\n\n- **No foreign code.** No C, no assembly pulled from other libraries, and no\n  third-party crypto crates. Everything is implemented here, in Rust.\n- **Constant time by default.** Secret-dependent values flow through the\n  [`ct`](src/ct) layer (branchless equality, selection, ordering) so higher\n  layers avoid timing side channels. Where an algorithm is intrinsically\n  non-constant-time (RSA keygen, modular inverse), it's used only on\n  one-time/key-generation paths and documented as such.\n- **`no_std` core.** The crate is `#![no_std]`; `alloc` and `std` are opt-in\n  features (`std` is the default and implies `alloc`).\n- **Validated.** Where a standard publishes test vectors we run them — RFC\n  8439, RFC 8032, RFC 8448, FIPS 203/204/205 ACVP — and cross-check the X.509\n  / TLS / PQC stacks against OpenSSL 3.5.\n\n## Layout\n\nSingle crate, modules gated by Cargo features:\n\n| Layer            | Module      | Status |\n| ---------------- | ----------- | ------ |\n| Constant-time    | `ct`        | ✅ implemented |\n| Hashing          | `hash`      | ✅ SHA-2, SHA-3 + Keccak-256, SHAKE/cSHAKE/KMAC/TupleHash/ParallelHash, TurboSHAKE/KangarooTwelve, BLAKE2b/2s (+keyed/X), BLAKE3, SM3, MD4/MD5/SHA-1/RIPEMD-160; HMAC + `Mac` trait (constant-time verify, drop-zeroizing). Ascon-Hash256/XOF128/CXOF128 live in `ascon` |\n| Randomness       | `rng`       | ✅ RngCore/CryptoRng, HMAC-DRBG (NIST SP 800-90A), OsRng (Unix + Windows) |\n| Symmetric cipher | `cipher`    | ✅ AES-128/192/256 (constant-time, table-free); SM4 (GB/T 32907); CBC/CFB/OFB/CTR; GCM, CCM, ChaCha20-Poly1305, XChaCha20-Poly1305, AEGIS-128L/256, AES-GCM-SIV (RFC 8452) and AES-SIV (RFC 5297, nonce-misuse-resistant) (AEAD); XTS (disk encryption); AES-KW + AES-KWP (RFC 3394 / 5649); DES + 3-DES (EDE3 / EDE2) with `Cbc64` for legacy interop. Ascon-AEAD128 lives in `ascon` |\n| MAC              | `mac`       | ✅ AES-CMAC (RFC 4493); GMAC (NIST SP 800-38D); UMAC-64 / UMAC-128 (RFC 4418); HMAC lives in `hash` |\n| Bignum (CT)      | `bignum`    | ✅ `Uint\u003cLIMBS\u003e` and runtime-sized `BoxedUint`, widening mul, Montgomery modular arith, modexp, Fermat \u0026 extended-Euclid inverse |\n| Asymmetric keys  | `rsa`       | ✅ RSA keygen (compile-time + runtime, 512–65536 bits), raw, PKCS#1 v1.5 enc/sign, OAEP enc, PSS sign/verify, PKCS#1 DER/PEM |\n| Key derivation   | `kdf`       | ✅ PBKDF2, HKDF, scrypt (RFC 7914), Argon2id/2d/2i (RFC 9106), SP 800-108 KBKDF (counter + feedback, HMAC/CMAC PRF) |\n| Elliptic curve   | `ec`        | ✅ ECDSA/ECDH on P-256/P-384/P-521/secp256k1 (runtime multi-curve) + fast const-generic P-256, X25519, Ed25519 (EdDSA, RFC 8032), X448 (RFC 7748), Ed448 (EdDSA, RFC 8032), SM2 signature + encryption (GB/T 32918 / RFC 8998) |\n| Post-quantum KEM | `mlkem`     | ✅ ML-KEM-512 / 768 / 1024 (FIPS 203), `no_std`/no-alloc; OpenSSL-interop on -768 |\n| Post-quantum sig | `mldsa`     | ✅ ML-DSA-44/65/87 (FIPS 204); hedged + deterministic; FIPS 204 ACVP + OpenSSL-interop |\n| Post-quantum sig | `slhdsa`    | ✅ SLH-DSA, all 12 sets (FIPS 205, SHA-2/SHAKE × 128/192/256 × s/f); FIPS 205 ACVP + OpenSSL-interop |\n| Stateful HBS     | `lms`       | ✅ LMS / HSS (RFC 8554, NIST SP 800-208); LM-OTS W{1,2,4,8} × LMS H{5,10,15,20,25}; **stateful** advancing key; RFC 8554 KATs |\n| Stateful HBS     | `xmss`      | ✅ XMSS / XMSS^MT (RFC 8391, NIST SP 800-208); **stateful** advancing key; RFC 8391 / reference KATs |\n| Lightweight      | `ascon`     | ✅ Ascon (NIST SP 800-232): Ascon-AEAD128 + Ascon-Hash256 / XOF128 / CXOF128 from one 320-bit permutation |\n| Diffie-Hellman   | `dh`        | ✅ Finite-field DH over RFC 3526 MODP groups (group14..group18) + RFC 4419 group-exchange, for SSH / legacy TLS / IKE interop (new code: ECDH in `ec`) |\n| ASN.1 / DER      | `der`       | ✅ DER reader/writer, base64, PEM |\n| X.509            | `x509`      | ✅ self-signed + CA issuance (RSA, ECDSA, Ed25519 \u0026 Ed448), PKCS#10 CSRs, parse, verify; PKIX SPKI; RFC 5280 nameConstraints enforcement across the chain; OpenSSL-interop |\n| TLS              | `tls`       | ✅ TLS 1.2 and 1.3, DTLS 1.2 and 1.3 client + server (sans-I/O core + blocking `Stream`); x25519/secp256r1 + X25519MLKEM768 hybrid (1.3); AES-GCM \u0026 ChaCha20-Poly1305; Ed25519/Ed448/ECDSA/RSA auth; ALPN, record_size_limit (RFC 8449), TLS-Exporter (RFC 5705); PSK session resumption + 0-RTT (early_data) with an anti-replay window (1.3); RFC 5077 session tickets (1.2); mTLS / client certificate authentication; HelloRetryRequest (client + server); bidirectional KeyUpdate; RFC 8448 KATs; DTLS HelloVerifyRequest / cookie DoS guard, handshake fragmentation + reassembly, 64-bit sliding-window anti-replay; DTLS 1.3 encrypted sequence numbers + ACK-driven retransmission. |\n| HPKE             | `hpke`     | ✅ RFC 9180 hybrid public-key encryption — 4 KEMs × 3 KDFs × 3 AEADs + ExportOnly, all four modes (Base/PSK/Auth/AuthPSK) |\n| ECH              | `ech`      | ✅ draft-ietf-tls-esni-22 Encrypted Client Hello — client + server, retry_configs, HRR confirmation signal, bit-shape GREASE |\n| QUIC             | `quic`     | ✅ QUIC v1 (RFC 9000) + QUIC-TLS (RFC 9001) + recovery / congestion (RFC 9002) + DATAGRAM extension (RFC 9221), sans-I/O |\n| Cert compression | `cert-compression` | ✅ RFC 8879 TLS 1.3 certificate compression (zlib via the `compcol` sibling crate) |\n| C ABI            | `ffi`       | ✅ hashing/HMAC + AES-CMAC + GMAC, KBKDF, RNG, AEAD (incl. AEGIS, Ascon) + AES-KW, RSA, ECDSA, Ed25519, Ed448, X25519, X448, SM2, ML-KEM, ML-DSA, SLH-DSA, LMS/XMSS, X.509, TLS / DTLS (sans-I/O); opaque handles + caller buffers; `include/purecrypto.h` |\n| CLI              | (binary)    | ✅ `hash`, `rand`, `genpkey` (classical + PQ), `pkey`, `req`, `x509` (CA), `s_client`, `s_server`, `s_dtls_client`, `s_dtls_server` |\n\n## CLI + C-API coverage matrix\n\nEach functional area below is callable from the Rust library, the\n`purecrypto` CLI, and the C ABI (`include/purecrypto.h`).\n\n| Area                                  | CLI                                  | C API                                                              |\n| ------------------------------------- | ------------------------------------ | ------------------------------------------------------------------ |\n| Hashing (SHA-2/3, BLAKE2/3, SM3, Ascon, …) | `hash`                          | `pc_digest`, `pc_hash_*`, `pc_ascon_xof`/`pc_ascon_cxof`          |\n| HMAC (SHA-1, SHA-2, SHA-3, SM3, …)    | `mac`                                | `pc_hmac`                                                          |\n| AES-CMAC (RFC 4493)                   | `mac -alg cmac`                      | `pc_cmac`                                                          |\n| GMAC (NIST SP 800-38D)                | `mac -alg gmac -nonce …`             | `pc_gmac`                                                         |\n| KDFs (HKDF, PBKDF2, scrypt, Argon2)   | `kdf hkdf\\|pbkdf2\\|scrypt\\|argon2`   | `pc_hkdf`, `pc_pbkdf2`, `pc_scrypt`, `pc_argon2`                   |\n| KBKDF (SP 800-108, counter/feedback)  | `kdf kbkdf`                          | `pc_kbkdf_counter`, `pc_kbkdf_feedback`                            |\n| AEAD (AES-GCM/CCM, ChaCha20-Poly1305, XChaCha20-Poly1305, AES-GCM-SIV, AES-SIV, AEGIS-128L/256, Ascon-AEAD128) | `enc`                                | `pc_aead_encrypt`, `pc_aead_decrypt`                               |\n| AES key wrap (RFC 3394/5649)          | `enc -alg AES-KW\\|AES-KWP`           | `pc_aes_kw_wrap/unwrap`, `pc_aes_kwp_wrap/unwrap`                  |\n| Randomness                            | `rand`                               | `pc_rand_bytes`                                                    |\n| RSA keygen + PKCS#1 sign/verify       | `genpkey`, `req`, `x509`, `pkeyutl`  | `pc_rsa_generate`, `pc_rsa_sign_pkcs1`, `pc_rsa_verify_pkcs1`      |\n| RSA-PSS sign/verify                   | `pkeyutl sign/verify -pkeyopt pss`   | `pc_rsa_sign_pss`, `pc_rsa_verify_pss`                             |\n| RSA-OAEP encrypt/decrypt              | `pkeyutl encrypt/decrypt -pkeyopt oaep` | `pc_rsa_encrypt_oaep`, `pc_rsa_decrypt_oaep`                    |\n| ECDSA keygen + sign/verify            | `genpkey -alg EC`, `pkeyutl`         | `pc_ec_generate`, `pc_ec_sign`, `pc_ec_verify`                     |\n| Ed25519 sign/verify                   | `genpkey -alg ED25519`, `pkeyutl`    | `pc_ed25519_*`                                                     |\n| Ed448 sign/verify                     | `genpkey -alg ED448`, `pkeyutl`      | `pc_ed448_*`                                                       |\n| SM2 sign/verify + encrypt/decrypt     | `genpkey -alg SM2`, `pkeyutl`        | `pc_sm2_*`                                                         |\n| ECDH on NIST curves                   | `kex -alg ECDH-P{256,384,521}`       | `pc_ecdh`                                                          |\n| X25519                                | `kex -alg X25519`                    | `pc_x25519`, `pc_x25519_public`                                    |\n| X448                                  | `kex -alg X448`                      | `pc_x448`, `pc_x448_public`                                        |\n| ML-KEM (FIPS 203)                     | `kem keygen\\|encaps\\|decaps`         | `pc_mlkem_*`                                                       |\n| ML-DSA (FIPS 204)                     | `pkeyutl sign/verify` (ML-DSA keys)  | `pc_mldsa_*`                                                       |\n| SLH-DSA (FIPS 205)                    | `pkeyutl sign/verify` (SLH-DSA keys) | `pc_slhdsa_*`                                                      |\n| LMS / HSS (RFC 8554, stateful)        | `genpkey -alg LMS-…\\|HSS-…`, `pkeyutl` | `pc_lms_*`, `pc_hss_*`                                          |\n| XMSS / XMSS^MT (RFC 8391, stateful)   | `genpkey -alg XMSS-…\\|XMSSMT-…`, `pkeyutl` | `pc_xmss_*`, `pc_xmssmt_*`                                  |\n| CSR (PKCS#10)                         | `req`                                | `pc_csr_create_rsa`, `pc_csr_from_pem`, `pc_csr_verify_self_signed`|\n| X.509 certificate parse + verify      | `x509`, `ca`                         | `pc_cert_*`, `pc_ec_self_signed_pem`                               |\n| CRL parse + verify                    | `crl`                                | `pc_crl_*`                                                         |\n| TLS 1.2 / 1.3 client + server         | `s_client`, `s_server`               | `pc_tls_cfg_*`, `pc_tls_*` (memory-BIO style)                      |\n| DTLS 1.2 / 1.3 client + server        | `s_dtls_client`, `s_dtls_server`     | `pc_tls_cfg_*` (`PC_DTLS_1_*` selector), `pc_dtls_next_timeout/on_timeout` |\n\nThe C ABI is sans-I/O for TLS/DTLS: the caller pumps wire bytes through\n`pc_tls_feed` / `pc_tls_pop` and application bytes through `pc_tls_send` /\n`pc_tls_recv` (mirrors OpenSSL's `BIO_s_mem`).\n\n## Cargo features\n\nDefault is `std + cli` with every module on. Disable defaults for a `no_std`\nbuild and re-enable only what you need:\n\n```toml\n# Bare no_std, no allocator: just `ct` and primitives that fit.\npurecrypto = { version = \"0.3\", default-features = false }\n\n# no_std core + ML-KEM-768 (no alloc):\npurecrypto = { version = \"0.3\", default-features = false, features = [\"mlkem\"] }\n\n# Library with PQ signing only:\npurecrypto = { version = \"0.3\", default-features = false, features = [\"mldsa\", \"slhdsa\"] }\n```\n\nModule gates: `hash`, `cipher`, `mac`, `kdf`, `bignum`, `rng`,\n`linux-getrandom`, `rsa`, `dh`, `der`, `ec`, `x509`, `tls`, `dtls`, `quic`,\n`mlkem`, `mldsa`, `slhdsa`, `hpke`, `ech`, `cert-compression`, `ffi`, `cli`.\nEach pulls in only its own dependencies. `alloc` is required by anything that\nneeds heap (most things except `ct`, `hash`, `cipher`, and the no-alloc\n`mlkem` core).\n\n## Building\n\n```sh\ncargo build                                          # default: std + CLI binary\ncargo build --no-default-features                    # bare no_std\ncargo build --no-default-features --features alloc   # no_std + alloc\ncargo test                                           # full suite\ncargo test --release -- --ignored                    # heavy KATs (SLH-DSA 's' sets, RSA keygen)\n```\n\nRequires Rust 1.95+ (edition 2024).\n\n## Command-line tool\n\nThe `purecrypto` binary (built by default; or `cargo build --features cli`).\nEvery subcommand reads `stdin` when no `-in` is given and writes to `stdout`\nwhen no `-out` is given, so commands compose with pipes.\n\n### `hash` — message digests\n\n```sh\npurecrypto hash sha256 file.txt              # one-shot digest\necho -n abc | purecrypto hash sha3-256       # any algorithm from the `hash` module\n```\n\nAlgorithms: `sha224`, `sha256`, `sha384`, `sha512`, `sha512-224`, `sha512-256`,\n`sha3-224`, `sha3-256`, `sha3-384`, `sha3-512`, `keccak256`, `blake2b256`,\n`blake2b384`, `blake2b512`, `blake2s256`, `blake3`, `sm3`, `sha1`, `md5`,\n`ripemd160`. (The XOFs `shake128`/`shake256` and the BLAKE2X/cSHAKE/KMAC\nvariants are exposed through the Rust library, not the CLI.)\n\n### `rand` — randomness\n\n```sh\npurecrypto rand 32              # 32 random bytes as hex\npurecrypto rand 16 --binary     # raw bytes to stdout\n```\n\n### `genpkey` — key generation (classical and post-quantum)\n\n```sh\n# Classical\npurecrypto genpkey -algorithm RSA -bits 2048   -out rsa.pem    # also 3072, 4096\npurecrypto genpkey -algorithm RSA -bits 8192   -out rsa8k.pem  # any even size, 512..=65536\npurecrypto genpkey -algorithm EC  -curve P-256 -out ec.pem     # or P-384, P-521, secp256k1\npurecrypto genpkey -algorithm ED25519          -out ed.pem\n\n# Post-quantum signatures (FIPS 204 / FIPS 205)\npurecrypto genpkey -algorithm ML-DSA-44               -out mldsa44.pem\npurecrypto genpkey -algorithm ML-DSA-65               -out mldsa65.pem\npurecrypto genpkey -algorithm ML-DSA-87               -out mldsa87.pem\npurecrypto genpkey -algorithm SLH-DSA-SHA2-128f       -out slh128f.pem\npurecrypto genpkey -algorithm SLH-DSA-SHAKE-256s      -out slh256s.pem\n\n# Post-quantum KEM (FIPS 203) — all three security levels\npurecrypto genpkey -algorithm ML-KEM-512              -out mlkem512.pem\npurecrypto genpkey -algorithm ML-KEM-768              -out mlkem768.pem\npurecrypto genpkey -algorithm ML-KEM-1024             -out mlkem1024.pem\n```\n\nThe full SLH-DSA matrix is supported:\n`SLH-DSA-{SHA2,SHAKE}-{128,192,256}{s,f}` (12 parameter sets).\n\nOutput format:\n- RSA → `-----BEGIN RSA PRIVATE KEY-----` (PKCS#1)\n- EC → `-----BEGIN EC PRIVATE KEY-----` (SEC1)\n- Ed25519 / ML-DSA / ML-KEM / SLH-DSA → `-----BEGIN PRIVATE KEY-----` (PKCS#8,\n  algorithm identified by the embedded OID)\n\n\u003e **PKCS#8 interop note.** purecrypto uses the simple PKCS#8 form — `OCTET\n\u003e STRING` containing the raw expanded key bytes — for every PQ scheme. This\n\u003e matches OpenSSL 3.5 byte-for-byte for SLH-DSA, and OpenSSL parses the\n\u003e resulting private keys directly. For ML-DSA and ML-KEM, OpenSSL writes a\n\u003e richer `SEQUENCE { seed, expanded }` form; purecrypto's PEM round-trips\n\u003e through itself but may not load into OpenSSL as a private key. Public-key\n\u003e SPKI is fully interoperable for every scheme.\n\n### `pkey` — inspect or convert a key\n\n```sh\npurecrypto pkey -in key.pem -text     # describe the key\npurecrypto pkey -in key.pem -pubout   # emit the SPKI public-key PEM\npurecrypto pkey \u003c key.pem             # re-emit the private key (round-trip)\n```\n\n`pkey` auto-detects every supported flavor (RSA PKCS#1, EC SEC1, and the PKCS#8\ntypes above) and routes by the embedded OID for PKCS#8 inputs.\n\n### `req` — PKCS#10 certificate signing requests\n\n```sh\npurecrypto req -key leaf.pem -subj \"/CN=leaf.example/O=Acme\" \\\n               -addext \"subjectAltName=DNS:leaf.example,DNS:www.leaf.example\" \\\n               -out leaf.csr\npurecrypto req -in leaf.csr -verify       # check the CSR self-signature\n```\n\n### `x509` — self-signed certificates and a small CA\n\n```sh\n# Build a self-signed CA cert\npurecrypto x509 -new --ca -key ca.pem -subj \"/CN=Internal CA\" -out ca.crt\n\n# Issue a leaf certificate from a CSR\npurecrypto x509 -req -in leaf.csr -CA ca.crt -CAkey ca.pem -out leaf.crt\n\n# Inspect a certificate\npurecrypto x509 -in leaf.crt -text\n```\n\n### `s_client` — TLS 1.3 test client\n\n```sh\npurecrypto s_client -connect example.com:443\npurecrypto s_client -connect 127.0.0.1:8443 -CAfile ca.crt -servername leaf.example\npurecrypto s_client -connect 127.0.0.1:8443 -insecure -quiet      # skip cert verify, stdin → server\n\n# Negotiate HTTP/2 (or fall back to http/1.1) via ALPN\npurecrypto s_client -connect example.com:443 -alpn h2,http/1.1\n\n# Dump the negotiated secrets in NSS SSLKEYLOGFILE format — Wireshark can\n# then decrypt the captured pcap.\npurecrypto s_client -connect example.com:443 -keylogfile sslkeys.log\n\n# Present a client certificate (mTLS). The key may be Ed25519 (PKCS#8) or\n# ECDSA (SEC1).\npurecrypto s_client -connect server:443 -cert client.pem -key client.key\n```\n\nThe client offers `X25519MLKEM768` (post-quantum hybrid) first, then `x25519`\nand `secp256r1`; all three TLS 1.3 cipher suites\n(`TLS_AES_128_GCM_SHA256`, `TLS_AES_256_GCM_SHA384`,\n`TLS_CHACHA20_POLY1305_SHA256`); and Ed25519, Ed448, ECDSA, and RSA peer signatures.\n\n### `s_server` — TLS 1.3 echo / `-www` server\n\nA one-shot test server: it binds, accepts one connection, performs the\nhandshake, exchanges data, and exits.\n\n```sh\n# Plain TLS echo:\npurecrypto s_server -cert server.pem -key server.key -accept 4433\n\n# Serve a fixed HTTP response (text/plain) for one request:\npurecrypto s_server -cert server.pem -key server.key -accept 4433 -www\n\n# Negotiate ALPN, listen on 8443:\npurecrypto s_server -cert server.pem -key server.key -accept 8443 -alpn h2,http/1.1\n\n# mTLS: require + verify a client cert against the bundle in `client-ca.pem`.\npurecrypto s_server -cert server.pem -key server.key -accept 8443 \\\n                    -Verify client-ca.pem\n```\n\n### TLS 1.2\n\n`s_client` / `s_server` default to TLS 1.3. Pass `-tls1_2` on either side\nto force TLS 1.2. The TLS 1.2 path is ECDHE-AEAD only (AES-GCM and\nChaCha20-Poly1305) and supports mTLS plus RFC 5077 session tickets.\n\n```sh\n# Server (TLS 1.2)\npurecrypto s_server -tls1_2 -accept 0.0.0.0:4443 -cert cert.pem -key key.pem\n\n# Client (TLS 1.2)\npurecrypto s_client -tls1_2 -connect example.com:443 -CAfile roots.pem\n```\n\n### DTLS — `s_dtls_client` / `s_dtls_server`\n\nDTLS runs the TLS handshake over UDP. Either use the dedicated\n`s_dtls_client` / `s_dtls_server` binaries, or pass `-dtls1_2` / `-dtls1_3`\nto `s_client` / `s_server`. The two forms are equivalent.\n\n```sh\n# DTLS 1.2 echo\npurecrypto s_dtls_server -dtls1_2 -accept 0.0.0.0:5684 -cert cert.pem -key key.pem\npurecrypto s_dtls_client -dtls1_2 -connect localhost:5684\n\n# DTLS 1.3 echo\npurecrypto s_dtls_server -dtls1_3 -accept 0.0.0.0:5685 -cert cert.pem -key key.pem\npurecrypto s_dtls_client -dtls1_3 -connect localhost:5685\n\n# Equivalent via s_client / s_server with version flags\npurecrypto s_server -dtls1_3 -accept 0.0.0.0:5685 -cert cert.pem -key key.pem\npurecrypto s_client -dtls1_3 -connect localhost:5685\n```\n\nThe DTLS server stands up a HelloVerifyRequest cookie exchange (1.2) or\nHelloRetryRequest cookie (1.3) before allocating any per-connection\nstate, and both directions install a 64-bit sliding-window replay\nfilter once the handshake-protected keys are in place. The default\nrecord size is 1200 bytes to stay below common path MTUs; override with\n`-mtu`.\n\n### Cookbook\n\nEnd-to-end CA + leaf with EC keys:\n\n```sh\npurecrypto genpkey -algorithm EC -curve P-256 -out ca.pem\npurecrypto x509 -new --ca -key ca.pem -subj \"/CN=My CA\" -out ca.crt\n\npurecrypto genpkey -algorithm EC -curve P-256 -out leaf.pem\npurecrypto req -key leaf.pem -subj \"/CN=leaf.example\" \\\n               -addext \"subjectAltName=DNS:leaf.example\" -out leaf.csr\npurecrypto x509 -req -in leaf.csr -CA ca.crt -CAkey ca.pem -out leaf.crt\n```\n\nA post-quantum signature key and its public counterpart:\n\n```sh\npurecrypto genpkey -algorithm ML-DSA-65 -out mldsa.pem\npurecrypto pkey -in mldsa.pem -text                       # ML-DSA-65 private key\npurecrypto pkey -in mldsa.pem -pubout \u003e mldsa.pub.pem     # PKIX SPKI\n```\n\nA two-process mTLS handshake on a single host (client cert presented to the\nserver, both keys Ed25519):\n\n```sh\n# CA + server cert + client cert\npurecrypto genpkey -algorithm ED25519 -out ca.pem\npurecrypto x509 -new --ca -key ca.pem -subj \"/CN=Local CA\" -out ca.crt\npurecrypto genpkey -algorithm ED25519 -out server.pem\npurecrypto req -key server.pem -subj \"/CN=127.0.0.1\" \\\n               -addext \"subjectAltName=DNS:127.0.0.1\" -out server.csr\npurecrypto x509 -req -in server.csr -CA ca.crt -CAkey ca.pem -out server.crt\npurecrypto genpkey -algorithm ED25519 -out client.pem\npurecrypto req -key client.pem -subj \"/CN=alice\" -out client.csr\npurecrypto x509 -req -in client.csr -CA ca.crt -CAkey ca.pem -out client.crt\n\n# In one terminal — server requires + verifies client certs against ca.crt:\npurecrypto s_server -cert server.crt -key server.pem -accept 8443 -Verify ca.crt -www\n\n# In another terminal — client presents its cert + key:\npurecrypto s_client -connect 127.0.0.1:8443 -CAfile ca.crt \\\n                    -cert client.crt -key client.pem -alpn http/1.1 \\\n                    -keylogfile keys.log\n```\n\n## Library usage\n\nIdiomatic Rust API — see [docs.rs/purecrypto](https://docs.rs/purecrypto) for\nthe full reference. A few common patterns:\n\n```rust\nuse purecrypto::hash::{Digest, Sha256};\nlet d = Sha256::digest(b\"abc\");\n\nuse purecrypto::ec::Ed25519PrivateKey;\nuse purecrypto::rng::OsRng;\nlet sk = Ed25519PrivateKey::generate(\u0026mut OsRng);\nlet sig = sk.sign(b\"hello\");\nsk.public_key().verify(b\"hello\", \u0026sig).unwrap();\n\nuse purecrypto::mldsa::MlDsa65PrivateKey;\nlet (sk, pk) = MlDsa65PrivateKey::generate(\u0026mut OsRng);\nlet sig = sk.sign(\u0026mut OsRng, b\"hello\", b\"\").unwrap();\nassert!(pk.verify(\u0026sig, b\"hello\", b\"\"));\n\nuse purecrypto::mlkem::MlKem768DecapsKey;\nlet (dk, ek) = MlKem768DecapsKey::generate(\u0026mut OsRng);\nlet (ct, ss_a) = ek.encapsulate(\u0026mut OsRng);\nlet ss_b = dk.decapsulate(\u0026ct);\nassert_eq!(ss_a, ss_b);\n```\n\n### Versions and transports\n\n`purecrypto` ships both TLS (TCP) and DTLS (UDP) at two protocol\nversions each:\n\nAll four versions (TLS 1.2, TLS 1.3, DTLS 1.2, DTLS 1.3) and both roles\n(client, server) share **one** public API: [`tls::Config`] +\n[`tls::Connection`]. The version is selected by\n`Config::builder().versions(min, max).build()`; the role is selected at\nconnection-construction time via `Connection::client(\u0026cfg)` or\n`Connection::server(\u0026cfg)`.\n\n- **TLS 1.2** is ECDHE-AEAD only (AES-128/256-GCM, ChaCha20-Poly1305) —\n  no static RSA, no static DH, no CBC. Forward secrecy by construction.\n  Includes mTLS and RFC 5077 stateless session tickets.\n- **TLS 1.3** is the full RFC 8446 with PSK resumption, 0-RTT,\n  exporter, ALPN, mTLS, and downgrade-detection.\n- **DTLS 1.2** (RFC 6347) carries the TLS 1.2 handshake over UDP with\n  HelloVerifyRequest cookies, handshake fragmentation/reassembly,\n  replay protection, and retransmission. Negotiates the same\n  ECDHE-AEAD suites × groups × signature schemes the TLS 1.2 path\n  supports.\n- **DTLS 1.3** (RFC 9147) carries the TLS 1.3 handshake over UDP with\n  selective ACK reliability, encrypted sequence numbers, and a\n  HelloRetryRequest cookie. Negotiates the same TLS 1.3 suites,\n  groups (including `X25519MLKEM768`), and signature schemes as the\n  TLS 1.3 path.\n\n### TLS 1.3\n\nThe `tls` module is a sans-I/O TLS 1.3 implementation with a thin\n`std::io::Read + Write` adapter for blocking TCP. The full feature surface,\nconfigured per side:\n\n```text\n// Client (TLS or DTLS, any version):\nConfig::builder()\n    .versions(ProtocolVersion::TLSv1_2, ProtocolVersion::TLSv1_3)\n    .roots(roots)\n    .server_name(\"example.com\")\n    .alpn(vec![b\"h2\".to_vec(), b\"http/1.1\".to_vec()])\n    .record_size_limit(4096)             // RFC 8449\n    .identity(client_chain, client_key)  // mTLS (any SigningKey)\n    .build();\n\n// Server (TLS or DTLS, any version):\nConfig::builder()\n    .tls_only()                          // shorthand for versions(TLSv1_2, TLSv1_3)\n    .identity(chain, SigningKey::Rsa(rsa) | SigningKey::Ecdsa(ec) | ...)\n    .alpn(...)\n    .ticket_key([0u8; 32])               // enables NewSessionTicket emission\n    .max_early_data(16384)               // accept up to N bytes of 0-RTT\n    .client_auth(ClientAuth { roots, required: true }) // mTLS\n    .build();\n\n// DTLS variant:\nConfig::builder()\n    .dtls()                              // shorthand for versions(DTLSv1_2, DTLSv1_3)\n    .identity(chain, key)\n    .cookie_secret([0u8; 32])            // amplification defense\n    .max_record_size(1200)               // MTU ceiling\n    .build();\n\nlet mut conn = Connection::client(\u0026cfg)?;   // or Connection::server(\u0026cfg)\n```\n\nAfter a handshake completes, both sides expose:\n\n- `connection.alpn_protocol()` — the negotiated ALPN name, if any.\n- `connection.tls_exporter(label, context, out)` — RFC 8446 §7.5 / RFC 5705\n  application-layer keying material.\n- `connection.peer_certificates()` — the validated chain (leaf first).\n- (client) `connection.take_session()` — moves out a `StoredSession`\n  derived from the server's NewSessionTicket; pass it to\n  `ClientConfig::with_session` next time you connect to the same server.\n- (client) `connection.write_early_data(\u0026[u8])` — sends application data\n  under the early-traffic key before `ServerHello` arrives, valid only on a\n  resumed connection whose session enabled 0-RTT.\n\n**0-RTT replay caveat.** RFC 8446 §8: 0-RTT data is replayable by an active\nattacker, since the server cannot bind the early bytes to a unique\nclient-server handshake instance. The provided `ReplayWindow` blocks repeated\nbinders within a process, but cross-process / cross-server replay defenses\nare application-level. Mark any data sent via `write_early_data` as\nidempotent (a HEAD/GET, an idempotent RPC, …) and never as a state-changing\nwrite.\n\n```rust,no_run\nuse purecrypto::tls::{Config, Connection, HandshakeStatus, RootCertStore};\n\nlet roots = RootCertStore::new();        // populate from a PEM bundle …\nlet cfg = Config::builder()\n    .tls_only()\n    .roots(roots)\n    .server_name(\"example.com\")\n    .alpn(vec![b\"h2\".to_vec(), b\"http/1.1\".to_vec()])\n    .build();\nlet mut conn = Connection::client(\u0026cfg).unwrap();\n\n// Drive the handshake: pop wire bytes from `conn`, send them, recv from\n// the peer, feed them back. The sans-I/O surface is the same for TLS and\n// DTLS — the only difference is \"stream\" vs \"datagram\" framing.\n# fn _h(_: Connection) -\u003e std::io::Result\u003c()\u003e { Ok(()) }\n```\n\n\n### Signature algorithms\n\nX.509 chain validation and TLS 1.3 `CertificateVerify` both dispatch through\nthe [`signature_registry`](src/signature_registry.rs) module. Every signature\nprimitive purecrypto can do appears as a registry entry; a strict whitelist\n[`SignaturePolicy`] controls which ones a verifier will accept.\n\n#### Registry\n\n| `id` (whitelist key)        | X.509 OID                       | TLS 1.3 scheme | Default `modern()` |\n| --------------------------- | ------------------------------- | -------------- | ------------------ |\n| `rsa-pkcs1-sha1`            | `1.2.840.113549.1.1.5`          | (none)         | opt-in |\n| `rsa-pkcs1-sha256`          | `1.2.840.113549.1.1.11`         | `0x0401`       | ✅ |\n| `rsa-pkcs1-sha384`          | `1.2.840.113549.1.1.12`         | `0x0501`       | ✅ |\n| `rsa-pkcs1-sha512`          | `1.2.840.113549.1.1.13`         | (none)         | opt-in |\n| `rsa-pss-rsae-sha256`       | `1.2.840.113549.1.1.11` (RSAE)  | `0x0804`       | ✅ |\n| `rsa-pss-rsae-sha384`       | `1.2.840.113549.1.1.12` (RSAE)  | `0x0805`       | ✅ |\n| `rsa-pss-rsae-sha512`       | `1.2.840.113549.1.1.13` (RSAE)  | `0x0806`       | ✅ |\n| `rsa-pss-pss-sha256`        | `1.2.840.113549.1.1.10` (PSS-keys) | (none)      | opt-in |\n| `ecdsa-with-sha256`         | `1.2.840.10045.4.3.2` (any curve) | (none)       | ✅ |\n| `ecdsa-with-sha384`         | `1.2.840.10045.4.3.3` (any curve) | (none)       | ✅ |\n| `ecdsa-with-sha512`         | `1.2.840.10045.4.3.4` (any curve) | (none)       | ✅ |\n| `ecdsa-secp256r1-sha256`    | (TLS-only — strict curve)       | `0x0403`       | ✅ |\n| `ecdsa-secp384r1-sha384`    | (TLS-only — strict curve)       | `0x0503`       | ✅ |\n| `ecdsa-secp521r1-sha512`    | (TLS-only — strict curve)       | `0x0603`       | ✅ |\n| `ecdsa-secp256r1-sha384/512`, `ecdsa-secp384r1-sha256/512`, `ecdsa-secp521r1-sha256/384` | cross-hash, policy-only | (none) | opt-in |\n| `ecdsa-secp256k1-sha256/384/512` | secp256k1, policy-only      | (none)         | opt-in |\n| `ed25519`                   | `1.3.101.112`                   | `0x0807`       | ✅ |\n| `ml-dsa-44` / `-65` / `-87` | `2.16.840.1.101.3.4.3.17/18/19` | `0x0904/05/06` | ✅ (NIST FIPS 204) |\n| `slh-dsa-sha2-128s/128f/192s/192f/256s/256f`, `slh-dsa-shake-128s/128f/192s/192f/256s/256f` | `2.16.840.1.101.3.4.3.20..31` | (none) | opt-in (FIPS 205) |\n\nThe matched-curve / matched-hash ECDSA pairs (e.g. P-256 + SHA-256) have IANA\nTLS scheme codes; cross-hash pairs and all secp256k1 entries are reachable for\nchain dispatch via the OID-keyed `ecdsa-with-shaN` entries — which accept any\nsupported curve — and as fine-grained policy-keyed entries for TLS opt-in.\n\nML-DSA is on the default whitelist (the modern PQC future). SLH-DSA's twelve\nparameter sets are registered but never on the default whitelist: signatures\nare 7–50 KB and rarely the right default for X.509 leaves.\n\n#### Configuring the policy\n\n```rust\nuse purecrypto::signature_registry::SignaturePolicy;\nuse purecrypto::tls::{Config, RootCertStore};\n\nlet roots = RootCertStore::new();\n\n// Default — modern IANA-blessed set, RSA ≥ 2048 bits.\nlet cfg = Config::builder().roots(roots).build();\n\n// Legacy interop: accept SHA-1 RSA and lower the RSA-bit floor to 1024.\nlet roots = RootCertStore::new();\nlet cfg = Config::builder()\n    .roots(roots)\n    .signature_policy(\n        SignaturePolicy::modern()\n            .permit(\"rsa-pkcs1-sha1\")\n            .with_min_rsa_bits(1024),\n    )\n    .build();\n\n// PQC-strict: only ML-DSA + Ed25519, refuse everything classical.\nlet roots = RootCertStore::new();\nlet cfg = Config::builder()\n    .roots(roots)\n    .signature_policy(\n        SignaturePolicy::empty()\n            .permit(\"ml-dsa-65\")\n            .permit(\"ml-dsa-87\")\n            .permit(\"ed25519\"),\n    )\n    .build();\n\n// SLH-DSA chains: opt in to a single set the application expects.\nlet roots = RootCertStore::new();\nlet cfg = Config::builder()\n    .roots(roots)\n    .signature_policy(SignaturePolicy::modern().permit(\"slh-dsa-sha2-128f\"))\n    .build();\n```\n\n`signature_policy` on the unified [`Config`] applies to both client and\nserver roles — for the server it gates client-certificate validation under\nmTLS. The policy is a strict whitelist:\nadding an entry to the registry does NOT auto-permit it — the caller has to\nadd the id explicitly.\n\n## C library\n\nPrebuilt archives — the `purecrypto` CLI, the static (`.a`/`.lib`) and shared\n(`.so`/`.dylib`/`.dll`) C libraries, and the header — are attached to each\n[GitHub release](https://github.com/KarpelesLab/purecrypto/releases) for Linux,\nmacOS, and Windows.\n\nThe same code is callable from C via the `ffi` feature. Because the crate stays\n`rlib` by default (so the `no_std` build is unaffected), produce the C library\nwith `cargo rustc`:\n\n```sh\ncargo rustc --lib --release --features ffi --crate-type cdylib    # → target/release/libpurecrypto.so\ncargo rustc --lib --release --features ffi --crate-type staticlib # → target/release/libpurecrypto.a\n\n# Static link (self-contained):\ncc app.c -I include target/release/libpurecrypto.a -lpthread -ldl -lm -o app\n```\n\nThe API is declared in [`include/purecrypto.h`](include/purecrypto.h): one-shot\nand streaming hashing, HMAC, OS randomness, RSA/ECDSA/Ed25519 key generation,\nsigning, verification and PEM I/O, ML-KEM (FIPS 203) / ML-DSA (FIPS 204) /\nSLH-DSA (FIPS 205) keys, X.509 parsing/verification, and a sans-I/O TLS /\nDTLS surface (`pc_tls_cfg_*`, `pc_tls_*`) including post-handshake\naccessors for the negotiated cipher suite and peer SNI. Functions return a\n`pc_status` code; variable-length output uses an in/out length buffer;\nstateful objects are opaque handles freed by the library; panics never\ncross the boundary.\n\n## License\n\nLicensed under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarpeleslab%2Fpurecrypto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarpeleslab%2Fpurecrypto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarpeleslab%2Fpurecrypto/lists"}