From 8cd7d0857347849c92c840fdddcac0f4cd60aef9 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 13 May 2020 19:13:20 +0200 Subject: [PATCH] ADDED: crypto_data_hash/3, computing cryptographically secure digests --- README.md | 2 + src/prolog/clause_types.rs | 5 +- src/prolog/lib/crypto.pl | 131 ++++++++++++++++++++++++----- src/prolog/machine/system_calls.rs | 67 +++++++++++++++ 4 files changed, 185 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 1eb98982..26aec64e 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,8 @@ The modules that ship with Scryer Prolog are also called Probabilistic predicates and random number generators. * [`sockets`](src/prolog/lib/sockets.pl) Predicates for opening and accepting TCP connections as streams. +* [`crypto`](src/prolog/lib/crypto.pl) + Cryptographically secure random numbers and hashes. To read contents of external files, use `phrase_from_file/2` from [`library(pio)`](src/prolog/lib/pio.pl) to apply a DCG to diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index 0850735c..d6c4e13d 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -286,7 +286,8 @@ pub enum SystemClauseType { WriteTerm, WriteTermToChars, ScryerPrologVersion, - CryptoRandomByte + CryptoRandomByte, + CryptoDataHash } impl SystemClauseType { @@ -470,6 +471,7 @@ impl SystemClauseType { &SystemClauseType::WriteTermToChars => clause_name!("$write_term_to_chars"), &SystemClauseType::ScryerPrologVersion => clause_name!("$scryer_prolog_version"), &SystemClauseType::CryptoRandomByte => clause_name!("$crypto_random_byte"), + &SystemClauseType::CryptoDataHash => clause_name!("$crypto_data_hash"), } } @@ -633,6 +635,7 @@ impl SystemClauseType { ("$write_term_to_chars", 7) => Some(SystemClauseType::WriteTermToChars), ("$scryer_prolog_version", 1) => Some(SystemClauseType::ScryerPrologVersion), ("$crypto_random_byte", 1) => Some(SystemClauseType::CryptoRandomByte), + ("$crypto_data_hash", 3) => Some(SystemClauseType::CryptoDataHash), _ => None, } } diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 36f68009..67b5ac80 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -13,27 +13,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ :- module(crypto, [hex_bytes/2, - crypto_n_random_bytes/2]). + crypto_n_random_bytes/2, + crypto_data_hash/3 + ]). :- use_module(library(error)). :- use_module(library(lists)). :- use_module(library(between)). :- use_module(library(dcgs)). -% hex_bytes(?Hex, ?Bytes) is det. -% -% Relation between a hexadecimal sequence and a list of bytes. Hex -% is a string of hexadecimal numbers. Bytes is a list of *integers* -% between 0 and 255 that represent the sequence as a list of bytes. -% At least one of the arguments must be instantiated. -% -% Example: -% -% == -% ?- hex_bytes("501ACE", Bs). -% Bs = [80,26,206] -% ; false. -% == +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + hex_bytes(?Hex, ?Bytes) is det. + + Relation between a hexadecimal sequence and a list of bytes. Hex + is a string of hexadecimal numbers. Bytes is a list of *integers* + between 0 and 255 that represent the sequence as a list of bytes. + At least one of the arguments must be instantiated. + + Example: + + ?- hex_bytes("501ACE", Bs). + Bs = [80,26,206] + ; false. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + hex_bytes(Hs, Bytes) :- ( ground(Hs) -> @@ -45,10 +48,7 @@ hex_bytes(Hs, Bytes) :- ) ; must_be(list, Bytes), maplist(must_be(integer), Bytes), - ( member(B, Bytes), \+ between(0, 255, B) -> - type_error(byte, B, hex_bytes/2) - ; true - ), + must_be_bytes(Bytes, hex_bytes/2), phrase(bytes_hex(Bytes), Hs) ). @@ -72,9 +72,102 @@ char_hexval(C, H) :- nth0(H, "0123456789abcdef", C), !. char_hexval(C, H) :- nth0(H, "0123456789ABCDEF", C), !. +must_be_bytes(Bytes, Context) :- + ( member(B, Bytes), \+ between(0, 255, B) -> + type_error(byte, B, Context) + ; true + ). + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + crypto_n_random_bytes(+N, -Bytes) is det + + Bytes is unified with a list of N cryptographically secure + pseudo-random bytes. Each byte is an integer between 0 and 255. If + the internal pseudo-random number generator (PRNG) has not been + seeded with enough entropy to ensure an unpredictable byte + sequence, an exception is thrown. + + One way to relate such a list of bytes to an _integer_ is to use + CLP(ℤ) constraints as follows: + + :- use_module(library(clpz)). + :- use_module(library(lists)). + + bytes_integer(Bs, N) :- + foldl(pow, Bs, 0-0, N-_). + + pow(B, N0-I0, N-I) :- + B in 0..255, + N #= N0 + B*256^I0, + I #= I0 + 1. + + With this definition, we can generate a random 256-bit integer + _from_ a list of 32 random _bytes_: + + ?- crypto_n_random_bytes(32, Bs), + bytes_integer(Bs, I). + Bs = [146,166,162,210,242,7,25,132,64,94|...], + I = 337420085690608915485...(56 digits omitted) + + The above relation also works in the other direction, letting you + translate an integer _to_ a list of bytes. In addition, you can + use hex_bytes/2 to convert bytes to _tokens_ that can be easily + exchanged in your applications. + + ?- crypto_n_random_bytes(12, Bs), + hex_bytes(Hex, Bs). + Bs = [34,25,50,72,58,63,50,172,32,46|...], Hex = "221932483a3f32ac202 ..." +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + crypto_n_random_bytes(N, Bs) :- must_be(integer, N), length(Bs, N), maplist(crypto_random_byte, Bs). crypto_random_byte(B) :- '$crypto_random_byte'(B). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + crypto_data_hash(+Data, -Hash, +Options) + + Where Data is a list of bytes (integers between 0 and 255), + and Hash is the computed hash as a list of hexadecimal characters. + + The single supported option is: + + algorithm(A) + + where A is one of sha256, sha384, sha512, sha512_256, or a variable. + + If A is a variable, then it is unified with the default algorithm, + which is an algorithm that is considered cryptographically secure + at the time of this writing. + + Example: + + ?- crypto_data_hash([0'a,0'b,0'c], Hs, [algorithm(sha256)]). + Hs = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" + ; false. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +crypto_data_hash(Data, Hash, Options) :- + must_be(list, Data), + must_be_bytes(Data, crypto_data_hash/3), + must_be(list, Options), + ( Options = [algorithm(A)] -> true + ; true + ), + ( var(A) -> A = sha256 + ; true + ), + ( hash_algorithm(A) -> true + ; domain_error(hash_algorithm, A, crypto_data_hash/3) + ), + '$crypto_data_hash'(Data, HashBytes, A), + hex_bytes(Hash, HashBytes). + +hash_algorithm(sha256). +hash_algorithm(sha512). +hash_algorithm(sha384). +hash_algorithm(sha512_256). diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 3b8409a3..47d7f911 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -39,6 +39,7 @@ use crate::crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers}; use crate::crossterm::terminal::{enable_raw_mode, disable_raw_mode}; use ring::rand::{SecureRandom, SystemRandom}; +use ring::digest; pub fn get_key() -> KeyEvent { let key; @@ -5203,6 +5204,72 @@ impl MachineState { self.unify(arg, byte); } + &SystemClauseType::CryptoDataHash => { + let mut bytes: Vec = Vec::new(); + + let stub = MachineError::functor_stub(clause_name!("crypto_data_hash"), 3); + + match self.try_from_list(temp_v!(1), stub) { + Err(e) => return Err(e), + Ok(addrs) => { + + for addr in addrs { + let addr = self.store(self.deref(addr)); + + match Number::try_from((addr, &self.heap)) { + Ok(Number::Fixnum(n)) => { + match u8::try_from(n) { + Ok(b) => { + bytes.push(b); + } + Err(_) => { } + } + + continue; + } + Ok(Number::Integer(n)) => { + if let Some(b) = n.to_u8() { + bytes.push(b); + } + + continue; + } + _ => { + } + } + } + } + } + + let algorithm = self[temp_v!(3)]; + let algorithm_str = match self.store(self.deref(algorithm)) { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref atom, _) = &self.heap[h] { + atom.as_str() + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + }; + + let hash = digest::digest( + match algorithm_str { + "sha256" => { &digest::SHA256 } + "sha384" => { &digest::SHA384 } + "sha512" => { &digest::SHA512 } + "sha512_256" => { &digest::SHA512_256 } + _ => { unreachable!() } + }, + &bytes); + + let ints = hash.as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))); + let ints_list = Addr::HeapCell(self.heap.to_list(ints)); + + self.unify(self[temp_v!(2)], ints_list); + } }; return_from_clause!(self.last_call, self) -- 2.54.0