]> Repositorios git - scryer-prolog.git/commitdiff
Authenticated encryption in library(crypto), new encoding/1 option for hashes (#515)
authorMarkus Triska <[email protected]>
Sat, 16 May 2020 17:51:41 +0000 (19:51 +0200)
committerGitHub <[email protected]>
Sat, 16 May 2020 17:51:41 +0000 (11:51 -0600)
* shorten n_newlines//1

* remove unneeded variable

* ADDED: authenticated encryption and decryption with ChaCha20-Poly1305

* ADDED: encoding/1 option for crypto_data_hash/3 and crypto_data_hkdf/4

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

index 013327c6a492a688e80ccdd91a3c00e4a9cc5263..ed9161f5157a216446d7df94db3f78e6c4be671a 100644 (file)
--- a/README.md
+++ b/README.md
@@ -380,7 +380,7 @@ The modules that ship with Scryer&nbsp;Prolog are also called
 * [`crypto`](src/prolog/lib/crypto.pl)
   Cryptographically secure random numbers and hashes, HMAC-based
   key derivation (HKDF), password-based key derivation (PBKDF2),
-  and reasoning about elliptic curves.
+  authenticated encryption, 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 fc4d1c944b4c21ad2d15d32b77e9580334876553..cf4e1aa6ffc3a77426b9aca7e300740d8aa763bc 100644 (file)
@@ -289,7 +289,9 @@ pub enum SystemClauseType {
     CryptoRandomByte,
     CryptoDataHash,
     CryptoDataHKDF,
-    CryptoPasswordHash
+    CryptoPasswordHash,
+    CryptoDataEncrypt,
+    CryptoDataDecrypt
 }
 
 impl SystemClauseType {
@@ -476,6 +478,8 @@ impl SystemClauseType {
             &SystemClauseType::CryptoDataHash => clause_name!("$crypto_data_hash"),
             &SystemClauseType::CryptoDataHKDF => clause_name!("$crypto_data_hkdf"),
             &SystemClauseType::CryptoPasswordHash => clause_name!("$crypto_password_hash"),
+            &SystemClauseType::CryptoDataEncrypt => clause_name!("$crypto_data_encrypt"),
+            &SystemClauseType::CryptoDataDecrypt => clause_name!("$crypto_data_decrypt"),
         }
     }
 
@@ -642,6 +646,8 @@ impl SystemClauseType {
             ("$crypto_data_hash", 3) => Some(SystemClauseType::CryptoDataHash),
             ("$crypto_data_hkdf", 6) => Some(SystemClauseType::CryptoDataHKDF),
             ("$crypto_password_hash", 4) => Some(SystemClauseType::CryptoPasswordHash),
+            ("$crypto_data_encrypt", 5) => Some(SystemClauseType::CryptoDataEncrypt),
+            ("$crypto_data_decrypt", 5) => Some(SystemClauseType::CryptoDataDecrypt),
             _ => None,
         }
     }
index 0bb9d2a0ddc7ae8d65ecd692515b5dddbec2215e..b93a9cff595f71d77f0da32ea855b4aee8e41961 100644 (file)
    using strings leaves little trace of what was processed in the system,
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
-:- module(crypto, [hex_bytes/2,                % ?Hex, ?Bytes
-                   crypto_n_random_bytes/2,    % +N, -Bytes
-                   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
-                   crypto_password_hash/2,     % +Password, ?Hash
-                   crypto_password_hash/3      % +Password, -Hash, +Options
-                  ]).
+:- module(crypto,
+          [hex_bytes/2,                % ?Hex, ?Bytes
+           crypto_n_random_bytes/2,    % +N, -Bytes
+           crypto_data_hash/3,         % +Data, -Hash, +Options
+           crypto_data_hkdf/4,         % +Data, +Length, -Bytes, +Options
+           crypto_password_hash/2,     % +Password, ?Hash
+           crypto_password_hash/3,     % +Password, -Hash, +Options
+           crypto_data_encrypt/6,      % +PlainText, +Algorithm, +Key, +IV, -CipherText, +Options
+           crypto_data_decrypt/6,      % +CipherText, +Algorithm, +Key, +IV, -PlainText, +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)).
@@ -152,16 +155,17 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B).
    characters, and Hash is the computed hash as a list of hexadecimal
    characters.
 
