This commit is contained in:
Mark Thom
2020-05-16 14:51:19 -06:00
6 changed files with 324 additions and 41 deletions

View File

@@ -1,14 +1,31 @@
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
fast_finish: true
cache: cargo
os: linux
dist: xenial
script:
- cargo build --verbose --all
- cargo test --verbose --all
- cargo test --verbose --all --no-default-features --features num
before_script:
- cargo fetch
jobs:
allow_failures:
env:
- CAN_FAIL=true
include:
- stage: "Stable: Build"
rust: stable
script: cargo rustc --verbose -- -D warnings
name: "Build Stable"
- stage: "Stable: Tests"
rust: stable
script: cargo test --verbose --all
name: "Tests Stable"
- stage: "Features"
rust: stable
script: cargo test --verbose --all --no-default-features --features num
name: "num Tests"
env: CAN_FAIL=true
- stage: "Beta: Build"
# - #
rust: beta
script: cargo rustc --verbose -- -D warnings
name: "Build Beta"

View File

@@ -380,7 +380,7 @@ The modules that ship with Scryer 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 DCG to

View 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,
}
}

View File

@@ -12,17 +12,20 @@
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.

View 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)

View 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)
}
}