]> Repositorios git - scryer-prolog.git/commitdiff
ADDED: HMAC-based key derivation (HKDF) via crypto_data_hkdf/4
authorMarkus Triska <[email protected]>
Thu, 14 May 2020 18:07:59 +0000 (20:07 +0200)
committerMarkus Triska <[email protected]>
Thu, 14 May 2020 18:07:59 +0000 (20:07 +0200)
This is useful to generate keys and initialization vectors
from suitable input keying material, so that future predicates
for symmetric encryption can be used with appropriate parameters.

README.md
src/prolog/clause_types.rs
src/prolog/lib/crypto.pl
src/prolog/machine/machine_state_impl.rs
src/prolog/machine/system_calls.rs

index 61a4cc9e474bd5fbe7fdfeffe1305de57daff1ba..1e39f219f8b7fed75f41faa3e9c97ef3b0022c17 100644 (file)
--- a/README.md
+++ b/README.md
@@ -378,8 +378,8 @@ The modules that ship with Scryer&nbsp;Prolog are also called
 * [`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, and
-  reasoning about elliptic curves.
+  Cryptographically secure random numbers and hashes, HMAC-based
+  key derivation (HKDF), and reasoning about elliptic curves.
 
 To read contents of external files, use `phrase_from_file/2` from
 [`library(pio)`](src/prolog/lib/pio.pl) to apply a&nbsp;DCG to
index d6c4e13dde5713d9073942f3a70cb9eb9cc65203..30576ee8803e585641fa08dbb72319654e798a02 100644 (file)
@@ -287,7 +287,8 @@ pub enum SystemClauseType {
     WriteTermToChars,
     ScryerPrologVersion,
     CryptoRandomByte,
-    CryptoDataHash
+    CryptoDataHash,
+    CryptoDataHKDF
 }
 
 impl SystemClauseType {
@@ -472,6 +473,7 @@ impl SystemClauseType {
             &SystemClauseType::ScryerPrologVersion => clause_name!("$scryer_prolog_version"),
             &SystemClauseType::CryptoRandomByte => clause_name!("$crypto_random_byte"),
             &SystemClauseType::CryptoDataHash => clause_name!("$crypto_data_hash"),
+            &SystemClauseType::CryptoDataHKDF => clause_name!("$crypto_data_hkdf"),
         }
     }
 
@@ -636,6 +638,7 @@ impl SystemClauseType {
             ("$scryer_prolog_version", 1) => Some(SystemClauseType::ScryerPrologVersion),
             ("$crypto_random_byte", 1) => Some(SystemClauseType::CryptoRandomByte),
             ("$crypto_data_hash", 3) => Some(SystemClauseType::CryptoDataHash),
+            ("$crypto_data_hkdf", 6) => Some(SystemClauseType::CryptoDataHKDF),
             _ => None,
         }
     }
index 23a3273fd9e78e1fc4a45e3ecc2ae41dd0a1ad89..2968d4a1ebbaa08ee576d0c387708acd1531c13d 100644 (file)
 
 :- module(crypto, [hex_bytes/2,
                    crypto_n_random_bytes/2,
-                   crypto_data_hash/3,
+                   crypto_data_hash/3,         % +Data, -Hash, +Options
+                   crypto_data_hkdf/4,         % +Data, +Length, -Bytes, +Options
                    crypto_name_curve/2,        % +Name, -Curve
                    crypto_curve_order/2,       % +Curve, -Order
                    crypto_curve_generator/2,   % +Curve, -Generator
                    crypto_curve_scalar_mult/4  % +Curve, +Scalar, +Point, -Result
-                   ]).
+                  ]).
 
 :- use_module(library(error)).
 :- use_module(library(lists)).
@@ -52,9 +53,7 @@ hex_bytes(Hs, Bytes) :-
                 true
             ;   domain_error(hex_encoding, Hs, hex_bytes/2)
             )
-        ;   must_be(list, Bytes),
-            maplist(must_be(integer), Bytes),
-            must_be_bytes(Bytes, hex_bytes/2),
+        ;   must_be_bytes(Bytes, hex_bytes/2),
             phrase(bytes_hex(Bytes), Hs)
         ).
 
@@ -79,6 +78,8 @@ char_hexval(C, H) :- nth0(H, "0123456789ABCDEF", C), !.
 
 
 must_be_bytes(Bytes, Context) :-
+        must_be(list, Bytes),
+        maplist(must_be(integer), Bytes),
         (   member(B, Bytes), \+ between(0, 255, B) ->
             type_error(byte, B, Context)
         ;   true
@@ -86,6 +87,9 @@ must_be_bytes(Bytes, Context) :-
 
 
 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+   Cryptographically secure random numbers
+   =======================================
+
    crypto_n_random_bytes(+N, -Bytes) is det
 
    Bytes is unified with a list of N cryptographically secure
@@ -135,10 +139,14 @@ crypto_n_random_bytes(N, Bs) :-
 crypto_random_byte(B) :- '$crypto_random_byte'(B).
 
 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+   Hashing
+   =======
+
    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.
+   Where Data is a list of bytes (integers between 0 and 255) or
+   characters, and Hash is the computed hash as a list of hexadecimal
+   characters.
 
    The single supported option is:
 
@@ -153,27 +161,43 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B).
 
    Example:
 
-   ?- crypto_data_hash([0'a,0'b,0'c], Hs, [algorithm(sha256)]).
+   ?- crypto_data_hash("abc", 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
-        ),
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+   SHA256 is the current default for several hash-related predicates.
+   It is deemed sufficiently secure for the foreseeable future.  Yet,
+   application programmers must be aware that the default may change in
+   future versions. The hash predicates all yield the algorithm they
+   used if a Prolog variable is used for the pertaining option.
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+crypto_data_hash(Data0, Hash, Options0) :-
+        chars_bytes_(Data0, Data, crypto_data_hash/3),
+        must_be(list, Options0),
+        functor_hash_options(algorithm, A, Options0, _),
         (   hash_algorithm(A) -> true
         ;   domain_error(hash_algorithm, A, crypto_data_hash/3)
         ),
         '$crypto_data_hash'(Data, HashBytes, A),
         hex_bytes(Hash, HashBytes).
 
+
+default_hash(sha256).
+
+functor_hash_options(F, Hash, Options0, [Option|Options]) :-
+        Option =.. [F,Hash],
+        (   select(Option, Options0, Options) ->
+            (   var(Hash) ->
+                default_hash(Hash)
+            ;   must_be(atom, Hash)
+            )
+        ;   Options = Options0,
+            default_hash(Hash)
+        ).
+
 hash_algorithm(ripemd160).
 hash_algorithm(sha256).
 hash_algorithm(sha512).
@@ -181,6 +205,60 @@ hash_algorithm(sha384).
 hash_algorithm(sha512_256).
 
 
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+   crypto_data_hkdf(+Data, +Length, -Bytes, +Options) is det.
+
+   Concentrate possibly dispersed entropy of Data and then expand it
+   to the desired length. Data is a list of bytes or characters.
+
+   Bytes is unified with a list of bytes of length Length, and is
+   suitable as input keying material and initialization vectors to
+   symmetric encryption algorithms.
+
+   Admissible options are:
+
+     - algorithm(+Algorithm)
+       A hashing algorithm as specified to crypto_data_hash/3. The
+       default is a cryptographically secure algorithm. If you
+       specify a variable, then it is unified with the algorithm
+       that was used, which is a cryptographically secure algorithm.
+     - info(+Info)
+       Optional context and application specific information,
+       specified as a list of bytes or characters. The default is [].
+     - salt(+List)
+       Optionally, a list of bytes that are used as salt. The
+       default is all zeroes.
+
+   The `info/1`  option can be  used to  generate multiple keys  from a
+   single  master key,  using for  example values  such as  "key" and
+   "iv", or the name of a file that is to be encrypted.
+
+   See crypto_n_random_bytes/2 to obtain a suitable salt.
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+crypto_data_hkdf(Data0, L, Bytes, Options0) :-
+        functor_hash_options(algorithm, Algorithm, Options0, Options),
+        chars_bytes_(Data0, Data, crypto_data_hkdf/4),
+        option(salt(SaltBytes), Options, []),
+        must_be_bytes(SaltBytes, crypto_data_hkdf/4),
+        option(info(Info0), Options, []),
+        chars_bytes_(Info0, Info, crypto_data_hkdf/4),
+        '$crypto_data_hkdf'(Data, SaltBytes, Info, Algorithm, L, Bytes).
+
+option(What, Options, Default) :-
+        (   member(What, Options) -> true
+        ;   What =.. [_,Default]
+        ).
+
+chars_bytes_(Cs, Bytes, Context) :-
+        must_be(list, Cs),
+        (   maplist(integer, Cs) -> Bytes = Cs
+        ;   % use chars_utf8bytes/2 here once it becomes available!
+            maplist(atom_codes, Cs, Css),
+            append(Css, Bytes)
+        ),
+        must_be_bytes(Bytes, Context).
+
 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Modular multiplicative inverse.
 
index ea1d722afaaeab7ba44102849557ec49951d89b2..ab0a796cf98b7350ddd0f0961d191be093566f97 100644 (file)
@@ -2746,6 +2746,51 @@ impl MachineState {
         *list = result;
     }
 
+
+    pub(super)
+    fn integers_to_bytevec(
+        &self,
+        r: RegType,
+        caller: MachineStub,
+    ) -> Vec<u8> {
+
+        let mut bytes: Vec<u8> = Vec::new();
+
+        match self.try_from_list(r, caller) {
+            Err(_) => { unreachable!() }
+            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;
+                        }
+                        _ => {
+                        }
+                    }
+                }
+            }
+        }
+        bytes
+    }
+
+
     pub(super)
     fn try_from_list(
         &self,
index b572af41ef8ad054f2b757538c003aeba66bfa9d..0de1f423a7994b2490ed8c6b007f253c12a99c75 100644 (file)
@@ -39,7 +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;
+use ring::{digest,hkdf};
 use ripemd160::{Ripemd160, Digest};
 
 pub fn get_key() -> KeyEvent {
@@ -5206,41 +5206,8 @@ impl MachineState {
                 self.unify(arg, byte);
             }
             &SystemClauseType::CryptoDataHash => {
-                let mut bytes: Vec<u8> = 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 bytes = self.integers_to_bytevec(temp_v!(1), stub);
 
                 let algorithm = self[temp_v!(3)];
                 let algorithm_str = match self.store(self.deref(algorithm)) {
@@ -5276,6 +5243,58 @@ impl MachineState {
 
                 self.unify(self[temp_v!(2)], ints_list);
             }
+            &SystemClauseType::CryptoDataHKDF => {
+                let stub1 = MachineError::functor_stub(clause_name!("crypto_data_hkdf"), 6);
+                let data = self.integers_to_bytevec(temp_v!(1), stub1);
+                let stub2 = MachineError::functor_stub(clause_name!("crypto_data_hkdf"), 6);
+                let salt = self.integers_to_bytevec(temp_v!(2), stub2);
+                let stub3 = MachineError::functor_stub(clause_name!("crypto_data_hkdf"), 6);
+                let info = self.integers_to_bytevec(temp_v!(3), stub3);
+
+                let algorithm = match self.store(self.deref(self[temp_v!(4)])) {
+                    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 length =
+                    match Number::try_from((self[temp_v!(5)], &self.heap)) {
+                        Ok(Number::Fixnum(n)) => {
+                            usize::try_from(n).unwrap()
+                        }
+                        Ok(Number::Integer(n)) => {
+                            n.to_usize().unwrap()
+                        }
+                        _ => {
+                            unreachable!()
+                        }
+                    };
+
+                let ints_list =
+                        {   let digest_alg  =
+                               match algorithm {
+                                  "sha256" =>     { hkdf::HKDF_SHA256 }
+                                  "sha384" =>     { hkdf::HKDF_SHA384 }
+                                  "sha512" =>     { hkdf::HKDF_SHA512 }
+                                  _ =>            { unreachable!() }
+                               };
+                             let salt = hkdf::Salt::new(digest_alg, &salt);
+                             let mut bytes : Vec<u8> = Vec::new();
+                             bytes.resize(length, 0);
+                             salt.extract(&data).expand(&[&info[..]], MyKey(length)).unwrap().fill(&mut bytes).unwrap();
+
+                             Addr::HeapCell(self.heap.to_list(bytes.iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b))))))
+                        };
+
+                self.unify(self[temp_v!(6)], ints_list);
+            }
         };
 
         return_from_clause!(self.last_call, self)
@@ -5292,3 +5311,11 @@ fn rng() -> &'static dyn SecureRandom {
 
     RANDOM.deref()
 }
+
+struct MyKey<T: core::fmt::Debug + PartialEq>(T);
+
+impl hkdf::KeyType for MyKey<usize> {
+    fn len(&self) -> usize {
+        self.0
+    }
+}