-   The single supported option is:
+   Options is a list of:
 
-      algorithm(A)
-
-   where A is one of ripemd160, 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.
+     - algorithm(+A)
+       where A is one of ripemd160, 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.
+     - encoding(+Encoding)
+       The default encoding is utf8. The alternative is octet,
+       to treat the input as a list of raw bytes.
 
    Example:
 
@@ -179,8 +183,9 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B).
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
 crypto_data_hash(Data0, Hash, Options0) :-
-        chars_bytes_(Data0, Data, crypto_data_hash/3),
         must_be(list, Options0),
+        option(encoding(Encoding), Options0, utf8),
+        encoding_bytes(Encoding, Data0, Data),
         functor_hash_options(algorithm, A, Options0, _),
         (   hash_algorithm(A) -> true
         ;   domain_error(hash_algorithm, A, crypto_data_hash/3)
@@ -232,6 +237,9 @@ hash_algorithm(sha512_256).
      - salt(+List)
        Optionally, a list of bytes that are used as salt. The
        default is all zeroes.
+     - encoding(+Encoding)
+       The default encoding is utf8. The alternative is octet,
+       to treat the input as a list of raw bytes.
 
    The `info/1`  option can be  used to  generate multiple keys  from a
    single  master key,  using for  example values  such as  "key" and
@@ -242,7 +250,8 @@ hash_algorithm(sha512_256).
 
 crypto_data_hkdf(Data0, L, Bytes, Options0) :-
         functor_hash_options(algorithm, Algorithm, Options0, Options),
-        chars_bytes_(Data0, Data, crypto_data_hkdf/4),
+        option(encoding(Encoding), Options, utf8),
+        encoding_bytes(Encoding, Data0, Data),
         option(salt(SaltBytes), Options, []),
         must_be_bytes(SaltBytes, crypto_data_hkdf/4),
         option(info(Info0), Options, []),
@@ -336,7 +345,7 @@ dollar_segments(Ls, Segments) :-
 
      - algorithm(+Algorithm)
        The algorithm to use. Currently, the only available algorithm
-       is =|pbkdf2-sha512|=, which is therefore also the default.
+       is 'pbkdf2-sha512', which is therefore also the default.
      - cost(+C)
        C is an integer, denoting the binary logarithm of the number
        of _iterations_ used for the derivation of the hash. This
@@ -435,6 +444,165 @@ bytes_base64_([A,B,C|Ls]) --> [W,X,Y,Z],
         bytes_base64_(Ls).
 
 
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+   crypto_data_encrypt(+PlainText,
+                       +Algorithm,
+                       +Key,
+                       +IV,
+                       -CipherText,
+                       +Options).
+
+   Encrypt the given PlainText, using the symmetric algorithm
+   Algorithm, key Key, and initialization vector (or nonce) IV, to
+   give CipherText.
+
+   PlainText must be a list of codes or characters, Key and IV must be
+   lists of bytes, and CipherText is created as a list of characters.
+
+   Keys and IVs can be chosen at random (using for example
+   crypto_n_random_bytes/2) or derived from input keying material (IKM)
+   using for example crypto_data_hkdf/4. This input is often a shared
+   secret, such as a negotiated point on an elliptic curve, or the hash
+   that was computed from a password via crypto_password_hash/3 with a
+   freshly generated and specified _salt_.
+
+   Reusing the same combination of Key and IV typically leaks at least
+   _some_ information about the plaintext. For example, identical
+   plaintexts will then correspond to identical ciphertexts. For some
+   algorithms, reusing an IV with the same Key has disastrous results
+   and can cause the loss of all properties that are otherwise
+   guaranteed. Especially in such cases, an IV is also called a
+   _nonce_ (number used once).
+
+   It is safe to store and  transfer the used initialization vector (or
+   nonce) in plain text, but the key _must be kept secret_.
+
+   Currently, the only supported algorithm is 'chacha20-poly1305', a
+   powerful and efficient _authenticated_ encryption scheme, providing
+   secrecy and at the same time reliable protection against undetected
+   _modifications_ of the encrypted data. This is a very good choice
+   for virtually all use cases. It is a stream cipher and can encrypt
+   data of any length up to 256 GB. Further, the encrypted data has
+   exactly the same length as the original, and no padding is used.
+
+   Options:
+
+      - encoding(+Encoding)
+      Encoding to use for PlainText. Default is utf8. The alternative
+      is octet to treat PlainText as raw bytes.
+
+      - tag(-List)
+      For authenticated encryption schemes, List is unified with a
+      list of _bytes_ holding the tag. This tag must be provided for
+      decryption.
+
+   Here is an example encryption and decryption, using the ChaCha20
+   stream cipher with the Poly1305 authenticator. This cipher uses a
+   256-bit key and a 96-bit nonce, i.e., 32 and 12 _bytes_,
+   respectively:
+
+    ?- Algorithm = 'chacha20-poly1305',
+       crypto_n_random_bytes(32, Key),
+       crypto_n_random_bytes(12, IV),
+       crypto_data_encrypt("this text is to be encrypted", Algorithm,
+                   Key, IV, CipherText, [tag(Tag)]),
+       crypto_data_decrypt(CipherText, Algorithm,
+                   Key, IV, RecoveredText, [tag(Tag)]).
+
+   Yielding:
+
+       Algorithm = 'chacha20-poly1305',
+       Key = [113,247,153,134,177,220,13,193,50,150|...],
+       IV = [135,20,149,153,63,35,68,114,247,171|...],
+       CipherText = "\x94\0Ej\x94\®Â\x95\óÑÆXÃn¾ð©b\x1c\ ...",
+       RecoveredText = "this text is to be  ...",
+       Tag = [152,117,152,17,162,75,150,206,144,40|...]
+
+   In this example, we use crypto_n_random_bytes/2 to generate a key
+   and nonce from cryptographically secure random numbers. For
+   repeated applications, you must ensure that a nonce is only used
+   _once_ together with the same key. Note that for _authenticated_
+   encryption schemes, the _tag_ that was computed during encryption
+   is necessary for decryption. It is safe to store and transfer the
+   tag in plain text.
+
+   See also crypto_data_decrypt/6, and hex_bytes/2 for conversion
+   between bytes and hex encoding.
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+crypto_data_encrypt(PlainText0, Algorithm, Key, IV, CipherText, Options) :-
+        option(encoding(Encoding), Options, utf8),
+        encoding_bytes(Encoding, PlainText0, PlainText),
+        option(tag(Tag), Options, _),
+        (   nonvar(Tag) ->
+            must_be_bytes(Tag, crypto_data_encrypt/6)
+        ;   true
+        ),
+        must_be_bytes(Key, crypto_data_encrypt/6),
+        must_be_bytes(IV, crypto_data_encrypt/6),
+        must_be(atom, Algorithm),
+        (   Algorithm = 'chacha20-poly1305' -> true
+        ;   domain_error('chacha20-poly1305', Algorithm, crypto_data_encrypt/6)
+        ),
+        '$crypto_data_encrypt'(PlainText, Key, IV, Tag, CipherText).
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+  crypto_data_decrypt(+CipherText,
+                      +Algorithm,
+                      +Key,
+                      +IV,
+                      -PlainText,
+                      +Options).
+
+   Decrypt the given CipherText, using the symmetric algorithm
+   Algorithm, key Key, and initialization vector IV, to give
+   PlainText. CipherText must be a list of bytes or characters, and
+   Key and IV must be lists of bytes. PlainText is created as a list
+   of characters.
+
+   Currently, the only supported algorithm is 'chacha20-poly1305',
+   a very secure, fast and versatile authenticated encryption method.
+
+   Options is a list of:
+
+    - encoding(+Encoding)
+    Encoding to use for PlainText. The default is utf8. The
+    alternative is octet, which is used if the data are raw bytes.
+
+    - tag(+Tag)
+    For authenticated encryption schemes, the tag must be specified as
+    a list of bytes exactly as they were generated upon encryption.
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+crypto_data_decrypt(CipherText0, Algorithm, Key, IV, PlainText, Options) :-
+        option(tag(Tag), Options, []),
+        must_be_bytes(Tag, crypto_data_decrypt/6),
+        must_be_bytes(Key, crypto_data_decrypt/6),
+        must_be_bytes(IV, crypto_data_decrypt/6),
+        must_be(atom, Algorithm),
+        option(encoding(Encoding), Options, utf8),
+        must_be(list, CipherText0),
+        encoding_bytes(octet, CipherText0, CipherText1),
+        append(CipherText1, Tag, CipherText),
+        (   Algorithm = 'chacha20-poly1305' -> true
+        ;   domain_error('chacha20-poly1305', Algorithm, crypto_data_decrypt/6)
+        ),
+        '$crypto_data_decrypt'(CipherText, Key, IV, Encoding, PlainText).
+
+encoding_bytes(octet, Bs0, Bs) :-
+        (   maplist(integer, Bs0) ->
+            Bs0 = Bs
+        ;   maplist(char_code, Bs0, Bs)
+        ),
+        must_be_bytes(Bs, crypto_encoding).
+encoding_bytes(utf8, Cs, Bs) :-
+        (   maplist(atom, Cs) ->
+            chars_bytes_(Cs, Bs, crypto_encoding)
+        ;   domain_error(encryption_encoding, Cs, crypto)
+        ).
+
+char_code(Char, Code) :- atom_codes(Char, [Code]).
+
 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Modular multiplicative inverse.
 
index a4ffd0aec734c13761ce43f02cb5e1f94b856ecd..17ab0bfc1f1dcb7c2c8658ee021e07ccbb3ed7f3 100644 (file)
@@ -275,8 +275,7 @@ cells(Fs0, Args, Tab, Es) -->
         cells(Fs, Args, Tab, [chars(Fs1)|Es]).
 
 n_newlines(0) --> !.
-n_newlines(1) --> !, [newline].
-n_newlines(N0) --> { N0 > 1, N is N0 - 1 }, [newline], n_newlines(N).
+n_newlines(N0) --> { N0 > 0, N is N0 - 1 }, [newline], n_newlines(N).
 
 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 ?- phrase(upto_what(Cs, ~), "abc~test", Rest).
@@ -545,7 +544,7 @@ listing(PI) :-
         ;   type_error(predicate_indicator, PI, listing/1)
         ),
         functor(Head, Name, Arity),
