=Paper= {{Paper |id=Vol-3792/paper25 |storemode=property |title=Enforcing Fresh Nonces with Affine Type System |pdfUrl=https://ceur-ws.org/Vol-3792/paper25.pdf |volume=Vol-3792 |authors=Richard Ostertág |dblpUrl=https://dblp.org/rec/conf/itat/Ostertag24 }} ==Enforcing Fresh Nonces with Affine Type System == https://ceur-ws.org/Vol-3792/paper25.pdf
                                Enforcing fresh nonces with affine type system
                                Richard Ostertág
                                Department of Computer Science, Faculty of Mathematics, Physics and Informatics, Comenius University, Bratislava, Slovakia


                                                                          Abstract
                                                                          Cryptographic algorithms and protocols often need fresh random numbers as parameters (e.g. nonces). Failure to satisfy this
                                                                          requirement lead to vulnerable implementation and can result in security breach. We show how affine type systems and static
                                                                          type checking can be used to enforce the correct generation of a new fresh random number for each function invocation.

                                                                          Keywords
                                                                          Secure coding, Nonce, Static checking, Substructural type systems, Rust



                                1. Introduction                                                                                                                         algorithm. Alternatively, we can enforce the use
                                                                                                                                                                        of a specific, vetted software implementation.
                                The security of various cryptographic constructions re-                                                                                 For this first part, we will employ abstract data
                                lies on unique or even unpredictable values. Examples                                                                                   types with hidden data constructors, to ensure
                                include nonces in cryptographic protocols, initializa-                                                                                  that only approved random number generators
                                tion vectors in modes of symmetric encryption, salts                                                                                    can produce nonce values.
                                in password-based key derivation functions and others.                                                                               2. Preventing Reuse of Random Numbers:
                                These values are often generated as a random numbers                                                                                    In the second part, we will ensure that once the
                                of prescribed length.                                                                                                                   generated random number is used, it cannot be
                                   Programmers who are not experts in cryptography                                                                                      reused for the second time.
                                might assume that it is not strictly necessary to generate                                                                              Substructural type systems which enforce single
                                a new random number every time. Some of them may be                                                                                     (or at most single) use semantics on values, can
                                lazy and provide fixed numeric constant instead of a new                                                                                be utilized for this purpose. This approach can be
                                random number for each use. After all, the cryptographic                                                                                applied not only to languages that support linear
                                construction will “correctly”1 work even with this fixed                                                                                (single use) types but also to any language with
                                numeric constant. However, if the no-reuse principle is                                                                                 similar features, such as ownership and borrow-
                                not followed, it can lead to a serious security vulnerability                                                                           ing in Rust, affine types in Haskell, or uniqueness
                                in the resulting application (which is not visible at first                                                                             types in Clean. We will illustrate this concept
                                glance). A well-known example of this issue is “forbidden                                                                               using the Rust programming language.
                                attack” for AES-GCM [1], but e.g. see also [2].
                                   In this paper, we propose a method which demonstrate                 In Section 2, we describe the key features of abstract
                                how to implement a cryptographic library that would                  data types necessary to enforce proper random number
                                allow the compiler to detect incorrect (i.e. repeated) use           generation. Next, in Section 3, we introduce the funda-
                                of such one-time random numbers at compile time. We                  mental concepts of substructural type systems, focusing
                                will divide this task into two main parts:                           on linear and affine type systems that provide strict con-
                                                                                                     trol over resource usage, which we utilize to prevent the
                                               1. Ensuring Proper Random Number Generation:          reuse of random numbers. In Section 4, we then explore
                                                  In the first part, we ensure that the function ex- the unique features of the Rust programming language,
                                                  pecting a random number gets as an argument including traits, ownership, move semantics, and bor-
                                                  a random number generated by an “approved” rowing rules, which we leverage to implement the nonce
                                                  method. E.g. a true random number generated module. Finally, in Section 5, we demonstrate how to use
                                                  by a specialized hardware device and not just the nonce module in a cryptographic library to enforce
                                                  some pseudorandom number generated by weak correct nonce usage.


                                ITAT’24: Workshop on Applied Security, September 20–24, 2024,                                                                      2. Abstract data types
                                Drienica, SK
                                Envelope-Open richard.ostertag@fmph.uniba.sk (R. Ostertág)         Abstract data types (in short ADTs) serve as a fundamen-
                                Orcid 0000-0002-6560-1515 (R. Ostertág)                            tal abstraction mechanism in computer science, providing
                                                                    © 2024 Copyright for this paper by its authors. Use permitted under Creative Commons License
                                 CEUR
                                 Workshop
                                               http://ceur-ws.org
                                               ISSN 1613-0073
                                                                                                   a formal specification for data types that decouples their
                                                                    Attribution 4.0 International (CC BY 4.0).
                                             CEUR Workshop Proceedings (CEUR-WS.org)
                                  Depending on the cryptographic construction, it might (for exam- behavior from their concrete implementation. ADTs are
                                 Proceedings


                                1

                                  ple) still correctly encrypt and decrypt messages.               defined by their external behavior, such as operations like




CEUR
                  ceur-ws.org
Workshop      ISSN 1613-0073
Proceedings
insertion and deletion, while concealing the underlying        1 mod nonce {
implementation details from the user. This encapsulation       2   // A public struct with a private
grants implementers the flexibility to employ any inter-       3   // random value of type u128
nal data structures or modify their approach in the future.    4   pub struct Nonce {
As long as the external behavior (interface) remains con-      5     val: u128,
sistent, existing code utilizing the ADT will continue to      6   }
function without requiring modifications to accommo-           7


date changes in the internal implementation. In this way       8       impl Nonce {
ADTs also promote software reuse and modularity.               9         // A public constructor method
   ADTs are widely used and supported in many standard        10         pub fn new() -> Nonce {
programming languages, including C++, Java, or Pascal.        11           use rand::prelude::*;
They are typically realised as modules or objects that        12           Nonce { val: random() }
hide internal implementation details and expose only the      13         }
public interface to the client. For instance, if we want to   14


implement a stack (a LIFO data structure) as an ADT, we       15           // A public getter method
would provide public functions such as push , pop , and       16           pub fn get(self: &Self) -> &u128 {
others and a type Stack for variables holding values of       17             &self.val
this ADT. But the important aspect is, that we do not         18           }
disclose to the client any information on how the stack       19       }
is internally implemented. It could be a linked list, an      20


array, or something totally different. For example, if a      21       // The Copy and Clone traits are
more efficient data structure becomes available, the inter-   22       // intentionally not implemented
nal implementation of the ADT can be updated without          23


affecting the client code, as long as the public interface    24       // DerefMut is needed to modify through
remains unchanged. Additionally, we also do not pro-          25       // a dereference, so since only Deref
vide any external means for creating a new stack (since       26       // is defined, nonce cannot be modified
external users lack knowledge of the internal details of      27       use std::ops::Deref;
the Stack type). The only way to create a new stack is        28       impl Deref for Nonce {
to call a function from the module, which returns a new       29         type Target = u128;
Stack value (or to create a new instance if objects are       30         fn deref(self: &Self) -> &u128 {
used instead of modules).                                     31           &self.val
   ADT are particularly useful for constraining access        32         }
and preventing invalid states. By defining the stack as       33       }
ADT, the module implementer can maintain strict con-          34   }
trol over its representation. Clients cannot accidentally
or intentionally alter any of the stack’s representation           Listing 1: Implementation of nonce module in Rust
invariants. This ensures that the stack remains in a valid
state, and its operations behave as expected. Therefore
ADTs enhance code maintainability and readability.               While abstract types are a powerful means of con-
   We can use this technique to create a nonce module         trolling the structure and creation of data, they are not
in the Rust programming language with Nonce abstract          sufficient to limit the ordering and number of uses of
data type (see Listing 1).                                    values and functions. As another example, we can men-
   We have defined a public struct type Nonce in Rust, en-    tion e.g. files. There is no (static) way to prevent a file
capsulating a private random value of type u128 . Direct      from being read after it has been closed [3] utilising only
instantiation of structs with private field is prohibited.    ADTs. Additionally, it is challenging to enforce rules pre-
In this case for instance it is invalid to write let nonce    venting clients from erroneously closing files multiple
= Nonce { val: 42 } . The only way for the client to          times or forgetting to close them altogether. Similarly,
create a nonce is to invoke a public constructor method       with our Nonce example, there is no static way to stop
like let mut nonce = nonce::Nonce::new() .                    the client from using one nonce value multiple times just
   Since the client needs to call the new method, we can      by using ADTs alone. However, this can be enforced
ensure that on line 12, we, as implementers, select the       in programming languages that support the appropriate
appropriate system function to generate a new random          substructural type system.
number. This could potentially involve using a hardware
RNG for added security. However, to keep this example
simple, this step is not included.
3. Substructural type systems                                                      context

Before presenting our proposed solution using substruc-                    ⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞
                                                                           Γ1 , 𝑥 ∶ 𝜏𝑥 , 𝑦 ∶ 𝜏𝑦 , Γ2 ⊢ 𝑒 ∶ 𝜏
                                                                                                                    (Exchange)
tural type system, it’s essential to provide a brief overview              Γ⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟⏟
                                                                              1 , 𝑦 ∶ 𝜏 𝑦 , 𝑥 ∶ 𝜏𝑥 , Γ2 ⊢ 𝑒 ∶ 𝜏
of what substructural type systems are [3] and how they
                                                                            permutated context
are implemented within the well-known Rust program-
ming language [4].                                                The second property, weakening, indicates that adding
   Linear and affine type systems are a special case of sub-    extra, unneeded assumptions to the context, does not
structural type systems. They are particularly beneficial       prevent a term from type checking.
in scenarios where strict control over resource usage is
                                                                                     Γ ⊢ 𝑒∶𝜏
crucial and we need a way for constraining usage of the                                                           (Weakening)
                                                                                 Γ, 𝑥⏟∶ 𝜏𝑥 ⊢ 𝑒 ∶ 𝜏
interface of this resource (imagine resources like files or
memory). By utilizing linear (or affine) types, we can en-                 unneeded assumption
sure that certain values or operations are used exactly (or        Finally, the third property, contraction, states that if we
at most) once and in some way in the correct sequence,          can type check a term using two identical assumptions
thereby preventing common programming errors and en-            (𝑥2 ∶ 𝜏𝑥 and 𝑥3 ∶ 𝜏𝑥 ) then we can check the same term
hancing the security and reliability of software systems.       using a single assumption.
   In the context of our forthcoming solution, we will
demonstrate how substructural type system in Rust en-
                                                                            Γ, 𝑥2 ∶ 𝜏𝑥 , 𝑥3 ∶ 𝜏𝑥 ⊢ 𝑒 ∶ 𝜏
able us to enforce the one-time use of random numbers,                                                            (Contraction)
thereby mitigating potential security vulnerabilities as-            Γ, 𝑥1 ∶ 𝜏𝑥 ⊢ [𝑥2 ↦ 𝑥1 , 𝑥3 ↦ 𝑥1 ]𝑒 ∶ 𝜏
sociated with nonce reuse in cryptographic applications.         In his book [3], Pierce employs simply-typed lambda
This approach not only leverages Rust’s robust type sys-      calculus as a foundation to introduce the concepts of
tem but also showcases the practical application of ad-       linear and ordered lambda calculus. While the intricate
vanced type theories in real-world software development.      details of these theoretical underpinnings are beyond the
                                                              scope of our paper, we encourage interested readers to
3.1. Structural Properties                                    consult Pierce’s work for a comprehensive exploration.
                                                              His book provides an in-depth discussion on the princi-
In accordance with Pierce’s work [3] based on simply-
                                                              ples of substructural type systems and their applications,
typed lambda calculus, we will treat the type-checking
                                                              making it an valuable resource for those looking to delve
context, denoted as Γ, as a straightforward list of variable-
                                                              deeper into this topic.
type pairs, 𝑥 ∶ 𝜏𝑥 , where 𝑥 represents a variable and 𝜏𝑥
denotes its type.
   The comma operator (,) serves to append a pair to the 3.2. Substructural Type Systems
end of the type-checking context (e.g. Γ1 , 𝑥 ∶ 𝜏𝑥 ), or to A substructural type system is any type system that is
concatenate two type-checking contexts (e.g. Γ1 , Γ2 ). Let designed so that one or more of the structural properties
us denote by Γ ⊢ 𝑒 ∶ 𝜏 that, within the context Γ, we can do not hold [3]. Different substructural type systems
type-check that the term 𝑒 has the type 𝜏.                    arise when different properties are withheld.
   We denote the substitution of the term 𝑦 for the free
variable 𝑥 in the term 𝑒 by [𝑥 ↦ 𝑦]𝑒. We assume that 𝑥 and Linear type systems ensure that every variable is used
𝑦 have the same type, ensuring that the resulting term                exactly once by allowing exchange but not weak-
[𝑥 ↦ 𝑦]𝑒 is also correctly typed (in simply-typed lambda              ening or contraction.
calculus). Instead of writing [𝑥3 ↦ 𝑥4 ]([𝑥1 ↦ 𝑥2 ]𝑒), we
use the more concise form [𝑥1 ↦ 𝑥2 , 𝑥3 ↦ 𝑥4 ]𝑒.              Affine type systems ensure that every variable is used
                                                                      at most once by allowing exchange and weaken-
                    premise                                           ing, but not contraction.
                                              (Typing rule)
                  conclusion
                                                              Relevant type systems ensure that every variable is
   Finally, by typing rule we mean that if its premise is
                                                                      used at least once by allowing exchange and con-
true, then we can conclude that its conclusion also holds.
                                                                      traction, but not weakening.
   Lets discuss three basic structural properties. The first
property, exchange, indicates that the order in which Ordered type systems ensure that every variable is
we write down variables in the context is irrelevant. A               used exactly once and in the order in which it is
corollary of exchange is that if we can type check a term             introduced. Ordered type systems do not allow
with the context Γ, then we can type check that term                  any of the structural properties.
with any permutation of the variables in Γ.
   The Fig. 1 below2 can serve as a mnemonic for the               In Rust, there are several important standard traits
relationship between these systems. The system at the           that provide foundational functionality and are widely
bottom of the diagram (the ordered type system) admits          used. For the purposes of this paper, it is important to
no structural properties. As we proceed upwards in the          understand, that the Copy and Clone traits allow for du-
diagram, we add structural properties: E stands for ex-         plication of value. Therefore, we do not implement them
change; W stands for weakening; and C stands for con-           for the Nonce . The Deref trait only allows for reading a
traction.                                                       stored value. To enable writing, the DerefMut trait is nec-
                                                                essary, which we also do not implement. Programmers
             unrestricted (E,W,C ⇒ structural)                  can access the stored u128 value using the nonce.get()
                  +C                        +W                  method or by dereferencing with *nonce . Both return
                                                                an immutable reference (as in immutable borrowing).
   affine (E,W)                              relevant (E,C)
                                                                4.2. Ownership rules
               +W                           +C                  In Rust, when a value is assigned to a variable, the vari-
                                                                able becomes the “owner” of that value. When the owner
                           linear (E)
                                                                variable goes out of scope, the value is automatically deal-
                                                                located. Rust enforces that there can only be one owner
                                  +E
                                                                for a value at a time. This is the essence of substructural
                        ordered (none)                          type system in Rust [6]:

Figure 1: Relationship between linear and other substructural        • Each value has a variable that is called its owner.
type systems.                                                        • There can be only one owner at a time.
                                                                     • When the owner goes out of scope, the value will
                                                                       be dropped (memory will be deallocated).
   All type systems below unrestricted (aka. structural)
are called substructural type systems. It might be pos-
sible to define type systems containing other combina-          4.3. Move semantics
tions of structural properties, such as contraction only Move semantics in Rust is a fundamental concept that
or weakening only, but so far researchers have not found allows the transfer of ownership of a value from one
applications for such combinations [3]. Consequently,    variable to another. When a value is assigned from one
they are excluded from the diagram.                      variable to another (e.g. let s2 = s1; ) or passed to
                                                         a function, ownership of the value is transferred (aka
4. Rust                                                  moved) from s1 to the new variable s2 unless the value
                                                         implements the Copy trait [7]. In other words, variable
Ownership is Rust’s most unique feature which is closely bindings have “move semantics” if their type does not
related to the concept of substructural type systems in implement the Copy trait; otherwise, they have “copy
that both systems enforce strict rules about how data is semantics”.
accessed and modified to ensure safety and correctness.     After a move, the original variable is no longer valid
It enables Rust to make memory safety guarantees with- and cannot be used. Move semantics can improve per-
out needing a garbage collector. In Rust, the memory is formance by avoiding deep copies of data. Instead of
managed through a system of ownership with a set of copying the data, Rust only copies the pointer to the data
rules, that the compiler checks at compile time. None of and invalidates the original pointer. Single owner allows
the ownership features slow down the program while it for values to be deallocated as soon as their owner goes
is running (unlike garbage collection).                  out of the scope.

4.1. Rust traits                                                4.4. Borrowing rules
Traits in Rust serve as a way to define shared behavior. If you need to access or modify a value without transfer-
They are similar to interfaces in other programming lan- ring ownership, you can borrow a reference to it. There
guages. They allow to specify methods that types must are two types of borrowing in Rust:
implement, enabling polymorphism and code reuse. For
more see e.g. [5].                                            • Immutable borrowing:
                                                                 You can have multiple immutable references to a
2
                                                                 value, but you cannot modify the value through
Fig. 1 is based on similiar image in [3].
              these references. This prevents data races be-
                                                                               stack                        heap
              cause multiple threads can read a value without
              the risk of it being modified simultaneously.
            • Mutable borrowing:                                                   s1
              You can have only one mutable reference to a                   ptr
              value, and no other references (mutable or im-
              mutable) can coexist with it. This enforces exclu-            len         5             index    value
              sive access to the value, ensuring that only one            capacity      5               0          h
              part of the code can modify it at a time.                                                 1          e
                                                                                                        2          l
                                                                                   s2
    1 fn borrowing() {                                                       ptr                        3          l
    2   let s1 = String::from("Hello");                                                                 4          o
                                                                            len         5
    3   // ^^ move occurs because `String` does
                                                                          capacity      5
    4   //     not implement the `Copy` trait
    5

    6       let s2 = s1; // value moved from s1 to s2
    7
                                                                   Figure 2: Memory representation of variables s1 and s2
    8       println!("{}, world!", s1);
    9       //error: val. borrowed ^^ here after move
10      }                                                            On Listing 3 we implement function need_new_
                                                                   random_u128_every_time to demonstrate function sig-
                                                             nature for functions that require fresh random value for
            Listing 2: Classical example of ownership rules
                                                             every call. The body of the function is not significant,
                                                             but we demonstrate, that the nonce value can be used
   We will demonstrate some of these rules on Listing 2.
                                                             repeatedly inside library implementation, which is often
On line 2 we create a string and assign its value into
                                                             needed. We also implement Deref trait, so * can be used
variable s1 . This variable is now the only owner of the
                                                             on line 10 instead of longer nonce.get() from line 7.
string. Then on line 6 we move value from variable s1
to new owner – variable s2 (because String does not 1 fn need_new_random_u128_every_time(
implement Copy trait). Now s2 is the only owner of 2 nonce: nonce::Nonce
the string value. That is the reason, why we can not 3 ) {
use variable s1 on line 8 to borrow the string value to 4 let _tmp = nonce.get();
println! function. But we could use s2 for this. When         5
s2 comes out of scope the string value can be deallocated     6   println!("Nonce param value: {}",
from memory. This is illustrated in Fig. 2 on the right . 73
                                                                     nonce.get());
After assigning to s2 the value from s1 , variable s2 points 8
to the same memory on the heap, but s1 can not be used 9 println!("Nonce param value: {}",
for dereferencing anymore. This is used primarily for 10             *nonce);
memory management without the need for a garbage 11 }
collector or explicit deallocation. For more see e.g. [6].

                                                                   Listing 3: Example of function with nonce as argument
5. The solution
                                                                      When function need_new_random_u128_every_time
The solution in Rust is syntactically very simple because
                                                                   is called, then value ownership is moved from the local
it is well aligned with Rust syntax. Usually, when func-
                                                                   variable to the argument and thus local variable can not
tions in Rust take arguments, they are passed as refer-
                                                                   be used anymore. As an example, if in Listing 4 we
ences (with & before variable name). This way value is
                                                                   comment out line 7, we will get compile time error “value
not moved to the parameter from the local variable (it is
                                                                   used here after move” on the next line.
just borrowed). However, we can prevent borrowing by
not taking reference as the argument and not implement-
ing Copy trait in Nonce type.                                      6. Conclusion
                                                                   We have demonstrated how to use abstract data types and
3
    Fig. 2 is based on similiar image in [6].                      substructural type systems for enforcing the freshness of
 1 fn main() {                                                   ch10-02-traits.html, with contributions from the Rust
 2   // Structs with private fields can be                       Community.
 3   // created only using public constructors               [6] S. Klabnik, C. Nichols, What is ownership?, 2024.
 4   let mut nonce = nonce::Nonce::new();                        URL: https://doc.rust-lang.org/book/ch04-01-what-
 5   need_new_random_u128_every_time(nonce);                     is-ownership.html, with contributions from the Rust
 6
                                                                 Community.
 7       nonce = nonce::Nonce::new();                        [7] S. Klabnik, C. Nichols, Trait std::marker::Copy,
 8       need_new_random_u128_every_time(nonce);                 2024. URL: https://doc.rust-lang.org/std/marker/trait.
 9
                                                                 Copy.html, with contributions from the Rust Com-
10       need_new_random_u128_every_time(                        munity.
11          nonce::Nonce::new()
12       );
13   }


             Listing 4: Example of nonce usage


nonces for cryptographic library function calls. In Rust,
the syntax is very straightforward. This solution can be
implemented also in other languages with affine or linear
type system, like Haskell, which experimentally supports
linear types from version 9.0.1. But syntax, in this case,
is not so clear as in Rust.


Acknowledgments
This publication is the result of support under the
Operational Program Integrated Infrastructure for the
project: Advancing University Capacity and Compe-
tence in Research, Development a Innovation (ACCORD,
ITMS2014+:313021X329), co-financed by the European
Regional Development Fund.


References
[1] H. Böck, A. Zauner, S. Devlin, J. Somorovsky,
    P. Jovanovic, Nonce-Disrespecting adversaries:
    Practical forgery attacks on GCM in TLS, in:
    10th USENIX Workshop on Offensive Technologies
    (WOOT 16), USENIX Association, Austin, TX, 2016.
    URL: https://www.usenix.org/conference/woot16/
    workshop-program/presentation/bock.
[2] A. Joux, Authentication failures in NIST version of
    GCM, 2006. URL: https://csrc.nist.gov/csrc/media/
    projects/block-cipher-techniques/documents/bcm/
    joux_comments.pdf.
[3] B. C. Pierce (Ed.), Advanced topics in types and pro-
    gramming languages, The MIT Press, MIT Press,
    London, England, 2004.
[4] S. Klabnik, C. Nichols, The Rust programming lan-
    guage, No Starch Press, San Francisco, CA, 2019.
[5] S. Klabnik, C. Nichols, Traits: Defining shared be-
    havior, 2024. URL: https://doc.rust-lang.org/book/