From ad3be5c848dec53814d13a646c636bcf50976460 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 18:30:34 +0200 Subject: [PATCH 01/11] ADDED: Public key signatures and signature verification with Ed25519 --- README.md | 1 + src/prolog/clause_types.rs | 8 +++++- src/prolog/lib/crypto.pl | 42 +++++++++++++++++++++++++++++- src/prolog/machine/system_calls.rs | 34 +++++++++++++++++++++++- 4 files changed, 82 insertions(+), 3 deletions(-) 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..067e4437 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -291,7 +291,9 @@ pub enum SystemClauseType { CryptoDataHKDF, CryptoPasswordHash, CryptoDataEncrypt, - CryptoDataDecrypt + CryptoDataDecrypt, + Ed25519Sign, + Ed25519Verify } impl SystemClauseType { @@ -480,6 +482,8 @@ 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"), } } @@ -648,6 +652,8 @@ 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), _ => None, } } diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index e07b4b1d..1562b4c1 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -9,7 +9,7 @@ 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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ :- module(crypto, @@ -21,6 +21,8 @@ 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_sign/4, % +PrivateKey, +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 @@ -624,6 +626,44 @@ encoding_bytes(utf8, Cs, Bs) :- ; domain_error(encryption_encoding, Cs, crypto) ). +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Digital signatures with Ed25519 + =============================== + + ed25519_sign(+Key, +Data, -Signature, +Options) + + Key and Data must be lists of characters. Key is a private key in + PKCS#8 (v1 or v2) DER format. 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 in + PKCS#8 DER format. 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 both predicates is: + + - encoding(+Encoding) + The default encoding of Data is utf8. The alternative is octet, + which treats Data as a list of raw bytes. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +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..4ebbcf29 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,error,signature}; use ripemd160::{Ripemd160, Digest}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; use blake2::{Blake2s, Blake2b}; @@ -5448,6 +5448,38 @@ impl MachineState { self.unify(self[temp_v!(5)], 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_maybe_unchecked(&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) From 2d036c6b2511417f3c807fc8f75c39edfed55fae Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 20:10:26 +0200 Subject: [PATCH 02/11] "codes" --> "bytes" Support for lists of bytes may be dropped from library(crypto). Use lists of characters to make your code future-proof. Lists of characters will always be supported due to their compactness, and because Prolog applications should move towards characters. --- src/prolog/lib/crypto.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 1562b4c1..804c116f 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -478,7 +478,7 @@ 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 + PlainText must be a list of bytes 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 From 56b04f8df8194ac7c9aef06062367e0504f64ee7 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 20:13:07 +0200 Subject: [PATCH 03/11] use LessSafeKey to simplify the implementation of authenticated encryption The nonce is explicitly specified, and the application programmer must (and always had to) ensure that it is unique for a given key. --- src/prolog/machine/system_calls.rs | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 4ebbcf29..71a86627 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,signature}; +use ring::{digest,hkdf,pbkdf2,aead,signature}; 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(()); } }; @@ -5504,17 +5504,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) - } -} From 0ef7b5488d434936e969f7713c459b19da242f4e Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 23:06:09 +0200 Subject: [PATCH 04/11] ADDED: ed25519_new_keypair/1 to dynamically create a new Ed25519 key pair --- src/prolog/clause_types.rs | 5 ++++- src/prolog/lib/crypto.pl | 34 +++++++++++++++++++----------- src/prolog/machine/system_calls.rs | 9 ++++++++ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index 067e4437..536c9061 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -293,7 +293,8 @@ pub enum SystemClauseType { CryptoDataEncrypt, CryptoDataDecrypt, Ed25519Sign, - Ed25519Verify + Ed25519Verify, + Ed25519NewKeyPair } impl SystemClauseType { @@ -484,6 +485,7 @@ impl SystemClauseType { &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") } } @@ -654,6 +656,7 @@ impl SystemClauseType { ("$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), _ => None, } } diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 804c116f..5807292e 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -21,6 +21,7 @@ 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_sign/4, % +PrivateKey, +Data, -Signature, +Options ed25519_verify/4, % +PublicKey, +Data, -Signature, +Options crypto_name_curve/2, % +Name, -Curve @@ -630,28 +631,37 @@ encoding_bytes(utf8, Cs, Bs) :- Digital signatures with Ed25519 =============================== - ed25519_sign(+Key, +Data, -Signature, +Options) + - 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. - Key and Data must be lists of characters. Key is a private key in - PKCS#8 (v1 or v2) DER format. Sign Data with Key, yielding - Signature as a list of hexadecimal characters. + - 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. + - ed25519_sign(+Key, +Data, -Signature, +Options) + Key and Data must be lists of characters. Key is a private key or + key pair in PKCS#8 (v1 or v2) DER format. Sign Data with Key, + yielding Signature as a list of hexadecimal characters. - ed25519_verify(+Key, +Data, +Signature, +Options) + - 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. - Key and Data must be lists of characters. Key is a public key in - PKCS#8 DER format. 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 both predicates is: + 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_sign(Key0, Data0, Signature, Options) :- options_data_bytes(Options, Data0, Data), encoding_bytes(octet, Key0, Key), diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 71a86627..38f044b0 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -5448,6 +5448,15 @@ 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::Ed25519Sign => { let stub1 = MachineError::functor_stub(clause_name!("ed25519_sign"), 4); let key = self.integers_to_bytevec(temp_v!(1), stub1); From e254d84710b5ccf64310145d063f197923c9dced Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 23:17:42 +0200 Subject: [PATCH 05/11] clarify format of public key --- src/prolog/lib/crypto.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 5807292e..0723fb29 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -640,6 +640,7 @@ encoding_bytes(utf8, Cs, Bs) :- - 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 private key or From 2845f55157b93f2d45e64a9bcbd23048ce8b6c9e Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 23:32:46 +0200 Subject: [PATCH 06/11] ADDED: ed25519_keypair_public_key/2, relating a key pair to its public key --- src/prolog/clause_types.rs | 7 ++++-- src/prolog/lib/crypto.pl | 35 +++++++++++++++++------------- src/prolog/machine/system_calls.rs | 18 ++++++++++++++- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index 536c9061..b783ac0c 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -294,7 +294,8 @@ pub enum SystemClauseType { CryptoDataDecrypt, Ed25519Sign, Ed25519Verify, - Ed25519NewKeyPair + Ed25519NewKeyPair, + Ed25519KeyPairPublicKey } impl SystemClauseType { @@ -485,7 +486,8 @@ impl SystemClauseType { &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::Ed25519NewKeyPair => clause_name!("$ed25519_new_keypair"), + &SystemClauseType::Ed25519KeyPairPublicKey => clause_name!("$ed25519_keypair_public_key") } } @@ -657,6 +659,7 @@ impl SystemClauseType { ("$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 0723fb29..696bb0e6 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -13,21 +13,22 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ :- 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 - ed25519_new_keypair/1, % -KeyPair - ed25519_sign/4, % +PrivateKey, +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 + [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, % +PrivateKey, +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)). @@ -663,6 +664,10 @@ encoding_bytes(utf8, Cs, Bs) :- 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), diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 38f044b0..895efad8 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,signature}; +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}; @@ -5457,6 +5457,22 @@ impl MachineState { 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_maybe_unchecked(&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); From b43f27030eaa33e1a92a163086876003240c7838 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 23:51:31 +0200 Subject: [PATCH 07/11] require PKCS#8 v2 format for better security Notably, this format requires that the public key also be present. This format is what ed25519_new_keypair/1 generates, and it is strongly encouraged for higher security. --- src/prolog/lib/crypto.pl | 6 +++--- src/prolog/machine/system_calls.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 696bb0e6..9fb5e216 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -644,9 +644,9 @@ encoding_bytes(utf8, Cs, Bs) :- 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 private key or - key pair in PKCS#8 (v1 or v2) DER format. Sign Data with Key, - yielding Signature as a list of hexadecimal characters. + 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. diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 895efad8..1fe7b576 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -5461,7 +5461,7 @@ impl MachineState { 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_maybe_unchecked(&bytes) { + let key_pair = match signature::Ed25519KeyPair::from_pkcs8(&bytes) { Ok(kp) => { kp } _ => { self.fail = true; return Ok(()); } }; @@ -5479,7 +5479,7 @@ impl MachineState { 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_maybe_unchecked(&key) { + let key_pair = match signature::Ed25519KeyPair::from_pkcs8(&key) { Ok(kp) => { kp } _ => { self.fail = true; return Ok(()); } }; From 5d94276c4f02b4d6fa0bdc859982107cd1560397 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Thu, 21 May 2020 00:07:28 +0200 Subject: [PATCH 08/11] remove several mentions of list of bytes in predicate descriptions This is to focus more on the intended core representation of text: In Prolog, text is ideally represented as a list of characters, and this is what we want to encourage, especially given Scryer's compact representation for strings. For the time being, library(crypto) still also supports lists of bytes in several predicates. This will likely be removed at some point in the future. Please use lists of characters to make your code future-proof. --- src/prolog/lib/crypto.pl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 9fb5e216..d4ebf2e5 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -155,9 +155,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: @@ -231,7 +230,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 @@ -245,7 +244,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. @@ -480,8 +479,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 bytes 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) @@ -579,9 +578,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. From 72bf45912e13953c885516bb389f8470ff9e7e46 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Thu, 21 May 2020 00:14:15 +0200 Subject: [PATCH 09/11] "PrivateKey" --> "KeyPair" --- src/prolog/lib/crypto.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index d4ebf2e5..8c255a4c 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -23,7 +23,7 @@ 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, % +PrivateKey, +Data, -Signature, +Options + 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 From 93da831ef0f0eaac935f75a8c3d0a24bf20d5d34 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Thu, 21 May 2020 01:27:06 +0200 Subject: [PATCH 10/11] correct mode indication for ed25519_verify/4 Noted by @notoria in #545. Many thanks! --- src/prolog/lib/crypto.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 8c255a4c..67fef071 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -24,7 +24,7 @@ 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 + 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 From 1108c99688b926a00716fbe960fd416b3ed193a4 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Thu, 21 May 2020 13:41:57 +0200 Subject: [PATCH 11/11] document that lists of integers can be specified if encoding(octet) is used This seems to be a good compromise: The API now strongly encourages lists of characters, which are ideally suited to represent text, and also allows lists of bytes if the encoding(octet) option is used. --- src/prolog/lib/crypto.pl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 67fef071..cd7f93c5 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -10,6 +10,10 @@ Especially for cryptographic applications, it as an advantage that 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,