2022-11-17 22:58:44 +00:00
|
|
|
//! A text-based data format for cryptographic network protocols.
|
|
|
|
//!
|
|
|
|
//! ```
|
|
|
|
//! use nettext::enc::*;
|
|
|
|
//! use nettext::dec::*;
|
2022-11-22 14:29:04 +00:00
|
|
|
//! use nettext::crypto::*;
|
2022-11-17 22:58:44 +00:00
|
|
|
//!
|
2022-11-22 13:56:58 +00:00
|
|
|
//! let final_payload = {
|
|
|
|
//! let keypair = SigningKeyPair::gen_with_defaults();
|
2022-11-17 22:58:44 +00:00
|
|
|
//!
|
2022-11-22 13:56:58 +00:00
|
|
|
//! // Encode a fist object that represents a payload that will be hashed and signed
|
2022-12-15 12:32:38 +00:00
|
|
|
//! let signed_payload = seq([
|
2022-11-22 13:56:58 +00:00
|
|
|
//! string("CALL").unwrap(),
|
|
|
|
//! string("myfunction").unwrap(),
|
|
|
|
//! dict([
|
|
|
|
//! ("a", string("hello").unwrap()),
|
|
|
|
//! ("b", string("world").unwrap()),
|
2023-05-10 10:05:25 +00:00
|
|
|
//! ("c", raw(b"{ a = 12; b = 42 }").unwrap()),
|
2022-11-22 13:56:58 +00:00
|
|
|
//! ("d", bytes_split(&((0..128u8).collect::<Vec<_>>()))),
|
|
|
|
//! ]).unwrap(),
|
|
|
|
//! keypair.public_key.term().unwrap(),
|
|
|
|
//! ]).unwrap().encode();
|
|
|
|
//! eprintln!("{}", std::str::from_utf8(&signed_payload).unwrap());
|
2022-11-17 23:01:23 +00:00
|
|
|
//!
|
2022-11-22 14:29:04 +00:00
|
|
|
//! let hash = compute_hash(&signed_payload, None);
|
|
|
|
//! let sign = compute_signature(&signed_payload[..], &keypair.secret_key);
|
2022-11-17 22:58:44 +00:00
|
|
|
//!
|
2022-11-22 13:56:58 +00:00
|
|
|
//! // Encode a second object that represents the signed and hashed payload
|
|
|
|
//! dict([
|
|
|
|
//! ("hash", hash.term().unwrap()),
|
|
|
|
//! ("signature", sign.term().unwrap()),
|
|
|
|
//! ("payload", raw(&signed_payload).unwrap()),
|
|
|
|
//! ]).unwrap().encode()
|
|
|
|
//! };
|
|
|
|
//! eprintln!("{}", std::str::from_utf8(&final_payload).unwrap());
|
2022-11-17 23:01:23 +00:00
|
|
|
//!
|
|
|
|
//! // Decode and check everything is fine
|
2022-11-22 13:56:58 +00:00
|
|
|
//! let signed_object = decode(&final_payload).unwrap();
|
|
|
|
//! let [hash, signature, payload] = signed_object.dict_of(["hash", "signature", "payload"], false).unwrap();
|
2022-11-22 14:29:04 +00:00
|
|
|
//! let hash = hash.hash().unwrap();
|
2022-11-22 13:56:58 +00:00
|
|
|
//! let signature = signature.signature().unwrap();
|
2022-11-22 14:29:04 +00:00
|
|
|
//! let expected_hash = compute_hash(payload.raw(), None);
|
2022-11-22 13:56:58 +00:00
|
|
|
//! assert_eq!(hash, expected_hash);
|
2022-11-17 23:01:23 +00:00
|
|
|
//!
|
|
|
|
//! let object2 = decode(payload.raw()).unwrap();
|
|
|
|
//!
|
2022-12-15 12:32:38 +00:00
|
|
|
//! let [verb, arg1, arg2, pubkey] = object2.seq_of().unwrap();
|
2022-11-17 23:01:23 +00:00
|
|
|
//! let pubkey = pubkey.public_key().unwrap();
|
2022-11-22 14:29:04 +00:00
|
|
|
//! assert!(verify_signature(&signature, payload.raw(), &pubkey));
|
2022-11-17 23:01:23 +00:00
|
|
|
//!
|
|
|
|
//! assert_eq!(verb.string().unwrap(), "CALL");
|
|
|
|
//! assert_eq!(arg1.string().unwrap(), "myfunction");
|
2022-11-17 22:58:44 +00:00
|
|
|
//! ```
|
|
|
|
//!
|
2023-05-10 10:05:25 +00:00
|
|
|
//! The value of `signed_payload` would be as follows:
|
2022-11-17 22:58:44 +00:00
|
|
|
//!
|
|
|
|
//! ```raw
|
|
|
|
//! CALL myfunction {
|
2023-05-10 10:05:25 +00:00
|
|
|
//! a = hello;
|
|
|
|
//! b = world;
|
|
|
|
//! c = { a = 12; b = 42 };
|
2022-11-18 00:59:00 +00:00
|
|
|
//! d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
|
|
|
|
//! MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
|
2023-05-10 10:05:25 +00:00
|
|
|
//! YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
|
|
|
|
//! } ZCkE-mTMlK3355u_0UzabRbSNcNO3CWAur7dAhglYtI
|
2022-11-17 22:58:44 +00:00
|
|
|
//! ```
|
|
|
|
//!
|
2023-05-10 10:05:25 +00:00
|
|
|
//! And the value of `final_payload` would be as follows:
|
2022-11-17 22:58:44 +00:00
|
|
|
//! ```raw
|
|
|
|
//! {
|
2023-05-10 10:05:25 +00:00
|
|
|
//! hash = fTTk8Hm0HLGwaskCIqFBzRVMrVTeXGetmNBK2X3pNyY;
|
2022-11-17 22:58:44 +00:00
|
|
|
//! payload = CALL myfunction {
|
2023-05-10 10:05:25 +00:00
|
|
|
//! a = hello;
|
|
|
|
//! b = world;
|
|
|
|
//! c = { a = 12; b = 42 };
|
2022-11-18 00:59:00 +00:00
|
|
|
//! d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
|
|
|
|
//! MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
|
2023-05-10 10:05:25 +00:00
|
|
|
//! YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
|
|
|
|
//! } ZCkE-mTMlK3355u_0UzabRbSNcNO3CWAur7dAhglYtI;
|
|
|
|
//! signature = XPMrlhAIMfZb6a5Fh5F_ZaEf61olJ1hK4I2kh7vEPT1n20S-943X5cH35bb0Bfwkvy_ENfOTbb3ep1zn2lSIBg;
|
2022-11-17 22:58:44 +00:00
|
|
|
//! }
|
|
|
|
//! ```
|
2022-11-17 23:20:42 +00:00
|
|
|
//!
|
|
|
|
//! Note that the value of `text1` is embedded as-is inside `text2`. This is what allows us
|
|
|
|
//! to check the hash and the signature: the raw representation of the term hasn't changed.
|
2022-11-17 22:58:44 +00:00
|
|
|
|
2023-05-10 14:21:49 +00:00
|
|
|
pub mod buf;
|
2022-11-17 10:48:43 +00:00
|
|
|
pub mod dec;
|
2022-11-17 16:55:50 +00:00
|
|
|
pub mod enc;
|
2022-12-15 15:47:04 +00:00
|
|
|
pub mod switch64;
|
2022-11-17 16:55:50 +00:00
|
|
|
|
2022-11-22 13:56:58 +00:00
|
|
|
#[cfg(feature = "dryoc")]
|
|
|
|
pub mod crypto;
|
|
|
|
|
2022-11-18 13:15:30 +00:00
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
pub mod serde;
|
|
|
|
|
2022-12-15 15:47:04 +00:00
|
|
|
/// Possible encodings for byte strings in NetText
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
pub enum BytesEncoding {
|
|
|
|
/// Base64 encoding (default)
|
|
|
|
Base64 { split: bool },
|
|
|
|
/// Hexadecimal encoding
|
|
|
|
Hex { split: bool },
|
|
|
|
/// Switch64 encoding, a mix of plain text and base64
|
|
|
|
Switch64 { allow_whitespace: bool },
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for BytesEncoding {
|
|
|
|
fn default() -> Self {
|
|
|
|
BytesEncoding::Base64 { split: true }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BytesEncoding {
|
|
|
|
pub fn without_whitespace(&self) -> Self {
|
|
|
|
match self {
|
|
|
|
BytesEncoding::Base64 { .. } => BytesEncoding::Base64 { split: false },
|
|
|
|
BytesEncoding::Hex { .. } => BytesEncoding::Hex { split: false },
|
|
|
|
BytesEncoding::Switch64 { .. } => BytesEncoding::Switch64 {
|
|
|
|
allow_whitespace: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-17 16:55:50 +00:00
|
|
|
// ---- syntactic elements of the data format ----
|
|
|
|
|
|
|
|
pub(crate) const DICT_OPEN: u8 = b'{';
|
|
|
|
pub(crate) const DICT_CLOSE: u8 = b'}';
|
|
|
|
pub(crate) const DICT_ASSIGN: u8 = b'=';
|
2023-05-10 10:05:25 +00:00
|
|
|
pub(crate) const DICT_DELIM: u8 = b';';
|
2022-12-15 13:45:27 +00:00
|
|
|
pub(crate) const LIST_OPEN: u8 = b'[';
|
|
|
|
pub(crate) const LIST_CLOSE: u8 = b']';
|
2023-05-10 10:05:25 +00:00
|
|
|
pub(crate) const LIST_DELIM: u8 = b';';
|
|
|
|
const BASE_EXTRA_CHARS: &[u8] = br#".,:?!@$^<>|&#"'_-+*/%"#;
|
|
|
|
const STR_EXTRA_CHARS: &[u8] = b"\\";
|
2022-12-15 15:47:04 +00:00
|
|
|
|
|
|
|
pub(crate) const SWITCH64_SEPARATOR: u8 = b'\\';
|
2023-05-10 10:05:25 +00:00
|
|
|
pub(crate) const SWITCH64_EXTRA_CHARS: &[u8] = BASE_EXTRA_CHARS;
|
2022-11-17 16:55:50 +00:00
|
|
|
|
2022-12-15 15:47:04 +00:00
|
|
|
#[inline]
|
2022-11-17 16:55:50 +00:00
|
|
|
pub(crate) fn is_string_char(c: u8) -> bool {
|
2023-05-10 10:05:25 +00:00
|
|
|
c.is_ascii_alphanumeric() || BASE_EXTRA_CHARS.contains(&c) || STR_EXTRA_CHARS.contains(&c)
|
2022-11-17 16:55:50 +00:00
|
|
|
}
|
|
|
|
|
2022-12-15 15:47:04 +00:00
|
|
|
#[inline]
|
2022-11-17 16:55:50 +00:00
|
|
|
pub(crate) fn is_whitespace(c: u8) -> bool {
|
|
|
|
c.is_ascii_whitespace()
|
|
|
|
}
|
2022-12-15 13:45:27 +00:00
|
|
|
|
|
|
|
pub(crate) fn debug(x: &[u8]) -> &str {
|
|
|
|
std::str::from_utf8(x).unwrap_or("<invalid ascii>")
|
|
|
|
}
|