diff --git a/README.md b/README.md index ed9161f5..9ad29239 100644 --- a/README.md +++ b/README.md @@ -380,6 +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), + public key signatures and signature verification with Ed25519, authenticated encryption, and reasoning about elliptic curves. To read contents of external files, use `phrase_from_file/2` from diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index 78ca09de..b783ac0c 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -291,7 +291,11 @@ pub enum SystemClauseType { CryptoDataHKDF, CryptoPasswordHash, CryptoDataEncrypt, - CryptoDataDecrypt + CryptoDataDecrypt, + Ed25519Sign, + Ed25519Verify, + Ed25519NewKeyPair, + Ed25519KeyPairPublicKey } impl SystemClauseType { @@ -480,6 +484,10 @@ impl SystemClauseType { &SystemClauseType::CryptoPasswordHash => clause_name!("$crypto_password_hash"), &SystemClauseType::CryptoDataEncrypt => clause_name!("$crypto_data_encrypt"), &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_data_encrypt", 5) => Some(SystemClauseType::CryptoDataEncrypt), ("$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, } } diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index e07b4b1d..cd7f93c5 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -9,22 +9,30 @@ and strings have the advantage that the atom table remains unmodified. 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, - [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 + [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 + ed25519_new_keypair/1, % -KeyPair + ed25519_keypair_public_key/2, % +KeyPair, +PublicKey + ed25519_sign/4, % +KeyPair, +Data, -Signature, +Options + 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)). @@ -151,9 +159,8 @@ 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) or - characters, and Hash is the computed hash as a list of hexadecimal - characters. + Where Data is a list of characters, and Hash is the computed hash + as a list of hexadecimal characters. Options is a list of: @@ -227,7 +234,7 @@ hash_algorithm(blake2b512). 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. + to the desired length. Data is a list of characters. Bytes is unified with a list of bytes of length Length, and is suitable as input keying material and initialization vectors to @@ -241,7 +248,7 @@ hash_algorithm(blake2b512). cryptographically secure algorithm by default. - info(+Info) 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) Optionally, a list of bytes that are used as salt. The 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 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. + PlainText must be a list of 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) @@ -575,9 +582,9 @@ crypto_data_encrypt(PlainText0, Algorithm, Key, IV, CipherText, 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. + PlainText. CipherText must be a list of 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. @@ -624,6 +631,58 @@ encoding_bytes(utf8, Cs, Bs) :- ; 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. diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 4eacea2a..1fe7b576 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -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,aead,error}; +use ring::{digest,hkdf,pbkdf2,aead,signature::{self,KeyPair}}; use ripemd160::{Ripemd160, Digest}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; use blake2::{Blake2s, Blake2b}; @@ -5378,12 +5378,12 @@ impl MachineState { 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 = aead::BoundKey::new(unbound_key, nonce_sequence); + let nonce = aead::Nonce::try_assume_unique_for_key(&iv).unwrap(); + let key = aead::LessSafeKey::new(unbound_key); let mut in_out = data.clone(); 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 } _ => { self.fail = true; return Ok(()); } }; @@ -5421,14 +5421,14 @@ impl MachineState { }; 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 = aead::BoundKey::new(unbound_key, nonce_sequence); + let nonce = aead::Nonce::try_assume_unique_for_key(&iv).unwrap(); + let key = aead::LessSafeKey::new(unbound_key); let mut in_out = data.clone(); let complete_string = { 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 } _ => { self.fail = true; return Ok(()); } }; @@ -5448,6 +5448,63 @@ impl MachineState { 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) @@ -5472,17 +5529,3 @@ impl hkdf::KeyType for MyKey { self.0 } } - -struct OneNonceSequence(Option); - -impl OneNonceSequence { - fn new(nonce: aead::Nonce) -> Self { - Self(Some(nonce)) - } -} - -impl aead::NonceSequence for OneNonceSequence { - fn advance(&mut self) -> Result { - self.0.take().ok_or(error::Unspecified) - } -}