mte/unikernel/duniverse/eqaf/lib/eqaf.mli
2025-11-11 02:07:51 +01:00

318 lines
11 KiB
OCaml

(** Eqaf - constant time / timing side channel resistant functions *)
(** {1 Basics}
In cryptography, a timing-attack is a side-channel attack in which the
attacker attempts to compromise a cryptosystem by analyzing the time taken to
execute cryptographic algorithms.
In some cases, a process needs to compare two values (input value and
expected password). An attacker can analyze time needed by
{!String.compare}/{!String.equal} to calculate expected password.
This side-channel attack is due implementation of
{!String.compare}/{!String.equal} which leaves as soon as possible when it
reachs a difference between [a] and [b]. By this way, time taken to compare
two values differs if they are equal or not.
Distribution provides a little example of this kind of attack where we
construct step by step (byte per byte) expected value from time spended to
execute {!Stdlib.compare}.
Distribution wants to provide some functions which protect user against this
kind of attack:
{ul
{- [equal] like {!String.equal}}
{- [compare_be] like {!String.compare}}
{- [compare_le] which is a {!String.compare} with a reverse operation on
inputs}
{- {!divmod} like {!Int32.unsigned_div} and {!Int32.unsigned_rem}}}
These functions are tested to see how long they took to compare two equal
values and two different values. See {i check} tool for more informations. *)
(** {1 Comparison functions} *)
(** {2 Equal} *)
val equal : string -> string -> bool
(** [equal a b] returns [true] if [a] and [b] are equals. [String.equal a b =
equal a b] for any [a] and [b]. The execution time of [equal] depends solely
on the length of the strings, not the contents. *)
(** {2 Big-endian comparison} *)
val compare_be : string -> string -> int
(** [compare_be a b] returns [0] if [a] is equal to [b], a negative integer if
[a] if {i less} (lexicographically) than [b], and a positive integer if [a]
is {i greater} (lexicographically) than [b].
[compare_be a b] returns the same {i order} than [String.compare a b] for
any [a] and [b] (but not necessary the same integer!). Order is defined as:
{ul
{- [compare_be a b < 0] means [a < b]}
{- [compare_be a b > 0] means [a > b]}
{- [compare_be a b = 0] means [a = b]}}
About time, if [String.length a <> String.length b], [compare_be] does not
look into [a] or [b] and no comparison in bytes will be done. *)
val compare_be_with_len : len:int -> string -> string -> int
(** [compare_be_with_len ~len a b] does {!compare_be}[ a b] on [len] bytes.
@raise Invalid_argument if [len] is upper than [String.length a] or
[String.length b]. *)
(** {2 Little-endian comparison} *)
val compare_le : string -> string -> int
(** [compare_le a b] is semantically [compare_be (rev a) (rev b)],
where [rev] is a function that reverses a string bytewise
([a = rev (rev a)]). *)
val compare_le_with_len : len:int -> string -> string -> int
(** [compare_le_with_len a b] is semantically [compare_be_with_len ~len (rev a)
(rev b)]. With [rev] reverse a string ([a = rev (rev a)]).
@raise Invalid_argument if [len] is upper than [String.length a] or
[String.length b]. *)
(** {1 Arithmetic} *)
(** {2 Division} *)
val divmod : x:int32 -> m:int32 -> int32 * int32
(** 32-bit unsigned division with remainder,
constant-time with respect to [x] ({b not} [m]).
@param x Dividend (number to be divided). {b Can be secret}.
@param m Divisor {b Must not be secret}. Must be [0 < m < 16384]
This function is useful for implementation that need to produce e.g.
pincodes from binary values in int32 format, example: {!ascii_of_int32}.
@return [quotient, remainder]
Example:
{[
let ct = Eqaf.divmod ~x ~m in
let not_ct = Int32.unsigned_div x m, Int32.unsigned_rem x m in
assert ct = not_ct ;
]}
That is, an attacker might be able to learn [m] by measuring
execution time, but not the value of [x].
@raise Invalid_argument when [not (0 < m && m < 16384)].
@see "supercop/crypto_kem/sntrup761/ref/uint32.c" Adapted from the NTRU Prime team's algorithm from [supercop/sntrup761], see round-2 NTRU Prime submission to NISTPQC (March 2019).
*)
(** {1:stringutil String utilities} *)
(** {2 String search}*)
val find_uint8 : ?off:int -> f:(int -> bool) -> string -> int
(** [find_uint8 ?off ~f v] returns the index of the first occurrence which
respects the predicate [f] in string [v]. Otherwise, it returns [-1].
The caller is responsible for ensuring that [~f] operates in constant time.
The {!bool_of_int} function can be relevant when writing [~f] functions.
*)
val exists_uint8 : ?off:int -> f:(int -> bool) -> string -> bool
(** [exists_uint8 ?off ~f v] tests if an occurrence respects the predicate
[f] in the string [v]. *)
(** {2:ascii ASCII functions}*)
val ascii_of_int32 : digits:int -> int32 -> string
(** [ascii_of_int64 ~digits ~n] is a string consisting of
the rightmost [digits] characters of the decimal representation
of [n].
If [digits] is larger than the decimal representation, it is left-padded
with ['0'].
If [digits] is smaller, the output is truncated.
Example:
{[
let s1 = ascii_of_int64 ~digits:6 ~n:12345678L in
assert (s = "345678") ;
let s2 = ascii_of_int64 ~digits:6 ~n:1234L in
assert (s = "001234") ;
]}
@raise Invalid_argument when [digits < 0]
*)
val lowercase_ascii : string -> string
(** [lowercase_ascii str] is [str] where [A-Z] is replaced with [a-z].
It is a constant time implementation of {!String.lowercase_ascii}
*)
val uppercase_ascii : string -> string
(** [uppercase_ascii str] is [str] where [a-z] is replaced with [A-Z].
It is a constant time implementation of {!String.uppercase_ascii}
*)
(** {2:hex Hex encoding and decoding}*)
val hex_of_bytes : bytes -> string
(** [hex_of_bytes raw] is [raw] hex-encoded in constant time.
Can be used to serialize sensitive values.
The {b contents can be secret}, but an attacker can learn the
{b length of} [raw] by timing this function.
Hex has two valid forms, lowercase and uppercase.
This function produces lowercase hex characters exclusively.
Example:
{[
let secret = "--Hi\x24" in
let serialized = hex_of_string secret in
(* serialized is now "2d2d486924" *)
]}
@param raw is the source buffer. [raw] is not mutated by this function.
*)
val hex_of_string : string -> string
(** [hex_of_string raw] is [hex_of_bytes raw] as a {!string} *)
val bytes_of_hex : string -> bytes * int
(** [bytes_of_hex hex] is [raw, error] decoded in constant time.
Can be used to e.g. decode secrets from configuration files.
The {b contents can be secret}, but an attacker can learn
the {b length of} [hex] by timing this function.
{b Error handling:} The second tuple element [error] is {b non-zero}
when the length of [hex] is not a multiple of 2,
or [hex] contains invalid characters.
Implementations should ensure that `error = 0` before using [raw].
The function signals errors this way to allow implementations to handle
invalid input errors in constant time.
@param hex The hex-encoded octet string. Accepts characters [0-9 a-f A-F].
Note that [0x] prefixes or whitespace are not accepted.
Example:
{[
let serialized = "2d2d486924" in
let secret, error = string_of_hex serialized in
assert (error = 0);
(* secret is now [--Hi$] *)
]}
*)
val string_of_hex : string -> string * int
(** [string_of_hex hex] is {!bytes_of_hex} [hex],
but returning a {!type:string} instead of {!type:bytes}.
See {!bytes_of_hex} regarding handling of invalid input errors.
*)
(** {1 Low-level primitives} *)
(** {2 Bithacks} *)
val one_if_not_zero : int -> int
(** [one_if_not_zero n] is a constant-time version of
[if n <> 0 then 1 else 0]. This is functionally equivalent to [!!n] in the C
programming language. *)
val zero_if_not_zero : int -> int
(** [zero_if_not_zero n] is a constant-time of
[if n <> 0 then 0 else 1]. This is functionnaly equivalent to [!n] in the C
programming language. *)
val int_of_bool : bool -> int
(** [int_of_bool b] is equivalent to [if b then 1 else 0].
Internally it cast with [%identity] instead of branching.*)
val bool_of_int : int -> bool
(** [bool_of_int n] is equivalent to [if n = 0 then false else true]. *)
(** {2 Composition} *)
val select_int : int -> int -> int -> int
(** [select_int choose_b a b] is [a] if [choose_b = 0] and [b] otherwise.
This comparison is constant-time and it should not be possible for a measuring
adversary to determine anything about the values of [choose_b], [a], or [b]. *)
val select_a_if_in_range : low:int -> high:int ->
n:int -> int -> int -> int
(** [select_a_if_in_range ~low ~high ~n a b]
- is [a] if [low <= n <= high] (in range)
- is [b] is [n < low || high < n] (out of range)
This function {b only works for positive ranges}:
@param low invariant: [0 <= low <= max_int]
@param high invariant: [low <= high <= max_int]
This function can be used like {!select_int} but using an integer range
instead of zero/non-zero to select.
It operates in constant time and is safe to use with secret parameters for
[low, high, n, a, b].
Example:
{[
let a = 123 and b = 456 in
let x = select_a_if_in_range ~low:10 ~high:20 ~n:10 a b in
(* x = 123 *)
let x = select_a_if_in_range ~low:10 ~high:20 ~n:0 a b in
(* x = 456 *)
let x = select_a_if_in_range ~low:10 ~high:20 ~n:20 a b in
(* x = 123 *)
let x = select_a_if_in_range ~low:10 ~high:20 ~n:21 a b in
(* x = 456 *)
(* Constant-time subpatterns can be expressed by nesting,
Below is a constant time version of:
match 3 with
| 1 | 2| 3 | 4 ->
begin match 3 with
| 2 | 3 -> 111
| _ -> 222
end
| _ -> 333
*)
let n = 3
select_a_if_in_range ~low:1 ~high:4 ~n
(select_a_if_in_range ~low:2 ~high:3 ~n
(111)
(222)
)
333
(* evalutes to 111 because [1 <= n <= 4] selects the inner pattern
and [2 <= n <= 3] selects the first branch ("a") of the inner pattern.
*)
(* The applications can also be composed in constant time,
note that all the branches are always evaluated, so large
expressions can get slow:
*)
4
|> fun n -> select_a_if_in_range ~low:1 ~high:5 ~n
(n * 10)
(n - 100)
|> fun n -> select_a_if_in_range ~low:20 ~high 30 ~n
(n * 100)
(n+3)
(* evaluates to [4 *10 + 3] *)
(* Below the normal, non-constant time version: *)
4
|> (function
| n when 1 <= n && n <= 5 -> n * 10
| n -> n - 100)
|> (function
| n when 20 <= n && n <= 30 -> n * 100
| n -> n + 3)
]}
Another example of how to use this can be found in the implementations of
{!lowercase_ascii} and {!bytes_of_hex}.
*)