Merge pull request #545 from triska/ed25519

ADDED: Public key signatures and signature verification with Ed25519
This commit is contained in:
Mark Thom
2020-05-21 11:30:47 -03:00
committed by GitHub
4 changed files with 160 additions and 45 deletions

View File

@@ -380,6 +380,7 @@ The modules that ship with Scryer Prolog are also called
* [`crypto`](src/prolog/lib/crypto.pl) * [`crypto`](src/prolog/lib/crypto.pl)
Cryptographically secure random numbers and hashes, HMAC-based Cryptographically secure random numbers and hashes, HMAC-based
key derivation (HKDF), password-based key derivation (PBKDF2), key derivation (HKDF), password-based key derivation (PBKDF2),
public key signatures and signature verification with Ed25519,
authenticated encryption, and reasoning about elliptic curves. authenticated encryption, and reasoning about elliptic curves.
To read contents of external files, use `phrase_from_file/2` from To read contents of external files, use `phrase_from_file/2` from

View File

@@ -291,7 +291,11 @@ pub enum SystemClauseType {
CryptoDataHKDF, CryptoDataHKDF,
CryptoPasswordHash, CryptoPasswordHash,
CryptoDataEncrypt, CryptoDataEncrypt,
CryptoDataDecrypt CryptoDataDecrypt,
Ed25519Sign,
Ed25519Verify,
Ed25519NewKeyPair,
Ed25519KeyPairPublicKey
} }
impl SystemClauseType { impl SystemClauseType {
@@ -480,6 +484,10 @@ impl SystemClauseType {
&SystemClauseType::CryptoPasswordHash => clause_name!("$crypto_password_hash"), &SystemClauseType::CryptoPasswordHash => clause_name!("$crypto_password_hash"),
&SystemClauseType::CryptoDataEncrypt => clause_name!("$crypto_data_encrypt"), &SystemClauseType::CryptoDataEncrypt => clause_name!("$crypto_data_encrypt"),
&SystemClauseType::CryptoDataDecrypt => clause_name!("$crypto_data_decrypt"), &SystemClauseType::CryptoDataDecrypt => clause_name!("$crypto_data_decrypt"),
&SystemClauseType::Ed25519Sign => clause_name!("$ed25519_sign"),
&SystemClauseType::Ed25519Verify => clause_name!("$ed25519_verify"),
&SystemClauseType::Ed25519NewKeyPair => clause_name!("$ed25519_new_keypair"),
&SystemClauseType::Ed25519KeyPairPublicKey => clause_name!("$ed25519_keypair_public_key")
} }
} }
@@ -648,6 +656,10 @@ impl SystemClauseType {
("$crypto_password_hash", 4) => Some(SystemClauseType::CryptoPasswordHash), ("$crypto_password_hash", 4) => Some(SystemClauseType::CryptoPasswordHash),
("$crypto_data_encrypt", 5) => Some(SystemClauseType::CryptoDataEncrypt), ("$crypto_data_encrypt", 5) => Some(SystemClauseType::CryptoDataEncrypt),
("$crypto_data_decrypt", 5) => Some(SystemClauseType::CryptoDataDecrypt), ("$crypto_data_decrypt", 5) => Some(SystemClauseType::CryptoDataDecrypt),
("$ed25519_sign", 3) => Some(SystemClauseType::Ed25519Sign),
("$ed25519_verify", 3) => Some(SystemClauseType::Ed25519Verify),
("$ed25519_new_keypair", 1) => Some(SystemClauseType::Ed25519NewKeyPair),
("$ed25519_keypair_public_key", 2) => Some(SystemClauseType::Ed25519KeyPairPublicKey),
_ => None, _ => None,
} }
} }

View File

