Skip to content

Commit

Permalink
Merge 071e08b into d067785
Browse files Browse the repository at this point in the history
  • Loading branch information
marcoonroad committed Sep 8, 2019
2 parents d067785 + 071e08b commit 4a5928c
Show file tree
Hide file tree
Showing 22 changed files with 251 additions and 54 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ env:
- OCAML_VERSION=4.04 POST_INSTALL_HOOK="make report"
- OCAML_VERSION=4.05 POST_INSTALL_HOOK="make report"
- OCAML_VERSION=4.06 POST_INSTALL_HOOK="make lint-format; make report"
- OCAML_VERSION=4.07 POST_INSTALL_HOOK="make lint-format; make report"
- OCAML_VERSION=4.07 POST_INSTALL_HOOK="make lint-format; make bench; make report"
os:
- linux
# - osx
45 changes: 30 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ default: build

test: build
@ opam lint
@ dune build @test/spec/runtest -f --no-buffer -j 1
@ dune build @test/spec/runtest -f --no-buffer

build:
@ dune build -j 1
Expand All @@ -23,6 +23,8 @@ clear:
@ rm -rfv bisect*.out
@ dune clean

clean: clear

coverage: clear
@ mkdir -p docs/
@ rm -rf docs/apicov
Expand Down Expand Up @@ -71,11 +73,14 @@ docs: build
@ mv ./_build/default/_doc/_html/* ./docs/apiref/

pin:
@ opam pin add nocoiner . -n --yes
@ opam pin add nocoiner . -n --yes --working-dir

unpin:
@ opam pin remove nocoiner --yes

deps:
@ opam install . --deps-only --yes
@ opam install alcotest core --yes # force such test dependences
@ opam install . --deps-only --yes --working-dir
@ opam install alcotest --yes # force such test dependences

dev-deps:
@ opam install \
Expand All @@ -87,18 +92,20 @@ dev-deps:
merlin \
bisect_ppx \
utop \
core_bench \
--yes
@ opam update --yes
@ opam upgrade \
odoc \
ocveralls \
alcotest \
ocp-indent \
ocamlformat \
merlin \
bisect_ppx \
utop \
--yes
# @ opam update --yes
# @ opam upgrade \
# odoc \
# ocveralls \
# alcotest \
# ocp-indent \
# ocamlformat \
# merlin \
# bisect_ppx \
# utop \
# core_bench \
# --yes

lint-format:
@ opam install ocamlformat --yes
Expand All @@ -117,6 +124,14 @@ local-site-setup:
local-site-start:
@ cd docs && bundle exec jekyll serve && cd ..

bench: clean build
@ opam install core_bench --yes
@ NOCOINER_KDF_COST=2 \
NOCOINER_KDF_WORKERS=1 \
dune build @test/bench/runtest -f --no-buffer --auto-promote \
--diff-command="git diff --unified=10 --break-rewrites --no-index --exit-code --histogram --word-diff=none --color --no-prefix" || echo \
"\n\n=== Differences detected! ===\n\n"

# to run inside docker alpine context
binary: clear
@ dune build --profile deploy
Expand Down
64 changes: 63 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,67 @@ The complete API reference is available [here][7]. Coverage reports are
generated too, please refer to the respective [page][8].


### Cryptoanalysis

We have performed some benchmarks on valid inputs and on invalid inputs as well.
This is just to discover and prove exploitable loopholes. The kind of side-channel
vulnerabilities shown on version `1.0.0` are related to _timing attacks_. The used
_Key Derivation Function_ on both `commit` and `reveal` phases conceals a lot the
response time if this library is used as an _oracle_ (that is, an external server).
On the other hand, this algorithm is open and then the attacker can pre-compute the
derivation keys, and just perform her own cryptoanalysis on the next steps of the
algorithm.

Assuming that our _Nocoiner_ algorithm is just a black-box (oracle) where all the
steps are called "atomically", there are still some exploitable information if the
attacker gains access on the host machine for the oracle service. The benchmarks
provided with the `core_bench` library only work well for functions halting under
milliseconds, the KDF imposes a computation around few seconds. Due that issue, we
execute the benchmarks with a lower KDF cost (just to cover the possibility of
pre-computed derived keys, and also to remove timing noise imposed by a KDF with
stronger cycles). To run the benchmarks, just type `$ make bench` on this project's
root directory.

We will only take the relevant information (with major differences). The version
`1.0.0` is vulnerable during the opening phase, mostly 'cause:

- We compare the tags for the authenticated ciphertext in non-constant /
non-linear time. This is the most famous kind of exploitable timing attack.
- We don't decypher the AES ciphertext even if the opening key is wrong (don't
pass the MAC tag test). The result plaintext will be ignored 'cause the
authentication failed, but decryption must be performed to not leak side
information for the attacker.

The benchmarks results stored on this repository were performed on an Intel(R)
Dual-Core Celeron(R) of 1GHz each (both vulnerable to Meltdown, Spectre and MDS
CPU bugs, and possibly some NSA hardware backdoors too, you know). The first test
is the one with valid inputs, and the rest are evaluated with invalid inputs:

<p></p>

| Name | Time/Run | Cycls/Run | mWd/Run | mWd Overhd | mjWd/Run | mjWd Overhd | mGC/Run | Percentage |
|:-------------------|---------:|----------:|--------:|-----------:|---------:|------------:|---------:|-----------:|
| bound opening | 830.05us | 863.66kc | 19.77kw | 24.18w | 17.87w | 116.97w | 70.61e-3 | 100.00% |
| unbound commitment | 809.76us | 842.54kc | 19.56kw | 28.59w | 20.29w | -219.19w | 69.79e-3 | 97.56% |
| unbound opening | 807.82us | 840.52kc | 19.56kw | 28.59w | 20.29w | -219.19w | 69.79e-3 | 97.32% |

<div align="center" style="text-align: center;"><center>
<i>This table shows informations about the GC, minor heap & major heap. All cases
were executed with major heap compaction disabled to not mask execution time.</i>
</center></div>

<p></p>

As you can see, there's much more computations performed on valid/bound inputs than on unbound inputs. Inputs
are bound (the opening key and the commitment box) if they were previously computed during commitment phase.
Otherwise, the inputs are unbound _even if they were computed over the same secret during commitment_. This
is a huge important thing when we want a group of commitments (performed by many parties) to be independent
of each other. The security patch introduced on version `1.0.1` uses the [eqaf][10] library to compare in
constant time the MAC tags, and we also force decryption step even if a MAC tag mismatch occurs (obviously
the decrypted plain-text is ignored in this case and the whole opening phase fails).



### Disclaimer

This library was not fully tested against side-channel attacks. Keep in mind
Expand All @@ -217,4 +278,5 @@ process context).
[6]: https://en.wikipedia.org/wiki/Authenticated_encryption
[7]: https://marcoonroad.dev/nocoiner/apiref/nocoiner/Nocoiner/index.html
[8]: https://marcoonroad.dev/nocoiner/apicov/index.html
[9]: https://github.com/marcoonroad/nocoiner/issues/1
[9]: https://github.com/marcoonroad/nocoiner/issues/1
[10]: https://github.com/mirage/eqaf
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.0
1.0.1
2 changes: 1 addition & 1 deletion docs/dune
Original file line number Diff line number Diff line change
@@ -1 +1 @@
(dirs :standard \ vendor)
(dirs :standard \ vendor _site .bundle)
2 changes: 1 addition & 1 deletion dune-project
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(lang dune 1.9)
(name nocoiner)
(version 1.0.0)
(version 1.0.1)
(using fmt 1.1)
12 changes: 12 additions & 0 deletions lib/constants.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Sys = Core.Sys
module Option = Core.Option
module Int = Core.Int

let get variable default =
let optional = Sys.getenv variable in
Option.value optional ~default


let _KDF_COST = get "NOCOINER_KDF_COST" "8192" |> Int.of_string

let _KDF_WORKERS = get "NOCOINER_KDF_WORKERS" "2" |> Int.of_string
3 changes: 3 additions & 0 deletions lib/constants.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
val _KDF_COST : int

val _KDF_WORKERS : int
3 changes: 2 additions & 1 deletion lib/dune
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
(name nocoiner)
(public_name nocoiner)
(wrapped true)
(libraries core nocrypto.unix nocrypto digestif digestif.c scrypt-kdf)
(libraries core nocrypto.unix nocrypto digestif digestif.c eqaf eqaf.cstruct
scrypt-kdf)
(synopsis "The Nocoiner module for nocoiner library.")
(preprocess
(pps bisect_ppx -conditional -no-comment-parsing)))
40 changes: 29 additions & 11 deletions lib/encryption.ml
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
module String = Core.String
module AES = Nocrypto.Cipher_block.AES.CBC

let _MIN_BITS = Entropy.min_bits (32 * 8)
let _MAX_BITS = Entropy.max_bits (32 * 8)

let __kdf key =
let aes_salt = Entropy.min_bits (32 * 8) in
let mac_salt = Entropy.max_bits (32 * 8) in
let aes_salt = _MIN_BITS in
let mac_salt = _MAX_BITS in
let aes_key = Hardening.kdf ~size:32l ~salt:aes_salt key in
let mac_key = Hardening.kdf ~size:32l ~salt:mac_salt key in
(aes_key, mac_key)


let hash data = Cstruct.of_hex @@ Hashing.hash @@ Cstruct.to_string data
let hash data = Cstruct.of_string @@ Hashing.raw_hash @@ Cstruct.to_string data

let mac ~key data =
let key', data' = (Cstruct.to_string key, Cstruct.to_string data) in
Cstruct.of_hex @@ Hashing.mac ~key:key' data'
Cstruct.of_string @@ Hashing.raw_mac ~key:key' data'


let encrypt ~key ~iv ~metadata ~message:msg =
Expand All @@ -24,19 +27,34 @@ let encrypt ~key ~iv ~metadata ~message:msg =
AES.encrypt ~iv ~key:aes_key' @@ Cstruct.of_string plaintext
in
let secret = hash mac_key in
let payload = Cstruct.concat [ metadata; iv; ciphertext ] in
let payload = Cstruct.append metadata @@ Cstruct.append iv ciphertext in
let tag = mac ~key:secret payload in
(ciphertext, tag)


exception DecryptedPlaintext of Cstruct.t

let decrypt ~reason ~key ~iv ~metadata ~cipher ~tag =
let aes_key, mac_key = __kdf key in
let secret = hash mac_key in
let payload = Cstruct.concat [ metadata; iv; cipher ] in
let payload = Cstruct.append metadata @@ Cstruct.append iv cipher in
let tag' = mac ~key:secret payload in
if Cstruct.equal tag tag'
then
let aes_key' = AES.of_secret aes_key in
let plaintext = AES.decrypt ~iv ~key:aes_key' cipher in
(* we decypher before the tag verification to avoid
exploitable side-channels vulnerabilities such as
timing attacks. we also check the tags in linear
time regarding the tag size in bytes *)
let aes_key' = AES.of_secret aes_key in
let plaintext = AES.decrypt ~iv ~key:aes_key' cipher in
let decrypted =
Cstruct.of_string @@ Helpers.unpad @@ Cstruct.to_string plaintext
else raise reason
in
(* forces both bound and unbound flows to pass through the exception triggering
pipeline. this is just to approximate both execution timings to reduce the
vector attacks for side-channel attacks *)
try
if Eqaf_cstruct.equal tag tag'
then raise (DecryptedPlaintext decrypted)
else raise reason
with
| DecryptedPlaintext result ->
result
4 changes: 2 additions & 2 deletions lib/entropy.ml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module RngZ = Nocrypto.Rng.Z
module NumZ = Nocrypto.Numeric.Z

let __gen_min bits = "1" ^ Core.String.init (bits - 1) ~f:(Core.const '0')
let __gen_min bits = "1" ^ Core.String.make (bits - 1) '0'

let __gen_max bits = Core.String.init bits ~f:(Core.const '1')
let __gen_max bits = Core.String.make bits '1'

let __max_bits bits = Z.of_string_base 2 @@ __gen_max bits

Expand Down
10 changes: 6 additions & 4 deletions lib/fingerprint.ml
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
module List = Core.List

let hash data = Cstruct.of_hex @@ Hashing.hash data
let hash data = Cstruct.of_string @@ Hashing.raw_hash data

let xor = Nocrypto.Uncommon.Cs.xor

let xor_list = List.reduce_exn ~f:xor

let id () =
let timestamp = hash @@ string_of_float @@ Unix.gettimeofday () in
let pid = hash @@ string_of_int @@ Unix.getpid () in
let hostname = hash @@ Unix.gethostname () in
let cwd = hash @@ Unix.getcwd () in
let context =
Cstruct.to_string @@ xor_list [ timestamp; pid; hostname; cwd ]
timestamp
|> xor pid
|> xor hostname
|> xor cwd
|> Cstruct.to_string
in
Encoding.encode @@ Hashing.raw_hash context
10 changes: 9 additions & 1 deletion lib/hardening.ml
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
open Constants

let kdf ~size ~salt password =
Scrypt_kdf.scrypt_kdf ~password ~salt ~dk_len:size ~r:8 ~p:2 ~n:8192
Scrypt_kdf.scrypt_kdf
~password
~salt
~dk_len:size
~r:8
~p:_KDF_WORKERS
~n:_KDF_COST
4 changes: 1 addition & 3 deletions lib/hashing.ml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
module Blake2B = Digestif.BLAKE2B

let hash data = Blake2B.to_hex @@ Blake2B.digest_string data

let raw_hash data = Blake2B.to_raw_string @@ Blake2B.digest_string data

let mac ~key data = Blake2B.to_hex @@ Blake2B.Keyed.mac_string ~key data
let raw_mac ~key data = Blake2B.to_raw_string @@ Blake2B.Keyed.mac_string ~key data
4 changes: 1 addition & 3 deletions lib/hashing.mli
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
val hash : string -> string

val raw_hash : string -> string

val mac : key:string -> string -> string
val raw_mac : key:string -> string -> string
8 changes: 5 additions & 3 deletions lib/helpers.ml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
module Int = Core.Int
module String = Core.String
module Char = Core.Char

let __nullchar = Char.of_int_exn 0
let __nullchar = Char.unsafe_chr 0

let pad ~basis msg =
let encoded = Encoding.encode msg in
Expand All @@ -14,4 +13,7 @@ let pad ~basis msg =

let __nonzero char = char != __nullchar

let unpad msg = Encoding.decode @@ String.filter ~f:__nonzero msg
(* ignores input if it can't be base64-decoded after dropping null-padding data *)
let unpad msg =
let filtered = String.filter ~f:__nonzero msg in
try Encoding.decode @@ filtered with Failure _ -> msg
13 changes: 7 additions & 6 deletions nocoiner.opam
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
opam-version: "2.0"
name: "nocoiner"
version: "1.0.0"
version: "1.0.1"
synopsis: "A Commitment Scheme library for Coin Flipping/Tossing algorithms and sort"
description: """
This project implements Commitment Schemes using the
Expand All @@ -20,16 +20,17 @@ build: [
["dune" "build" "-p" name "-j" jobs]
]

run-test: ["dune" "runtest" "-p" name "-j" jobs]
run-test: ["dune" "build" "@test/spec/runtest" "-p" name "-j" jobs]

depends: [
"ocaml" {>= "4.03.0"}
"dune" {>= "1.9"}
"cmdliner" {>= "1.0.0"}
"alcotest" {with-test}
"nocrypto" {>= "0.5.4-1"}
"alcotest" {>= "0.8.0" & with-test}
"nocrypto" {>= "0.5.0"}
"scrypt-kdf" {>= "1.0.0"}
"digestif" {>= "0.7.0"}
"core" {>= "v0.9.1"}
"bisect_ppx" {>= "1.4.1"}
"core" {>= "v0.9.0"}
"eqaf" {>= "0.5"}
"bisect_ppx" {>= "1.4.0"}
]
Loading

0 comments on commit 4a5928c

Please sign in to comment.