the Everest VERified Endtoend Secure Transport Project Everest

  • Slides: 52
Download presentation
*the Everest VERified End-to-end Secure Transport Project Everest Ever. Crypt: A Fast, Verified, Cross-Platform

*the Everest VERified End-to-end Secure Transport Project Everest Ever. Crypt: A Fast, Verified, Cross-Platform Cryptographic Provider Karthikeyan Bhargavan, Benjamin Beurdouche, Joonwon Choi, Antoine Delignat-Lavaud, Cédric Fournet, Aymeric Fromherz, Chris Hawblitzel, Natalia Kulatova, Bryan Parno, Marina Polubelova, Jonathan Protzenko, Tahina Ramananandro, Aseem Rastogi, Nikhil Swamy, Christoph Wintersteiger, Santiago Zanella-Beguelin

A quick introduction

A quick introduction

mi. TLS 1. 3 Ever. Crypt HACL* (Poly 1305, Curve 25519, Chacha 20, etc.

mi. TLS 1. 3 Ever. Crypt HACL* (Poly 1305, Curve 25519, Chacha 20, etc. ) today’s focus Ever. Parse Vale. Crypt (Poly 1305, Curve 25519)

What is a cryptographic A COLLECTION OF ALGORITHMS (EXHAUSTIVE) SEVERAL IMPLEMENTATIONS (MULTIPLEXING) APIS GROUPED

What is a cryptographic A COLLECTION OF ALGORITHMS (EXHAUSTIVE) SEVERAL IMPLEMENTATIONS (MULTIPLEXING) APIS GROUPED BY FAMILY (AGILITY) EASY-TO-USE API (CPU AUTO-DETECTION) provider?

An essential piece of software A cryptographic provider is useful beyond secure communications, e.

An essential piece of software A cryptographic provider is useful beyond secure communications, e. g. • file encryption • secure enclaves • document signatures • cryptocurrencies • any modern piece of software

What is a cryptographic provider?

What is a cryptographic provider?

A brief reminder: why verify cryptographic algorithms?

A brief reminder: why verify cryptographic algorithms?

Circa 2006 (undergrad) throwback “the math”

Circa 2006 (undergrad) throwback “the math”

My former self didn’t know this would be useful! “the application”

My former self didn’t know this would be useful! “the application”

Distilling the math for implementors “the algorithm”

Distilling the math for implementors “the algorithm”

Writing the actual code “the reality”

Writing the actual code “the reality”

What could possibly go wrong?

What could possibly go wrong?

Many bugs in Curve 25519 implementations (C and assembly) Na. Cl (asm) Curve 25519

Many bugs in Curve 25519 implementations (C and assembly) Na. Cl (asm) Curve 25519 -donna Tweet. Na. Cl

3 Bugs in Open. SSL implementation of Poly 1305 last year Low* 14

3 Bugs in Open. SSL implementation of Poly 1305 last year Low* 14

Implementation bug in AES-GCM

Implementation bug in AES-GCM

Implementation bug in Windows Sym. Crypt Potential DDOS.

Implementation bug in Windows Sym. Crypt Potential DDOS.

Program verification!

Program verification!

Specification (“the mathematical truth”) via ocaml proof Pseudo-code (“implementation blueprint”) proof Vale/F* (“assembly-like”) via

Specification (“the mathematical truth”) via ocaml proof Pseudo-code (“implementation blueprint”) proof Vale/F* (“assembly-like”) via Vale printer Assembly (. asm) proof Low* (“C-like”) via Kre. MLin compiler C code (. c, . h) spec-test. exe (“testable specification”)

What do we verify? Memory- and type-safety. Mitigates buffer overruns, dangling pointers, code injections.

What do we verify? Memory- and type-safety. Mitigates buffer overruns, dangling pointers, code injections. No undefined behavior. Our fast implementations behave precisely as our simpler specifications. Access to secrets, including crypto keys and private app data is restricted according to design.

The Essence of Ever. Crypt

The Essence of Ever. Crypt

Ever. Crypt: no excuses industrial-grade crypto library, with full verification Ever. Crypt Vale/F* (“assembly-like”)

Ever. Crypt: no excuses industrial-grade crypto library, with full verification Ever. Crypt Vale/F* (“assembly-like”) - A single artifact for clients to use State-of-the-art performance A single verification result (Vale or Low*) Deep integration for seamless interop Total abstraction for clients Low* (“C-like”)

Algorithm C version AES-GCM ASM version Agile API ✔ (AESNI) ✔ Chacha. Poly ✔

Algorithm C version AES-GCM ASM version Agile API ✔ (AESNI) ✔ Chacha. Poly ✔ (+ AVX 2) ✔ MD 5, SHA 1 ✔ ✔ SHA 2 ✔ SHA 3 ✔ Blake 2 ✔ HMAC ✔ ✔ HKDF ✔ ✔ Curve 25519 ✔ P 256 ✔ Ed 25519 ✔ Chacha 20 ✔ (+ AVX 2) ✔ (SHAEXT) ✔ (BMI 2 + ADX) AES 128, 256 ✔ AES-CTR ✔ Poly 1305 ✔ (+ AVX 2) ✔ (X 64) ✔

One algorithm, several implementations (multiplexing) - Verifies multiple implementations (Vale & Low*) against one

One algorithm, several implementations (multiplexing) - Verifies multiple implementations (Vale & Low*) against one specification - Isolates clients from processor and target details - Auto-detects static & dynamic features - Eliminates illegal instruction errors - Expected by an industrial-grade library

Several algorithms, one API (agility) - Verifies that multiple algorithms fit the same family

Several algorithms, one API (agility) - Verifies that multiple algorithms fit the same family of specifications - Allows clients to switch between algorithms (crucial for TLS) - Uses F* meta-programming to templatize the code - Expected by an industrial-grade library

Deep integration between C and ASM (speed) Verification allows more optimizations and does not

Deep integration between C and ASM (speed) Verification allows more optimizations and does not compromise speed. Mundane parts of the algorithms are written in Low* while critical bits are in Vale. A new verified interop layer ensures sound interoperation between two languages.

A foundation for verified apps (abstraction) C client Signal* Merkle tree Ever. Crypt (C

A foundation for verified apps (abstraction) C client Signal* Merkle tree Ever. Crypt (C API) HACL* (C) clients (+ Ever. Quic, mi. TLS, etc. ) agile, multiplexing library Vale (ASM) cryptographic providers Ever. Crypt seals the abstraction, meaning verified clients are shielded from underlying verification details.

A significant verification effort (bringing all of Everest together)

A significant verification effort (bringing all of Everest together)

A look behind the scenes

A look behind the scenes

What made scaling possible in Ever. Crypt An in-depth look at three language-based mechanisms.

What made scaling possible in Ever. Crypt An in-depth look at three language-based mechanisms. 1. Establishing abstraction boundaries to deal with a large SMT context 2. Reasoning with abstract state in the presence of dynamic frames 3. Heavy reliance on meta-programming to minimize verification effort

Abstract, agile specifications 1 • One key challenge in SMT-backed software verification: the context

Abstract, agile specifications 1 • One key challenge in SMT-backed software verification: the context • Introducing abstractions is essential, even at the level of the specs Spec. SHA 2. fsti val compress: a: sha_alg -> state a -> bytes -> state a implements Spec. SHA 2. fst This maximizes spec compactness • Agile specifications limit code duplication! • Abstract specifications tame context proliferation • Cryptographically enforces constructions

Testable specifications • Specs are easy to get wrong (e. g. endianness, typo in

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. SHA 2. fsti Spec. SHA 2. fst val compress: a: sha_alg -> state a -> bytes -> state a Spec. Test. Hash. fst

Implementing abstract, agile (!) specifications • We are adamant about separating specifications from implementations

Implementing abstract, agile (!) specifications • We are adamant about separating specifications from implementations • The friend mechanism of F* provides language support for that Spec. SHA 2. fsti is specified by implements Spec. SHA 2. fst Impl. SHA 2. fsti implements friends abstraction Impl. SHA 2. fst val compress: a: sha_alg -> state a -> array u 8 -> Stack unit

Even more fine-grained abstraction • To avoid SMT-reasoning difficulties, some modules make every single

Even more fine-grained abstraction • To avoid SMT-reasoning difficulties, some modules make every single definition abstract (via [@ opaque_to_smt ]) • Stateful code reveals definitions of interest (via reveal_opaque)

An example of Low* code 2 val ladder_step_loop: #s: field_spec -> k: scalar ->

An example of Low* code 2 val ladder_step_loop: #s: field_spec -> k: scalar -> q: point s -> p 01_tmp 1_swap: lbuffer (limb s) (8 ul *! nlimb s +! 1 ul) -> tmp 2: felem_wide 2 s -> Stack unit (requires fun h 0 -> live h 0 k / live h 0 q / live h 0 p 01_tmp 1_swap / live h 0 tmp 2 /. . . ) (ensures fun h 0 _ h 1 -> modifies (loc p 01_tmp 1_swap |+| loc tmp 2) h 0 h 1 /. . . ) • • Stateful code: “buffers” (arrays), machine integers, stack-heap reasoning, built-in stack shape preservation, etc. Talks about liveness of inputs Talks about a modified set of locations A set of abstract reasoning principles (which modified locations are still live, disjoint-not-modified remains the same, etc. ) • Backed by SMT (triggers). . . works at a small scale. . . need to give some structure

Many goals to satisfy at once • Reveal just enough for clients to be

Many goals to satisfy at once • Reveal just enough for clients to be able to allocate / deallocate • Hide invariants, representation, state, etc. • Be C-compatible and output idiomatic C code • Maximize automation from the client’s perspective

Abstract state and C abstract structs // Ever. Crypt. AEAD. fsti // Ever. Crypt_AEAD.

Abstract state and C abstract structs // Ever. Crypt. AEAD. fsti // Ever. Crypt_AEAD. h [@ CAbstract. Struct] val state_s: alg -> Type 0 struct state_s; let state alg = B. pointer (state_s alg) typedef struct state_s *state; - Allows the client to talk and write pointers to abstract state - Abstract state is heap-allocated: it’s ok to not know its size - Special support in Kre. MLin for this attribute

Abstract footprints for modifies reasoning // Ever. Crypt. AEAD. fsti val footprint_s: #a: alg

Abstract footprints for modifies reasoning // Ever. Crypt. AEAD. fsti val footprint_s: #a: alg -> state_s a -> GTot B. loc let footprint (#a: alg) (m: HS. mem) (s: state a) = B. (loc_union (loc_addr_of_buffer s) (footprint_s (B. deref m s))) - Follows from the previous definition - Subtlety: loc_addr_of_buffer (makes de-allocation compatible with modifies) - Hides the variety and complexity of inner locations – good for SMT!

A (mostly) abstract invariant // Ever. Crypt. AEAD. fsti val invariant_s: (#a: alg) ->

A (mostly) abstract invariant // Ever. Crypt. AEAD. fsti val invariant_s: (#a: alg) -> HS. mem -> state_s a -> Type 0 let invariant (#a: alg) (m: HS. mem) (s: state a) = B. live m s / B. (loc_disjoint (loc_addr_of_buffer s) (footprint_s (B. deref m s))) / invariant_s m (B. deref m s) - Liveness only on the outer pointer (the one the client “manages”) - Liveness of the inner data structures covered by invariant_s - Disjointness is essential

Maximizing automation // Impl. QUIC. fsti val g_initial_packet_number: #i: index -> (s: state_s i)

Maximizing automation // Impl. QUIC. fsti val g_initial_packet_number: #i: index -> (s: state_s i) -> GTot Spec. QUIC. nat 62 val g_packet_number: #i: index -> (s: state_s i) -> (h: HS. mem) -> GTot Spec. QUIC. nat 62 val encrypt: . . . -> Stack. . . (ensures (fun h 0 _ h 1 -> modifies (footprint_s s) h 0 h 1)) - Crucial information here: the initial packet number is a value while the packet number is mutable Client gets “for free” that a call to encrypt preserves the initial packet number Relies on the outer invariant revealing the disjointness In essence, states that encrypt will not reallocate the state_s

The (final) framing lemma // Ever. Crypt. AEAD. fsti val frame_invariant: #a: alg ->

The (final) framing lemma // Ever. Crypt. AEAD. fsti val frame_invariant: #a: alg -> l: B. loc -> s: state a -> h 0: HS. mem -> h 1: HS. mem -> Lemma (requires ( invariant h 0 s / B. loc_disjoint l (footprint h 0 s) / B. modifies l h 0 h 1)) (ensures ( invariant h 1 s / footprint h 0 s == footprint h 1 s))

Conclusion for abstract reasoning • Revealing just enough for clients • Hides arbitrary amounts

Conclusion for abstract reasoning • Revealing just enough for clients • Hides arbitrary amounts of complexity and mutability • Essential for building applications on top of cryptography

Meta-programming all the things • Many algorithms share similarities • Verification is painful: minimize

Meta-programming all the things • Many algorithms share similarities • Verification is painful: minimize efforts (research is laziness) • A hierarchy of sharing 3 SHA 2 -512 SHA 2 -256 SHA 2 -384 SHA 2 -224 SHA 2 family Merkle. Damgård SHA 1 MD 5

The most complicated example: Curve 25519 •

The most complicated example: Curve 25519 •

A refresher on constraints • Must generate readable C code • Must generate fully

A refresher on constraints • Must generate readable C code • Must generate fully specialized C code (no unions) • Must split the code out into individual files (for compiler flags) • Must allow full modularity and be future-proof (no closed enum) In OCaml: two nested functors; here: non-idiomatic and too slow.

First ingredient: partial evaluation // Hacl. Impl. Curve 25519. Fields. Core. fsti type field_spec

First ingredient: partial evaluation // Hacl. Impl. Curve 25519. Fields. Core. fsti type field_spec = | M 51 | M 64 inline_for_extraction noextract let felem (s: field_spec) = lbuffer (limb s) (nlimb s) // Hacl. Impl. Curve 25519. Fields. Generic. fst inline_for_extraction noextract let ladder_step #s k q i p 01_tmp 1_swap tmp 2 =. . . cswap 2 #s sw nq nq_p 1; point_add_and_double #s q p 01_tmp 1 tmp 2; . . .

Relying on F*’s interpreter // Hacl. Curve 25519_64. fst let ecdh = Hacl. Impl.

Relying on F*’s interpreter // Hacl. Curve 25519_64. fst let ecdh = Hacl. Impl. Curve 25519. ecdh #64 • Works, but generates a huge function: bad for readability, bad for C compilers • Inlining is good (e. g. splitting up ladder); but not all the way • Does not allow different core operations for the two 64 implementations (closed enumeration of cases)

Hand-rolling C++-like templates // Field 64 core operations assume val add: felem M 64

Hand-rolling C++-like templates // Field 64 core operations assume val add: felem M 64 -> felem M 64 // Field 64 operations: parametric over core operations let store_felem: store_felem_t #M 64 = fun. . . ->. . . add. . . // Field 51 operations: nothing generic let store_felem: store_felem_t #M 51 = fun. . . ->. . . // Generic operations: parametric over index let store_felem #s = match s with M 51 -> Field 51. store_felem | M 64 -> Field 64. store_felem let ladder_step #s = fun. . . ->. . . let ladder #s = fun. . . ->. . . let scalarmult #s = fun. . . ->. . .

A call-graph rewriting tactic // Field 64 operations: parametric over core operations // Curve_64_Fallback

A call-graph rewriting tactic // Field 64 operations: parametric over core operations // Curve_64_Fallback Curve_51 Curve_64_ADX let store_felem (add: add_t): store_felem_t #M 64 = fun. . . ->. . . add. . . let store_felem ladder = = Field 64. store_felem Hacl. add Vale. add Generic. ladder_51 Field 51. store_felem // Field 51 operations: nothing generic let store_felem: store_felem_t #M 51 = fun. . . ->. . . let ladder = let scalarmult = Generic. ladder_64 store_felem Generic. scalarmult ladder // Generic operations: parametric over index let scalarmult = inline_for_extraction let ladder_step #s (store_felem: store_felem_t #s) = fun. . . ->. . . let ladder #s (store_felem: store_felem_t #s) = fun. . . ->. . . let scalarmult #s (ladder: ladder_t #s) = fun. . . ->. . . Generic. scalarmult ladder

A call-graph rewriting tactic • 500 lines of Meta-F* • Distinguishes betwen inline nodes

A call-graph rewriting tactic • 500 lines of Meta-F* • Distinguishes betwen inline nodes (e. g. ladder_step) and specialize nodes (e. g. ladder) • State-passing traversal of the entire call-graph • Allows writing the code in a natural style, and specialize it many times for each flavor of algorithm • Generated code looks idiomatic • No run-time overhead

Conclusion

Conclusion

A vision for Ever. Crypt • An industrial-grade crypto provider is now a reality

A vision for Ever. Crypt • An industrial-grade crypto provider is now a reality • already adopted • demonstrates Open. SSL’s libcrypto is no longer inevitable • Peer pressure to use verified code (good) • blockchains pushing formal verification • skepticism of crypto is high (backdoors? magic constants? Russian S-BOX? ) • open-source more nimble (Linux, Boring. SSL, Firefox) • Only possible through close language-applications interaction • many language mechanisms (meta, libraries, modifies theory, friends) • constant push for F* improvements • Next: proofs of cryptographic security & confidentiality • A huge verification & engineering effort • ongoing maintenance: looking to hire a talented build & CI expert