diff --git a/README.md b/README.md index 6c431ac6..df526bf8 100644 --- a/README.md +++ b/README.md @@ -445,10 +445,11 @@ The modules that ship with Scryer Prolog are also called * [`os`](src/lib/os.pl) Predicates for reasoning about environment variables. * [`crypto`](src/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. + 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 symmetric encryption with ChaCha20-Poly1305, and + reasoning about elliptic curves. To use predicates provided by the `lists` library, write: diff --git a/src/clause_types.rs b/src/clause_types.rs index 64e3c2dd..09cab2bb 100644 --- a/src/clause_types.rs +++ b/src/clause_types.rs @@ -688,16 +688,16 @@ impl SystemClauseType { ("$write_term_to_chars", 7) => Some(SystemClauseType::WriteTermToChars), ("$scryer_prolog_version", 1) => Some(SystemClauseType::ScryerPrologVersion), ("$crypto_random_byte", 1) => Some(SystemClauseType::CryptoRandomByte), - ("$crypto_data_hash", 3) => Some(SystemClauseType::CryptoDataHash), - ("$crypto_data_hkdf", 6) => Some(SystemClauseType::CryptoDataHKDF), + ("$crypto_data_hash", 4) => Some(SystemClauseType::CryptoDataHash), + ("$crypto_data_hkdf", 7) => Some(SystemClauseType::CryptoDataHKDF), ("$crypto_password_hash", 4) => Some(SystemClauseType::CryptoPasswordHash), - ("$crypto_data_encrypt", 5) => Some(SystemClauseType::CryptoDataEncrypt), - ("$crypto_data_decrypt", 5) => Some(SystemClauseType::CryptoDataDecrypt), + ("$crypto_data_encrypt", 6) => Some(SystemClauseType::CryptoDataEncrypt), + ("$crypto_data_decrypt", 6) => Some(SystemClauseType::CryptoDataDecrypt), ("$crypto_curve_scalar_mult", 5) => Some(SystemClauseType::CryptoCurveScalarMult), - ("$ed25519_sign", 3) => Some(SystemClauseType::Ed25519Sign), - ("$ed25519_verify", 3) => Some(SystemClauseType::Ed25519Verify), + ("$ed25519_sign", 5) => Some(SystemClauseType::Ed25519Sign), + ("$ed25519_verify", 5) => Some(SystemClauseType::Ed25519Verify), ("$ed25519_new_keypair", 1) => Some(SystemClauseType::Ed25519NewKeyPair), - ("$ed25519_keypair_public_key", 2) => Some(SystemClauseType::Ed25519KeyPairPublicKey), + ("$ed25519_keypair_public_key", 3) => Some(SystemClauseType::Ed25519KeyPairPublicKey), ("$load_html", 3) => Some(SystemClauseType::LoadHTML), ("$load_xml", 3) => Some(SystemClauseType::LoadXML), ("$getenv", 2) => Some(SystemClauseType::GetEnv), diff --git a/src/lib/crypto.pl b/src/lib/crypto.pl index 91ebc6ef..b0ff40e6 100644 --- a/src/lib/crypto.pl +++ b/src/lib/crypto.pl @@ -76,13 +76,13 @@ hex_bytes([]) --> []. hex_bytes([H1,H2|Hs]) --> [Byte], { char_hexval(H1, High), char_hexval(H2, Low), - Byte is High*16 + Low }, + Byte #= High*16 + Low }, hex_bytes(Hs). bytes_hex([]) --> []. bytes_hex([B|Bs]) --> [C0,C1], - { High is B>>4, - Low is B /\ 0xf, + { High #= B>>4, + Low #= B /\ 0xf, char_hexval(C0, High), char_hexval(C1, Low) }, @@ -101,6 +101,16 @@ must_be_bytes(Bytes, Context) :- ). +must_be_byte_chars(Chars, Context) :- + must_be(list, Chars), + ( member(Char, Chars), + char_code(Char, Code), + \+ between(0, 255, Code) -> + domain_error(byte_char, Char, Context) + ; true + ). + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cryptographically secure random numbers ======================================= @@ -191,18 +201,18 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B). crypto_data_hash(Data0, Hash, Options0) :- must_be(list, Options0), - options_data_bytes(Options0, Data0, Data), + options_data_chars(Options0, Data0, Data, Encoding), functor_hash_options(algorithm, A, Options0, _), ( hash_algorithm(A) -> true ; domain_error(hash_algorithm, A, crypto_data_hash/3) ), - '$crypto_data_hash'(Data, HashBytes, A), + '$crypto_data_hash'(Data, Encoding, HashBytes, A), hex_bytes(Hash, HashBytes). -options_data_bytes(Options, Data, Bytes) :- +options_data_chars(Options, Data, Chars, Encoding) :- option(encoding(Encoding), Options, utf8), must_be(atom, Encoding), - encoding_bytes(Encoding, Data, Bytes). + encoding_chars(Encoding, Data, Chars). default_hash(sha256). @@ -270,12 +280,12 @@ crypto_data_hkdf(Data0, L, Bytes, Options0) :- ), must_be(integer, L), L >= 0, - options_data_bytes(Options, Data0, Data), + options_data_chars(Options, Data0, Data, Encoding), option(salt(SaltBytes), Options, []), must_be_bytes(SaltBytes, crypto_data_hkdf/4), option(info(Info0), Options, []), chars_bytes_(Info0, Info, crypto_data_hkdf/4), - '$crypto_data_hkdf'(Data, SaltBytes, Info, Algorithm, L, Bytes). + '$crypto_data_hkdf'(Data, Encoding, SaltBytes, Info, Algorithm, L, Bytes). hkdf_algorithm(sha256). hkdf_algorithm(sha384). @@ -558,7 +568,7 @@ bytes_base64_([A,B,C|Ls]) --> [W,X,Y,Z], - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ crypto_data_encrypt(PlainText0, Algorithm, Key, IV, CipherText, Options) :- - options_data_bytes(Options, PlainText0, PlainText), + options_data_chars(Options, PlainText0, PlainText, Encoding), option(tag(Tag), Options, _), ( nonvar(Tag) -> must_be_bytes(Tag, crypto_data_encrypt/6) @@ -570,7 +580,7 @@ crypto_data_encrypt(PlainText0, Algorithm, Key, IV, CipherText, Options) :- ( Algorithm = 'chacha20-poly1305' -> true ; domain_error('chacha20-poly1305', Algorithm, crypto_data_encrypt/6) ), - '$crypto_data_encrypt'(PlainText, Key, IV, Tag, CipherText). + '$crypto_data_encrypt'(PlainText, Encoding, Key, IV, Tag, CipherText). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - crypto_data_decrypt(+CipherText, @@ -610,26 +620,25 @@ crypto_data_decrypt(CipherText0, Algorithm, Key, IV, PlainText, Options) :- must_be(atom, Encoding), member(Encoding, [utf8,octet]), must_be(list, CipherText0), - encoding_bytes(octet, CipherText0, CipherText1), - append(CipherText1, Tag, CipherText), + encoding_chars(octet, CipherText0, CipherText1), + maplist(char_code, TagChars, Tag), + append(CipherText1, TagChars, CipherText), ( Algorithm = 'chacha20-poly1305' -> true ; domain_error('chacha20-poly1305', Algorithm, crypto_data_decrypt/6) ), - '$crypto_data_decrypt'(CipherText, Key, IV, Encoding, PlainText). + '$crypto_data_decrypt'(CipherText, octet, Key, IV, Encoding, PlainText). -encoding_bytes(octet, Bs0, Bs) :- - must_be(list, Bs0), - ( maplist(integer, Bs0) -> - Bs0 = Bs - ; maplist(char_code, Bs0, Bs) + +encoding_chars(octet, Bs, Cs) :- + must_be(list, Bs), + ( maplist(integer, Bs) -> + maplist(char_code, Cs, Bs) + ; Bs = Cs ), - must_be_bytes(Bs, crypto_encoding). -encoding_bytes(utf8, Cs, Bs) :- + must_be_byte_chars(Cs, crypto_encoding). +encoding_chars(utf8, Cs, Cs) :- must_be(list, Cs), - ( maplist(atom, Cs) -> - chars_bytes_(Cs, Bs, crypto_encoding) - ; domain_error(encryption_encoding, Cs, crypto) - ). + maplist(must_be(character), Cs). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Digital signatures with Ed25519 @@ -667,21 +676,21 @@ 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_keypair_public_key(Pair, PublicKey) :- + must_be_byte_chars(Pair, ed25519_keypair_public_key), + '$ed25519_keypair_public_key'(Pair, octet, PublicKey). -ed25519_sign(Key0, Data0, Signature, Options) :- - options_data_bytes(Options, Data0, Data), - encoding_bytes(octet, Key0, Key), - '$ed25519_sign'(Key, Data, Signature0), +ed25519_sign(Key, Data0, Signature, Options) :- + must_be_byte_chars(Key, ed25519_sign), + options_data_chars(Options, Data0, Data, Encoding), + '$ed25519_sign'(Key, octet, Data, Encoding, Signature0), hex_bytes(Signature, Signature0). -ed25519_verify(Key0, Data0, Signature0, Options) :- - options_data_bytes(Options, Data0, Data), - encoding_bytes(octet, Key0, Key), +ed25519_verify(Key, Data0, Signature0, Options) :- + must_be_byte_chars(Key, ed25519_verify), + options_data_chars(Options, Data0, Data, Encoding), hex_bytes(Signature0, Signature), - '$ed25519_verify'(Key, Data, Signature). + '$ed25519_verify'(Key, octet, Data, Encoding, Signature). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Operations on Elliptic Curves diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index e8db40ce..20bf70e4 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -5291,11 +5291,9 @@ impl MachineState { self.unify(arg, byte); } &SystemClauseType::CryptoDataHash => { - let stub = MachineError::functor_stub(clause_name!("crypto_data_hash"), 3); - let bytes = self.integers_to_bytevec(temp_v!(1), stub); + let bytes = self.string_encoding_bytes(1, 2); - let algorithm = self[temp_v!(3)]; - let algorithm_str = match self.store(self.deref(algorithm)) { + let algorithm_str = 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() @@ -5344,17 +5342,16 @@ impl MachineState { } }; - self.unify(self[temp_v!(2)], ints_list); + self.unify(self[temp_v!(3)], ints_list); } &SystemClauseType::CryptoDataHKDF => { + let data = self.string_encoding_bytes(1, 2); let stub1 = MachineError::functor_stub(clause_name!("crypto_data_hkdf"), 4); - let data = self.integers_to_bytevec(temp_v!(1), stub1); + let salt = self.integers_to_bytevec(temp_v!(3), stub1); let stub2 = MachineError::functor_stub(clause_name!("crypto_data_hkdf"), 4); - let salt = self.integers_to_bytevec(temp_v!(2), stub2); - let stub3 = MachineError::functor_stub(clause_name!("crypto_data_hkdf"), 4); - let info = self.integers_to_bytevec(temp_v!(3), stub3); + let info = self.integers_to_bytevec(temp_v!(4), stub2); - let algorithm = match self.store(self.deref(self[temp_v!(4)])) { + let algorithm = match self.store(self.deref(self[temp_v!(5)])) { Addr::Con(h) if self.heap.atom_at(h) => { if let HeapCellValue::Atom(ref atom, _) = &self.heap[h] { atom.as_str() @@ -5368,7 +5365,7 @@ impl MachineState { }; let length = - match Number::try_from((self[temp_v!(5)], &self.heap)) { + match Number::try_from((self[temp_v!(6)], &self.heap)) { Ok(Number::Fixnum(n)) => { usize::try_from(n).unwrap() } @@ -5400,7 +5397,7 @@ impl MachineState { Addr::HeapCell(self.heap.to_list(bytes.iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) }; - self.unify(self[temp_v!(6)], ints_list); + self.unify(self[temp_v!(7)], ints_list); } &SystemClauseType::CryptoPasswordHash => { let stub1 = MachineError::functor_stub(clause_name!("crypto_password_hash"), 3); @@ -5436,12 +5433,11 @@ 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 data = self.string_encoding_bytes(1, 2); let stub2 = MachineError::functor_stub(clause_name!("crypto_data_encrypt"), 6); - let key = self.integers_to_bytevec(temp_v!(2), stub2); + let key = self.integers_to_bytevec(temp_v!(3), stub2); let stub3 = MachineError::functor_stub(clause_name!("crypto_data_encrypt"), 6); - let iv = self.integers_to_bytevec(temp_v!(3), stub3); + let iv = self.integers_to_bytevec(temp_v!(4), stub3); let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap(); let nonce = aead::Nonce::try_assume_unique_for_key(&iv).unwrap(); @@ -5462,18 +5458,17 @@ impl MachineState { self.heap.put_complete_string(&buffer) }; - self.unify(self[temp_v!(4)], tag_list); - self.unify(self[temp_v!(5)], complete_string); + self.unify(self[temp_v!(5)], tag_list); + self.unify(self[temp_v!(6)], complete_string); } &SystemClauseType::CryptoDataDecrypt => { + let data = self.string_encoding_bytes(1, 2); let stub1 = MachineError::functor_stub(clause_name!("crypto_data_decrypt"), 6); - let data = self.integers_to_bytevec(temp_v!(1), stub1); + let key = self.integers_to_bytevec(temp_v!(3), 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 iv = self.integers_to_bytevec(temp_v!(4), stub2); - let encoding = match self.store(self.deref(self[temp_v!(4)])) { + let encoding = match self.store(self.deref(self[temp_v!(5)])) { Addr::Con(h) if self.heap.atom_at(h) => { if let HeapCellValue::Atom(ref atom, _) = &self.heap[h] { atom.as_str() @@ -5512,7 +5507,7 @@ impl MachineState { self.heap.put_complete_string(&buffer) }; - self.unify(self[temp_v!(5)], complete_string); + self.unify(self[temp_v!(6)], complete_string); } &SystemClauseType::CryptoCurveScalarMult => { let curve = match self.store(self.deref(self[temp_v!(1)])) { @@ -5573,8 +5568,7 @@ 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 bytes = self.string_encoding_bytes(1, 2); let key_pair = match signature::Ed25519KeyPair::from_pkcs8(&bytes) { Ok(kp) => { kp } @@ -5586,13 +5580,11 @@ impl MachineState { self.heap.put_complete_string(&buffer) }; - self.unify(self[temp_v!(2)], complete_string); + self.unify(self[temp_v!(3)], 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 = self.string_encoding_bytes(1, 2); + let data = self.string_encoding_bytes(3, 4); let key_pair = match signature::Ed25519KeyPair::from_pkcs8(&key) { Ok(kp) => { kp } @@ -5604,15 +5596,13 @@ impl MachineState { 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); + self.unify(self[temp_v!(5)], 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 key = self.string_encoding_bytes(1, 2); + let data = self.string_encoding_bytes(3, 4); + let stub = MachineError::functor_stub(clause_name!("ed25519_verify"), 5); + let signature = self.integers_to_bytevec(temp_v!(5), stub); let peer_public_key = signature::UnparsedPublicKey::new(&signature::ED25519, &key); match peer_public_key.verify(&data, &signature) { @@ -5665,6 +5655,40 @@ impl MachineState { return_from_clause!(self.last_call, self) } + pub(super) + fn string_encoding_bytes( + &mut self, + data_arg: usize, + encoding_arg: usize, + ) -> Vec { + let data = self.heap_pstr_iter(self[temp_v!(data_arg)]).to_string(); + + let encoding_str = match self.store(self.deref(self[temp_v!(encoding_arg)])) { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref atom, _) = &self.heap[h] { + atom.as_str() + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + }; + + match encoding_str { + "utf8" => { data.into_bytes() } + "octet" => { + let mut buf = vec![]; + for c in data.chars() { + buf.push(c as u8); + } + buf + } + _ => { unreachable!() } + } + } + pub(super) fn xml_node_to_term( &mut self,