-        \+ \+ clause(Head, Body), % only true if there is at least one clause
+        \+ \+ clause(Head, _), % only true if there is at least one clause
         (   clause(Head, Body),
             (   Body == true ->
                 portray_clause(Head)
index 70ece805aa505036f918320b17d5b2af0ea51110..4ccfb7f0b82e4e89ecd56a1e372bf4f37a640768 100644 (file)
@@ -40,7 +40,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,hkdf,pbkdf2};
+use ring::{digest,hkdf,pbkdf2,aead,error};
 use ripemd160::{Ripemd160, Digest};
 
 pub fn get_key() -> KeyEvent {
@@ -5326,6 +5326,85 @@ impl MachineState {
 
                 self.unify(self[temp_v!(4)], ints_list);
             }
+            &SystemClauseType::CryptoDataEncrypt => {
+                let stub1 = MachineError::functor_stub(clause_name!("crypto_data_encrypt"), 6);
+                let data = self.integers_to_bytevec(temp_v!(1), stub1);
+                let stub2 = MachineError::functor_stub(clause_name!("crypto_data_encrypt"), 6);
+                let key = self.integers_to_bytevec(temp_v!(2), stub2);
+                let stub3 = MachineError::functor_stub(clause_name!("crypto_data_encrypt"), 6);
+                let iv = self.integers_to_bytevec(temp_v!(3), stub3);
+
+                let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap();
+                let nonce_sequence = OneNonceSequence::new(aead::Nonce::try_assume_unique_for_key(&iv).unwrap());
+                let mut key: aead::SealingKey<OneNonceSequence> = aead::BoundKey::new(unbound_key, nonce_sequence);
+
+                let mut in_out = data.clone();
+                let tag =
+                     match key.seal_in_place_separate_tag(aead::Aad::empty(), &mut in_out) {
+                        Ok(d) => { d }
+                        _     => { self.fail = true; return Ok(()); }
+                      };
+
+                let tag_list =
+                      Addr::HeapCell(self.heap.to_list(tag.as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b))))));
+
+                let complete_string = {
+                          let buffer = String::from_iter(in_out.iter().map(|b| *b as char));
+                          self.heap.put_complete_string(&buffer)
+                      };
+
+                self.unify(self[temp_v!(4)], tag_list);
+                self.unify(self[temp_v!(5)], complete_string);
+            }
+            &SystemClauseType::CryptoDataDecrypt => {
+                let stub1 = MachineError::functor_stub(clause_name!("crypto_data_decrypt"), 6);
+                let data = self.integers_to_bytevec(temp_v!(1), stub1);
+                let stub2 = MachineError::functor_stub(clause_name!("crypto_data_decrypt"), 6);
+                let key = self.integers_to_bytevec(temp_v!(2), stub2);
+                let stub3 = MachineError::functor_stub(clause_name!("crypto_data_decrypt"), 6);
+                let iv = self.integers_to_bytevec(temp_v!(3), stub3);
+
+                let encoding = 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 unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap();
+                let nonce_sequence = OneNonceSequence::new(aead::Nonce::try_assume_unique_for_key(&iv).unwrap());
+                let mut key: aead::OpeningKey<OneNonceSequence> = aead::BoundKey::new(unbound_key, nonce_sequence);
+
+                let mut in_out = data.clone();
+
+                let complete_string = {
+                          let decrypted_data =
+                                match key.open_in_place(aead::Aad::empty(), &mut in_out) {
+                                   Ok(d) => { d }
+                                   _     => { self.fail = true; return Ok(()); }
+                                 };
+
+                          let buffer = match encoding {
+                                  "octet" => { String::from_iter(decrypted_data.iter().map(|b| *b as char)) }
+                                  "utf8"  => { match String::from_utf8(decrypted_data.to_vec()) {
+                                                  Ok(str) => { str }
+                                                  _ => { self.fail = true; return Ok(()); }
+                                                  }
+                                               }
+                                  _ => { unreachable!() }
+                               };
+
+                          self.heap.put_complete_string(&buffer)
+                      };
+
+                self.unify(self[temp_v!(5)], complete_string);
+            }
         };
 
         return_from_clause!(self.last_call, self)
@@ -5350,3 +5429,17 @@ impl hkdf::KeyType for MyKey<usize> {
         self.0
     }
 }
+
+struct OneNonceSequence(Option<aead::Nonce>);
+
+impl OneNonceSequence {
+    fn new(nonce: aead::Nonce) -> Self {
+        Self(Some(nonce))
+    }
+}
+
+impl aead::NonceSequence for OneNonceSequence {
+    fn advance(&mut self) -> Result<aead::Nonce, error::Unspecified> {
+        self.0.take().ok_or(error::Unspecified)
+    }
+}