@@ -9,22 +9,30 @@
and strings have the advantage that the atom table remains unmodified. and strings have the advantage that the atom table remains unmodified.
Especially for cryptographic applications, it as an advantage that Especially for cryptographic applications, it as an advantage that
using strings leaves little trace of what was processed in the system, using strings leaves little trace of what was processed in the system.
For predicates that accept an encoding/1 option to specify the encoding
of the input data, if encoding(octet) is used, then the input can also
be specified as a list of bytes, i.e., integers between 0 and 255.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
:- module(crypto, :- module(crypto,
[hex_bytes/2, % ?Hex, ?Bytes [hex_bytes/2, % ?Hex, ?Bytes
crypto_n_random_bytes/2, % +N, -Bytes crypto_n_random_bytes/2, % +N, -Bytes
crypto_data_hash/3, % +Data, -Hash, +Options crypto_data_hash/3, % +Data, -Hash, +Options
crypto_data_hkdf/4, % +Data, +Length, -Bytes, +Options crypto_data_hkdf/4, % +Data, +Length, -Bytes, +Options
crypto_password_hash/2, % +Password, ?Hash crypto_password_hash/2, % +Password, ?Hash
crypto_password_hash/3, % +Password, -Hash, +Options crypto_password_hash/3, % +Password, -Hash, +Options
crypto_data_encrypt/6, % +PlainText, +Algorithm, +Key, +IV, -CipherText, +Options crypto_data_encrypt/6, % +PlainText, +Algorithm, +Key, +IV, -CipherText, +Options
crypto_data_decrypt/6, % +CipherText, +Algorithm, +Key, +IV, -PlainText, +Options crypto_data_decrypt/6, % +CipherText, +Algorithm, +Key, +IV, -PlainText, +Options
crypto_name_curve/2, % +Name, -Curve ed25519_new_keypair/1, % -KeyPair
crypto_curve_order/2, % +Curve, -Order ed25519_keypair_public_key/2, % +KeyPair, +PublicKey
crypto_curve_generator/2, % +Curve, -Generator ed25519_sign/4, % +KeyPair, +Data, -Signature, +Options
crypto_curve_scalar_mult/4 % +Curve, +Scalar, +Point, -Result ed25519_verify/4, % +PublicKey, +Data, +Signature, +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(error)).
@@ -151,9 +159,8 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B).
crypto_data_hash(+Data, -Hash, +Options) crypto_data_hash(+Data, -Hash, +Options)
Where Data is a list of bytes (integers between 0 and 255) or Where Data is a list of characters, and Hash is the computed hash
characters, and Hash is the computed hash as a list of hexadecimal as a list of hexadecimal characters.
characters.
Options is a list of: Options is a list of:
@@ -227,7 +234,7 @@ hash_algorithm(blake2b512).
crypto_data_hkdf(+Data, +Length, -Bytes, +Options) is det. crypto_data_hkdf(+Data, +Length, -Bytes, +Options) is det.
Concentrate possibly dispersed entropy of Data and then expand it Concentrate possibly dispersed entropy of Data and then expand it
to the desired length. Data is a list of bytes or characters. to the desired length. Data is a list of characters.
Bytes is unified with a list of bytes of length Length, and is Bytes is unified with a list of bytes of length Length, and is
suitable as input keying material and initialization vectors to suitable as input keying material and initialization vectors to
@@ -241,7 +248,7 @@ hash_algorithm(blake2b512).
cryptographically secure algorithm by default. cryptographically secure algorithm by default.
- info(+Info) - info(+Info)
Optional context and application specific information, Optional context and application specific information,
specified as a list of bytes or characters. The default is []. specified as a list of characters. The default is [].
- salt(+List) - salt(+List)
Optionally, a list of bytes that are used as salt. The Optionally, a list of bytes that are used as salt. The
default is all zeroes. default is all zeroes.
@@ -476,8 +483,8 @@ bytes_base64_([A,B,C|Ls]) --> [W,X,Y,Z],
Algorithm, key Key, and initialization vector (or nonce) IV, to Algorithm, key Key, and initialization vector (or nonce) IV, to
give CipherText. give CipherText.
PlainText must be a list of codes or characters, Key and IV must be PlainText must be a list of characters, Key and IV must be lists of
lists of bytes, and CipherText is created as a list of characters. bytes, and CipherText is created as a list of characters.
Keys and IVs can be chosen at random (using for example Keys and IVs can be chosen at random (using for example
crypto_n_random_bytes/2) or derived from input keying material (IKM) crypto_n_random_bytes/2) or derived from input keying material (IKM)
@@ -575,9 +582,9 @@ crypto_data_encrypt(PlainText0, Algorithm, Key, IV, CipherText, Options) :-
Decrypt the given CipherText, using the symmetric algorithm Decrypt the given CipherText, using the symmetric algorithm
Algorithm, key Key, and initialization vector IV, to give Algorithm, key Key, and initialization vector IV, to give
PlainText. CipherText must be a list of bytes or characters, and PlainText. CipherText must be a list of characters, and Key and IV
Key and IV must be lists of bytes. PlainText is created as a list must be lists of bytes. PlainText is created as a list of
of characters. characters.
Currently, the only supported algorithm is 'chacha20-poly1305', Currently, the only supported algorithm is 'chacha20-poly1305',
a very secure, fast and versatile authenticated encryption method. a very secure, fast and versatile authenticated encryption method.
@@ -624,6 +631,58 @@ encoding_bytes(utf8, Cs, Bs) :-
; domain_error(encryption_encoding, Cs, crypto) ; domain_error(encryption_encoding, Cs, crypto)
). ).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Digital signatures with Ed25519
===============================
- ed25519_new_keypair(-Pair)
Yields a new Ed25519 key pair Pair, a list of characters. The
pair contains the private key and must be kept absolutely secret.
Pair can be used for signing. Its public key can be obtained
with ed25519_keypair_public_key/2.
- ed25519_keypair_public_key(+Pair, -PublicKey)
PublicKey is the public key of the given key pair. The public key
can be used for signature verification, and can be shared freely.
The public key is represented as a list of characters.
- ed25519_sign(+Key, +Data, -Signature, +Options)
Key and Data must be lists of characters. Key is a key pair in
PKCS#8 v2 format as generated by ed25519_new_keypair/1. Sign Data
with Key, yielding Signature as a list of hexadecimal characters.
- ed25519_verify(+Key, +Data, +Signature, +Options)
Key and Data must be lists of characters. Key is a public key.
Succeeds if Data was signed with the private key corresponding to
Key, where Signature is a list of hexadecimal characters as
generated by ed25519_sign/4. Fails otherwise.
Currently, the only option for signing and verifying is:
- encoding(+Encoding)
The default encoding of Data is utf8. The alternative is octet,
which treats Data as a list of raw bytes.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
ed25519_new_keypair(Pair) :-
'$ed25519_new_keypair'(Pair).
ed25519_keypair_public_key(Pair0, PublicKey) :-
encoding_bytes(octet, Pair0, Pair),
'$ed25519_keypair_public_key'(Pair, PublicKey).
ed25519_sign(Key0, Data0, Signature, Options) :-
options_data_bytes(Options, Data0, Data),
encoding_bytes(octet, Key0, Key),
'$ed25519_sign'(Key, Data, Signature0),
hex_bytes(Signature, Signature0).
ed25519_verify(Key0, Data0, Signature0, Options) :-
options_data_bytes(Options, Data0, Data),
encoding_bytes(octet, Key0, Key),
hex_bytes(Signature0, Signature),
'$ed25519_verify'(Key, Data, Signature).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Modular multiplicative inverse. Modular multiplicative inverse.

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 crate::crossterm::terminal::{enable_raw_mode, disable_raw_mode};
use ring::rand::{SecureRandom, SystemRandom}; use ring::rand::{SecureRandom, SystemRandom};
use ring::{digest,hkdf,pbkdf2,aead,error}; use ring::{digest,hkdf,pbkdf2,aead,signature::{self,KeyPair}};
use ripemd160::{Ripemd160, Digest}; use ripemd160::{Ripemd160, Digest};
use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512};
use blake2::{Blake2s, Blake2b}; use blake2::{Blake2s, Blake2b};
@@ -5378,12 +5378,12 @@ impl MachineState {
let iv = self.integers_to_bytevec(temp_v!(3), stub3); let iv = self.integers_to_bytevec(temp_v!(3), stub3);
let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap(); 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 nonce = aead::Nonce::try_assume_unique_for_key(&iv).unwrap();
let mut key: aead::SealingKey<OneNonceSequence> = aead::BoundKey::new(unbound_key, nonce_sequence); let key = aead::LessSafeKey::new(unbound_key);
let mut in_out = data.clone(); let mut in_out = data.clone();
let tag = let tag =
match key.seal_in_place_separate_tag(aead::Aad::empty(), &mut in_out) { match key.seal_in_place_separate_tag(nonce, aead::Aad::empty(), &mut in_out) {
Ok(d) => { d } Ok(d) => { d }
_ => { self.fail = true; return Ok(()); } _ => { self.fail = true; return Ok(()); }
}; };
@@ -5421,14 +5421,14 @@ impl MachineState {
}; };
let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap(); 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 nonce = aead::Nonce::try_assume_unique_for_key(&iv).unwrap();
let mut key: aead::OpeningKey<OneNonceSequence> = aead::BoundKey::new(unbound_key, nonce_sequence); let key = aead::LessSafeKey::new(unbound_key);
let mut in_out = data.clone(); let mut in_out = data.clone();
let complete_string = { let complete_string = {
let decrypted_data = let decrypted_data =
match key.open_in_place(aead::Aad::empty(), &mut in_out) { match key.open_in_place(nonce, aead::Aad::empty(), &mut in_out) {
Ok(d) => { d } Ok(d) => { d }
_ => { self.fail = true; return Ok(()); } _ => { self.fail = true; return Ok(()); }
}; };
@@ -5448,6 +5448,63 @@ impl MachineState {
self.unify(self[temp_v!(5)], complete_string); self.unify(self[temp_v!(5)], complete_string);
} }
&SystemClauseType::Ed25519NewKeyPair => {
let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(rng()).unwrap();
let complete_string = {
let buffer = String::from_iter(pkcs8_bytes.as_ref().iter().map(|b| *b as char));
self.heap.put_complete_string(&buffer)
};
self.unify(self[temp_v!(1)], complete_string);
}
&SystemClauseType::Ed25519KeyPairPublicKey => {
let stub1 = MachineError::functor_stub(clause_name!("ed25519_keypair_public_key"), 2);
let bytes = self.integers_to_bytevec(temp_v!(1), stub1);
let key_pair = match signature::Ed25519KeyPair::from_pkcs8(&bytes) {
Ok(kp) => { kp }
_ => { self.fail = true; return Ok(()); }
};
let complete_string = {
let buffer = String::from_iter(key_pair.public_key().as_ref().iter().map(|b| *b as char));
self.heap.put_complete_string(&buffer)
};
self.unify(self[temp_v!(2)], complete_string);
}
&SystemClauseType::Ed25519Sign => {
let stub1 = MachineError::functor_stub(clause_name!("ed25519_sign"), 4);
let key = self.integers_to_bytevec(temp_v!(1), stub1);
let stub2 = MachineError::functor_stub(clause_name!("ed25519_sign"), 4);
let data = self.integers_to_bytevec(temp_v!(2), stub2);
let key_pair = match signature::Ed25519KeyPair::from_pkcs8(&key) {
Ok(kp) => { kp }
_ => { self.fail = true; return Ok(()); }
};
let sig = key_pair.sign(&data);
let sig_list =
Addr::HeapCell(self.heap.to_list(sig.as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize)))));
self.unify(self[temp_v!(3)], sig_list);
}
&SystemClauseType::Ed25519Verify => {
let stub1 = MachineError::functor_stub(clause_name!("ed25519_verify"), 4);
let key = self.integers_to_bytevec(temp_v!(1), stub1);
let stub2 = MachineError::functor_stub(clause_name!("ed25519_verify"), 4);
let data = self.integers_to_bytevec(temp_v!(2), stub2);
let stub3 = MachineError::functor_stub(clause_name!("ed25519_verify"), 4);
let signature = self.integers_to_bytevec(temp_v!(3), stub3);
let peer_public_key = signature::UnparsedPublicKey::new(&signature::ED25519, &key);
match peer_public_key.verify(&data, &signature) {
Ok(_) => { }
_ => { self.fail = true; return Ok(()); }
}
}
}; };
return_from_clause!(self.last_call, self) return_from_clause!(self.last_call, self)
@@ -5472,17 +5529,3 @@ impl hkdf::KeyType for MyKey<usize> {
self.0 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)
}
}