270 likes | 282 Views
EverCrypt is a comprehensive and verifiable library that optimizes for different platforms, architectures, and algorithms on the fly. It aims to provide a high-performance and secure software stack for real-world applications.
E N D
The EverCrypt Verified Library MicrosoftResearch CMU: B. Parno, A. Fromherz INRIA: K. Bhargavan, B. Beurdouche, M. Polubelova MSR-Cambridge: A. Delignat-Lavaud, C. Fournet, J. Choi, S. Zanella MSR-Redmond: C. Hawblitzel, J. Protzenko, T. Ramananandro, N. Swamy MSR-Bangalore: A. Rastogi Rosario: G. Martinez
Verifying cryptography • An essential pursuit in the quest for a secure software stack (e.g. TLS) • Many successes: Fiat, VST, CryptoLine, Cryptol/SAW, Vale, HACL*/Low* • However: Real-world applications want a high-performance library that optimizes for separate platforms, architectures, and can seamlessly pick the best algorithm on the fly. (see Linux/ZINC, OpenSSL, etc.)
The features of an ideal library (programmer) • Usable: preferably in C or ASM, not “exotic” languages • Comprehensive: one algorithm per processor generation / bitsize • Auto-configurable: best algorithm picked automatically • Deep integration: ASM, e.g. for the multiplication, C, e.g. for the rest • Abstraction: clients deal with a unified API for each family
The features of an ideal library (researcher) • Verifiable: written in a language amenable to verification • Programmer productivity: share as much code as possible / agile • Auto-configurable: doesn’t blue-screen with “missing instruction” • Deep integration: each implementation verifies against the same spec • Abstraction: clients need not know any implementation details We are aiming for a comprehensive verification result without compromising performance.
EverCrypt mediates between (possibly verified) clients and different implementations C client miTLS Merkle tree clients EverCrypt (C) agile, multiplexing library HACL* (C) Vale (ASM) cryptographic providers • Agility: same code, multiple algorithms (e.g. hash) • Multiplexing: same algorithm, multiple implementations (e.g. SHA2_256) • Abstraction: clients verify against a single spec and an abstract footprint
Challenges in verification (an outline) 1. Vale vs Low*: reconciling different memory models & abstractions 2. HACL*: high-level, generic combinators vs. low-level code 3. EverCrypt: hide multiplexing and layer agile abstractions for clients
Vale: a deep embedding of assembly semantics in F* • Memory model maps addresses to bytes • Refinements of the memory model to adopt structured views (e.g. nat64) • Small, trusted interop interface generator ensures ABI and calling conventions are respected
Low*: a shallow embedding of a (curated) C subset in F* • Memory model: a region-based, structured memory model (à la CompCert); each allocation unit (array) has a type & layout • Libraries: a series of models for machine integers, arrays (stack & heap), low-level data structures, system functions • Language subset: no closures, limited higher-order, etc. • Limitations: not the full power of C, e.g. can’t take address of a field • Motto: high-level proofs for low-level code – the user retains the full expressive power of F* for proofs
The Low*/Vale boundary • A map from the Low* memory model to the Vale one (and vice-versa) • A library of views that capture the layout of arrays • The trusted wrapper (auto-generated) sets up the initial register state • A combinator captures that a Vale procedure (mem -> mem) can “morally” be executed with a suitable effect when in Low* • Extra proof obligations related to byte sequences vs words, endianness, etc.
Abstract, agile specifications • One key challenge in SMT-backed software verification: the context • Introducing abstractions is essential, even at the level of the specs Spec.SHA2.fsti val compress:a:sha_alg -> state a -> bytes -> state a implements Spec.SHA2.fst • Agile specifications limit code duplication! • Abstract specifications tame context proliferation This maximizes spec compactness
Testable specifications • Specs are easy to get wrong (e.g. endianness, typo in constants) • Specifications are not Low*, but can be compiled to OCaml for tests Spec.SHA2.fsti Spec.Test.Hash.fst Spec.SHA2.fst val compress:a:sha_alg -> state a -> bytes -> state a
Implementing abstract, agile (!) specifications • We are adamant about separating specifications from implementations • The friend mechanism of F* provides language support for that is specified by Spec.SHA2.fsti Impl.SHA2.fsti implements implements abstraction Impl.SHA2.fst Spec.SHA2.fst friends val compress:a:sha_alg -> state a -> array u8 -> Stack unit
A first layer of partial evaluation This is not Low*:Reason: val compress:a:sha_alg -> state a -> array u8 -> Stack unit let state a = function | SHA2_224 | SHA2_256 -> array u32 | SHA2_384 | SHA2_512 -> array u64 This could be compiled as a union. However, this is not idiomatic.Instead, we rely on partial evaluation: let compress_224 = compress SHA2_224let compress_256 = compress SHA2_256 let compress_384 = compress SHA2_384 let compress_512 = compress SHA2_512
Higher-order combinators • It is frequent to combine core operations to generate a construction (counter-mode, hash, etc.) Example: • Given any (abstract)compression function, one can define a Merkle-Damgård construction • Construction: folding the core compression function over multiple blocks of input data • We do not want to write this n times...
An example of a higher-order combinator // Write once; this is not Low*noextractinline_for_extractionlet mk_compress_blocks (a: hash_alg) (compress: compress_st a) (s: state a) (input: blocks) (n: u32 { length input = block_size a * n }) =C.Loops.for 0ul n (fun i -> compress s (Buffer.sub input (i * block_size a) (block_size a))) // Specialize many times; now this is Low*let compress_blocks_224 = mk_compress_blocks SHA2_224 compress_224...let compress_md5 = mk_compress_blocks MD5 compress_md5...
Meta-programming is pervasive • We use it for a given algorithm (SHA2) • We use it for constructions (Merkle-Damgard) • We will reuse combinators later on at the level of EverCrypt • More higher-order combinators, e.g. noextractinline_for_extractionlet mk_hash (a: hash_alg) (init: init_st a) (compress_blocks: compress_blocks_st a) (compress_last: compress_last_st a) (extract: extract_st a)= ...
No state abstraction in HACL* After partially evaluating everything, we get: • a library of pure C functions • all monomorphic and Low* • higher-order combinators (not extracted) • transparent state for each algorithm
State abstraction for algorithmic agility • EverCrypthides its internal state, offering callers an abstract footprint • Modifying a disjoint memory location preserves footprint and invariant • Internally, a tagged union implements algorithmic agility let compress_blocks (s: multiplexed_state) (b: array u8) (n: u32) = match s with | SHA2_256 s -> ... | MD5 s -> ... | ...
Connecting Vale and HACL* for implementation multiplexing let multiplexed_compress_blocks_sha2_256 (s: state SHA2_256) (blocks: array u8) (n: u32)= if StaticConfig.has_vale && AutoConfig.has_shaext () then Vale.Interop.SHA2.compress_256 s blocks n else Hacl.SHA2.compress_256 s blocks nThis uses static and dynamic configuration (more on that in a couple slides). • On the Low* side:extern void Vale_Interop_SHA2_compress256(uint32_t *s, uint8_t *blocks, uint32_t n) • On the Vale side: .text .global Vale_Interop_SHA2_compress256 Vale_Interop_SHA2_compress256: reconciled at link-time!
Reusing combinators • Since all the cryptographic constructions that need compress_blocks are higher-order combinators, EverCrypt can mix and match • For instance, instead of:let hash_sha2_256 =mk_hash init_sha2_256 compress_blocks_sha2_256 compress_last_sha2_256 ...we can use:let hash_sha2_256 =mk_hash init_sha2_256 multiplexed_compress_blocks_sha2_256 compress_last_sha2_256 ... • Each operation can now be multiplexed at any level in the stack
Preserving specifications • From the client’s perspective, the algorithmic specification remains the same • It is now agile between all algorithms from a given family • The specification abstraction ensures no context pollution occurs • The library can serve as a foundation for higher-level constructions
Feature detection EverCrypt relies on two flavors of feature detection: • static configurations allow disabling one provider entirely (e.g. Vale), meaning entire branches are partially evaluated and eliminated (no symbols) • dynamic configurations detect the presence of CPU features at run-time and allow switching at run-time between Vale, HACL*, or others The dynamic configuration needs the CPUID instruction; this is implemented in Vale.
Building higher-level constructions Using EverCrypt as a foundation, one can build advanced functionalities, such as: • HMAC • HKDF • Merkle Trees Each functionality can offer a new layer of abstraction to further shield its clients from large contexts.Relying on EverCrypt, HMAC is naturally agile and multiplexing.
Conclusion • Three core tenets: agility, multiplexing, abstraction • Connection at proof-time and run-time of two different languages • New motto: high-level proofs for high-level code that partially evaluates to low-level • Usable and used already: by both C clients (Firefox, Linux, Windows, ...) and verified Low* clients (Merkle trees, MPFR re-implementation...) Roadmap: • Performance: encouraging, in-progress • Eventually, all of: SHA2, SHA3 (+ SHA1, MD5), Argon, Blake2, P256, Chacha20, Salsa20, Poly1305 (32+64), Curve25519, Ed25519, AES-GCM, AES-CBC, HMAC, HKDF