simplify byte encodings, use prefixes for crypto data types
This commit is contained in:
parent
5b5bb430b9
commit
024a715d0d
8 changed files with 251 additions and 163 deletions
26
README.md
26
README.md
|
@ -82,6 +82,30 @@ Terms can be interpreted in a number of different ways, depending on the context
|
||||||
|
|
||||||
Terms further have mappings as different data types:
|
Terms further have mappings as different data types:
|
||||||
|
|
||||||
- BYTES: if the term maps as a STRING, decode it using base64
|
|
||||||
- INT: if the term maps as a STRING, decode it as an integer written in decimal notation
|
- INT: if the term maps as a STRING, decode it as an integer written in decimal notation
|
||||||
|
- BYTES: if the term maps as a STRING, decode it using base64. Since a STRING cannot be empty, the string `-` is used to represent an empty byte string.
|
||||||
|
- Cryptographic data types (see below)
|
||||||
|
|
||||||
|
## Cryptographic data types
|
||||||
|
|
||||||
|
Cryptographic values such as keys, hashes, signatures, etc. are encoded
|
||||||
|
as STRING with a prefix indicating the algorithm used, followed by ":",
|
||||||
|
followed by the base64-encoded value.
|
||||||
|
|
||||||
|
Prefixes are as follows:
|
||||||
|
|
||||||
|
- `pk.box:` public key for NaCl's box API
|
||||||
|
- `sk.box:` secret key for NaCl's box API
|
||||||
|
- `sk.sbox:` secret key for NaCl's secretbox API
|
||||||
|
- `h.sha256:` sha256 hash
|
||||||
|
- `h.sha512:` sha512 hash
|
||||||
|
- `h.sha3:` sha3 hash
|
||||||
|
- `h.b2:` blake2b hash
|
||||||
|
- `h.b3:` blake3 hash
|
||||||
|
- `sig.ed25519:` ed25519 signature
|
||||||
|
- `pk.ed25519:` ed25519 public signing key
|
||||||
|
- `sk.ed25519:` ed25519 secret signing key
|
||||||
|
|
||||||
|
More can be added.
|
||||||
|
|
||||||
- HASH, PUBKEY, SECKEY, SIGNATURE, ENCKEY, DECKEY, SYMKEY: a bunch of things that interpret BYTES as specific cryptographic items
|
- HASH, PUBKEY, SECKEY, SIGNATURE, ENCKEY, DECKEY, SYMKEY: a bunch of things that interpret BYTES as specific cryptographic items
|
||||||
|
|
|
@ -1,48 +1,156 @@
|
||||||
//! Helpers to use cryptographic data types in nettext
|
//! Helpers to use cryptographic data types in nettext
|
||||||
|
|
||||||
|
pub use dryoc::types::Bytes;
|
||||||
pub use dryoc::*;
|
pub use dryoc::*;
|
||||||
|
|
||||||
use dryoc::types::{Bytes, StackByteArray};
|
use crate::dec;
|
||||||
|
|
||||||
use crate::enc;
|
use crate::enc;
|
||||||
|
|
||||||
pub type SigningKeyPair = sign::SigningKeyPair<sign::PublicKey, sign::SecretKey>;
|
const BM_HASH: &str = "h.b2";
|
||||||
|
|
||||||
impl<const N: usize> enc::Encode for StackByteArray<N> {
|
const BM_SIGNATURE: &str = "sig.ed25519";
|
||||||
fn term(&self) -> enc::Result<'_> {
|
const BM_SIGN_KEYPAIR: &str = "sk.ed25519";
|
||||||
Ok(enc::bytes(self.as_slice()))
|
const BM_SIGN_PUBKEY: &str = "pk.ed25519";
|
||||||
|
|
||||||
|
// ---- types ----
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||||
|
pub struct Hash(pub generichash::Hash);
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||||
|
pub struct Signature(pub sign::Signature);
|
||||||
|
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||||
|
pub struct SigningPublicKey(pub sign::PublicKey);
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub struct SigningKeyPair(pub sign::SigningKeyPair<sign::PublicKey, sign::SecretKey>);
|
||||||
|
|
||||||
|
impl SigningKeyPair {
|
||||||
|
/// Return the public key of this keypair
|
||||||
|
pub fn public_key(&self) -> SigningPublicKey {
|
||||||
|
SigningPublicKey(self.0.public_key.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl enc::Encode for sign::SigningKeyPair<sign::PublicKey, sign::SecretKey> {
|
// ---- encoding ----
|
||||||
|
|
||||||
|
impl enc::Encode for Hash {
|
||||||
fn term(&self) -> enc::Result<'_> {
|
fn term(&self) -> enc::Result<'_> {
|
||||||
Ok(enc::bytes(self.secret_key.as_slice()))
|
enc::marked_bytes(BM_HASH, self.0.as_slice())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- helpers ----
|
impl enc::Encode for Signature {
|
||||||
|
fn term(&self) -> enc::Result<'_> {
|
||||||
|
enc::marked_bytes(BM_SIGNATURE, self.0.as_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl enc::Encode for SigningPublicKey {
|
||||||
|
fn term(&self) -> enc::Result<'_> {
|
||||||
|
enc::marked_bytes(BM_SIGN_PUBKEY, self.0.as_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl enc::Encode for SigningKeyPair {
|
||||||
|
fn term(&self) -> enc::Result<'_> {
|
||||||
|
enc::marked_bytes(BM_SIGN_KEYPAIR, self.0.secret_key.as_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- calculating hashes, signatures, etc ----
|
||||||
|
|
||||||
/// Compute the hash of a payload with default dryoc parameters and optionnal key
|
/// Compute the hash of a payload with default dryoc parameters and optionnal key
|
||||||
pub fn compute_hash(bytes: &[u8], key: Option<&[u8; 32]>) -> generichash::Hash {
|
pub fn compute_hash(bytes: &[u8], key: Option<&[u8; 32]>) -> Hash {
|
||||||
generichash::GenericHash::hash_with_defaults(bytes, key).unwrap()
|
Hash(generichash::GenericHash::hash_with_defaults(bytes, key).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a new signing keypair
|
||||||
|
pub fn gen_signing_keypair() -> SigningKeyPair {
|
||||||
|
SigningKeyPair(sign::SigningKeyPair::gen_with_defaults())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the ed25519 signature of a message using a secret key
|
/// Compute the ed25519 signature of a message using a secret key
|
||||||
pub fn compute_signature(message: &[u8], secret_key: &sign::SecretKey) -> sign::Signature {
|
pub fn compute_signature(message: &[u8], keypair: &SigningKeyPair) -> Signature {
|
||||||
SigningKeyPair::from_secret_key(secret_key.clone())
|
Signature(
|
||||||
|
keypair
|
||||||
|
.0
|
||||||
.sign_with_defaults(message)
|
.sign_with_defaults(message)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_parts()
|
.into_parts()
|
||||||
.0
|
.0,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the ed25519 signature of a message using a public key
|
/// Verify the ed25519 signature of a message using a public key
|
||||||
pub fn verify_signature(
|
pub fn verify_signature(
|
||||||
signature: &sign::Signature,
|
signature: &Signature,
|
||||||
message: &[u8],
|
message: &[u8],
|
||||||
public_key: &sign::PublicKey,
|
public_key: &SigningPublicKey,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
sign::SignedMessage::from_parts(signature.clone(), message)
|
sign::SignedMessage::from_parts(signature.0.clone(), message)
|
||||||
.verify(public_key)
|
.verify(&public_key.0)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- decode helpers ----
|
||||||
|
|
||||||
|
pub trait CryptoDec {
|
||||||
|
/// Try to interpret this string as a Blake2b512 digest
|
||||||
|
/// (32-bytes base64 encoded, prefixed by `h.b2:`)
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
/// use nettext::crypto::{compute_hash, CryptoDec};
|
||||||
|
///
|
||||||
|
/// let term = decode(b"{
|
||||||
|
/// message = hello;
|
||||||
|
/// hash = h.b2:Mk3PAn3UowqTLEQfNlol6GsXPe-kuOWJSCU0cbgbcs8;
|
||||||
|
/// }").unwrap();
|
||||||
|
/// let [msg, hash] = term.dict_of(["message", "hash"], false).unwrap();
|
||||||
|
/// let expected_hash = compute_hash(msg.raw(), None);
|
||||||
|
/// assert_eq!(hash.hash().unwrap(), expected_hash);
|
||||||
|
/// ```
|
||||||
|
fn hash(&self) -> Result<Hash, dec::TypeError>;
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 signature
|
||||||
|
/// (64 bytes base64 encoded, prefixed by `sig.ed25519:`)
|
||||||
|
fn signature(&self) -> Result<Signature, dec::TypeError>;
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 keypair
|
||||||
|
/// (64 bytes base64 encoded, prefixed by `sk.ed25519:`)
|
||||||
|
fn keypair(&self) -> Result<SigningKeyPair, dec::TypeError>;
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 public key
|
||||||
|
/// (32 bytes base64 encoded, prefixed by `pk.ed25519:`)
|
||||||
|
fn public_key(&self) -> Result<SigningPublicKey, dec::TypeError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CryptoDec for dec::Term<'a, 'b> {
|
||||||
|
fn hash(&self) -> Result<Hash, dec::TypeError> {
|
||||||
|
Ok(Hash(generichash::Hash::from(
|
||||||
|
self.marked_bytes_exact(BM_HASH)?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 signature (64 bytes base64 encoded)
|
||||||
|
fn signature(&self) -> Result<Signature, dec::TypeError> {
|
||||||
|
Ok(Signature(sign::Signature::from(
|
||||||
|
self.marked_bytes_exact(BM_SIGNATURE)?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keypair(&self) -> Result<SigningKeyPair, dec::TypeError> {
|
||||||
|
let secret_key = sign::SecretKey::from(self.marked_bytes_exact(BM_SIGN_KEYPAIR)?);
|
||||||
|
Ok(SigningKeyPair(sign::SigningKeyPair::from_secret_key(
|
||||||
|
secret_key,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public_key(&self) -> Result<SigningPublicKey, dec::TypeError> {
|
||||||
|
Ok(SigningPublicKey(sign::PublicKey::from(
|
||||||
|
self.marked_bytes_exact(BM_SIGN_PUBKEY)?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,9 @@ pub enum TypeError {
|
||||||
/// The term could not be decoded in the given type
|
/// The term could not be decoded in the given type
|
||||||
#[error(display = "Not a {}", _0)]
|
#[error(display = "Not a {}", _0)]
|
||||||
WrongType(&'static str),
|
WrongType(&'static str),
|
||||||
|
/// The term did not have the correct marker
|
||||||
|
#[error(display = "Byte marker was not {}", _0)]
|
||||||
|
WrongMarker(&'static str),
|
||||||
|
|
||||||
/// The term is not an array of the requested length
|
/// The term is not an array of the requested length
|
||||||
#[error(display = "Expected {} items, got {}", _0, _1)]
|
#[error(display = "Expected {} items, got {}", _0, _1)]
|
||||||
|
|
|
@ -5,9 +5,6 @@ mod error;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[cfg(any(feature = "dryoc"))]
|
|
||||||
use crate::crypto;
|
|
||||||
|
|
||||||
use crate::debug;
|
use crate::debug;
|
||||||
|
|
||||||
pub use decode::*;
|
pub use decode::*;
|
||||||
|
@ -420,7 +417,7 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
};
|
};
|
||||||
match self.0.mkref() {
|
match self.0.mkref() {
|
||||||
AnyTerm::Str(encoded) => {
|
AnyTerm::Str(encoded) => {
|
||||||
if encoded == b"." {
|
if encoded == b"-" {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
} else {
|
} else {
|
||||||
decode(encoded)
|
decode(encoded)
|
||||||
|
@ -442,68 +439,40 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to interpret this string as base64-encoded bytes,
|
/// Try to interpret this string as base64-encoded bytes,
|
||||||
/// with an exact length.
|
/// with a marker prefix and an exact byte length.
|
||||||
|
/// This is typically used for cryptographic data types such as hashes,
|
||||||
|
/// keys, signatures, ...
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use nettext::dec::decode;
|
/// use nettext::dec::decode;
|
||||||
///
|
///
|
||||||
/// let term = decode(b"aGVsbG8sIHdvcmxkIQ").unwrap();
|
/// let term = decode(b"test:aGVsbG8sIHdvcmxkIQ").unwrap();
|
||||||
/// assert_eq!(&term.bytes_exact::<13>().unwrap(), b"hello, world!");
|
/// assert_eq!(&term.marked_bytes_exact::<13>("test").unwrap(), b"hello, world!");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn bytes_exact<const N: usize>(&self) -> Result<[u8; N], TypeError> {
|
pub fn marked_bytes_exact<const N: usize>(
|
||||||
let bytes = self.bytes()?;
|
&self,
|
||||||
|
marker: &'static str,
|
||||||
|
) -> Result<[u8; N], TypeError> {
|
||||||
|
let mkr = marker.as_bytes();
|
||||||
|
match &self.0 {
|
||||||
|
AnyTerm::Str(s)
|
||||||
|
if s.len() >= mkr.len() + 2 && &s[..mkr.len()] == mkr && s[mkr.len()] == b':' =>
|
||||||
|
{
|
||||||
|
let bytes = match &s[mkr.len() + 1..] {
|
||||||
|
b"-" => vec![],
|
||||||
|
bytes => base64::decode_config(bytes, base64::URL_SAFE_NO_PAD)
|
||||||
|
.map_err(|_| TypeError::WrongType("BYTES"))?,
|
||||||
|
};
|
||||||
let bytes_len = bytes.len();
|
let bytes_len = bytes.len();
|
||||||
bytes
|
bytes
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| TypeError::WrongLength(bytes_len, N))
|
.map_err(|_| TypeError::WrongLength(bytes_len, N))
|
||||||
}
|
}
|
||||||
}
|
AnyTerm::Str(_) => Err(TypeError::WrongMarker(marker)),
|
||||||
|
_ => Err(TypeError::WrongType("BYTES")),
|
||||||
// ---- CRYPTO HELPERS ----
|
|
||||||
|
|
||||||
#[cfg(feature = "dryoc")]
|
|
||||||
impl<'a, 'b> Term<'a, 'b> {
|
|
||||||
/// Try to interpret this string as a Blake2b512 digest (32-bytes base64 encoded)
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use nettext::dec::decode;
|
|
||||||
/// use nettext::crypto::generichash::GenericHash;
|
|
||||||
///
|
|
||||||
/// let term = decode(b"{
|
|
||||||
/// message = hello;
|
|
||||||
/// hash = Mk3PAn3UowqTLEQfNlol6GsXPe-kuOWJSCU0cbgbcs8;
|
|
||||||
/// }").unwrap();
|
|
||||||
/// let [msg, hash] = term.dict_of(["message", "hash"], false).unwrap();
|
|
||||||
/// let expected_hash = GenericHash::hash_with_defaults(msg.raw(), None::<&Vec<u8>>).unwrap();
|
|
||||||
/// assert_eq!(hash.hash().unwrap(), expected_hash);
|
|
||||||
/// ```
|
|
||||||
pub fn hash(&self) -> Result<crypto::generichash::Hash, TypeError> {
|
|
||||||
Ok(crypto::generichash::Hash::from(self.bytes_exact()?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 keypair (64 bytes base64 encoded)
|
|
||||||
pub fn keypair(&self) -> Result<crypto::SigningKeyPair, TypeError> {
|
|
||||||
let secret_key = crypto::sign::SecretKey::from(self.bytes_exact()?);
|
|
||||||
Ok(crypto::SigningKeyPair::from_secret_key(secret_key))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 public key (32 bytes base64 encoded)
|
|
||||||
pub fn public_key(&self) -> Result<crypto::sign::PublicKey, TypeError> {
|
|
||||||
Ok(crypto::sign::PublicKey::from(self.bytes_exact()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 secret key (32 bytes base64 encoded)
|
|
||||||
pub fn secret_key(&self) -> Result<crypto::sign::SecretKey, TypeError> {
|
|
||||||
Ok(crypto::sign::SecretKey::from(self.bytes_exact()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 signature (64 bytes base64 encoded)
|
|
||||||
pub fn signature(&self) -> Result<crypto::sign::Signature, TypeError> {
|
|
||||||
Ok(crypto::sign::Signature::from(self.bytes_exact()?))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,9 @@ mod error;
|
||||||
use std::borrow::{Borrow, Cow};
|
use std::borrow::{Borrow, Cow};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::*;
|
|
||||||
use crate::dec::{self, decode};
|
use crate::dec::{self, decode};
|
||||||
use crate::{is_string_char, is_whitespace, BytesEncoding};
|
use crate::*;
|
||||||
|
use crate::{is_string_char, is_whitespace};
|
||||||
|
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
|
||||||
|
@ -100,41 +100,36 @@ pub fn raw(bytes: &[u8]) -> Result<'_> {
|
||||||
/// Term corresponding to a byte slice,
|
/// Term corresponding to a byte slice,
|
||||||
/// encoding using base64 url-safe encoding without padding.
|
/// encoding using base64 url-safe encoding without padding.
|
||||||
/// Since empty strings are not possible in nettext,
|
/// Since empty strings are not possible in nettext,
|
||||||
/// an empty byte string is encoded as an empty list (`[]`).
|
/// an empty byte string is encoded as the special string `-`.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use nettext::enc::*;
|
/// use nettext::enc::*;
|
||||||
///
|
///
|
||||||
|
/// assert_eq!(bytes(b"").encode(), b"-");
|
||||||
/// assert_eq!(bytes(b"hello, world!").encode(), b"aGVsbG8sIHdvcmxkIQ");
|
/// assert_eq!(bytes(b"hello, world!").encode(), b"aGVsbG8sIHdvcmxkIQ");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn bytes(bytes: &[u8]) -> Term<'static> {
|
pub fn bytes(bytes: &[u8]) -> Term<'static> {
|
||||||
bytes_format(bytes, BytesEncoding::Base64 { split: false })
|
if bytes.is_empty() {
|
||||||
|
Term(T::Str(b"-"))
|
||||||
|
} else {
|
||||||
|
Term(T::OwnedStr(
|
||||||
|
base64::encode_config(bytes, base64::URL_SAFE_NO_PAD).into_bytes(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `bytes()`, but splits the byte slice in 48-byte chunks
|
/// Same as `bytes()`, but splits the byte slice in 48-byte chunks
|
||||||
/// and encodes each chunk separately, putting them in a sequence of terms.
|
/// and encodes each chunk separately, putting them in a sequence of terms.
|
||||||
/// Usefull for long byte slices to have cleaner representations,
|
/// Usefull for long byte slices to have cleaner representations.
|
||||||
/// mainly usefull for dictionnary keys.
|
|
||||||
pub fn bytes_split(bytes: &[u8]) -> Term<'static> {
|
pub fn bytes_split(bytes: &[u8]) -> Term<'static> {
|
||||||
bytes_format(bytes, BytesEncoding::Base64 { split: true })
|
if bytes.is_empty() {
|
||||||
}
|
Term(T::Str(b"-"))
|
||||||
|
} else {
|
||||||
pub fn bytes_format(bytes: &[u8], encoding: BytesEncoding) -> Term<'static> {
|
|
||||||
match encoding {
|
|
||||||
BytesEncoding::Base64 { .. } | BytesEncoding::Hex { .. } if bytes.is_empty() => {
|
|
||||||
Term(T::List(vec![]))
|
|
||||||
}
|
|
||||||
BytesEncoding::Base64 { split: false } => Term(T::OwnedStr(
|
|
||||||
base64::encode_config(bytes, base64::URL_SAFE_NO_PAD).into_bytes(),
|
|
||||||
)),
|
|
||||||
BytesEncoding::Base64 { split: true } => {
|
|
||||||
let chunks = bytes
|
let chunks = bytes
|
||||||
.chunks(48)
|
.chunks(48)
|
||||||
.map(|b| {
|
.map(|b| T::OwnedStr(base64::encode_config(b, base64::URL_SAFE_NO_PAD).into_bytes()))
|
||||||
T::OwnedStr(base64::encode_config(b, base64::URL_SAFE_NO_PAD).into_bytes())
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if chunks.len() > 1 {
|
if chunks.len() > 1 {
|
||||||
Term(T::Seq(chunks))
|
Term(T::Seq(chunks))
|
||||||
|
@ -142,18 +137,39 @@ pub fn bytes_format(bytes: &[u8], encoding: BytesEncoding) -> Term<'static> {
|
||||||
Term(chunks.into_iter().next().unwrap())
|
Term(chunks.into_iter().next().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BytesEncoding::Hex { split: false } => Term(T::OwnedStr(hex::encode(bytes).into_bytes())),
|
}
|
||||||
BytesEncoding::Hex { split: true } => {
|
|
||||||
let chunks = bytes
|
/// Term corresponding to a byte slice,
|
||||||
.chunks(32)
|
/// encoding using base64 url-safe encoding without padding,
|
||||||
.map(|b| T::OwnedStr(hex::encode(b).into_bytes()))
|
/// with a prefix used to identify its content type.
|
||||||
.collect::<Vec<_>>();
|
/// The marker prefix is typically used in crypto settings to identify
|
||||||
if chunks.len() > 1 {
|
/// a cryptographic protocol or algorithm; it may not contain the `:` character.
|
||||||
Term(T::Seq(chunks))
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::enc::*;
|
||||||
|
///
|
||||||
|
/// assert_eq!(marked_bytes("mytype", b"").unwrap().encode(), b"mytype:-");
|
||||||
|
/// assert_eq!(marked_bytes("mytype", b"hello, world!").unwrap().encode(), b"mytype:aGVsbG8sIHdvcmxkIQ");
|
||||||
|
/// ```
|
||||||
|
pub fn marked_bytes(marker: &str, bytes: &[u8]) -> Result<'static> {
|
||||||
|
for c in marker.as_bytes().iter() {
|
||||||
|
if !is_string_char(*c) || *c == b':' {
|
||||||
|
return Err(Error::InvalidCharacter(*c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bytes.is_empty() {
|
||||||
|
Ok(Term(T::OwnedStr(format!("{}:-", marker).into_bytes())))
|
||||||
} else {
|
} else {
|
||||||
Term(chunks.into_iter().next().unwrap())
|
Ok(Term(T::OwnedStr(
|
||||||
}
|
format!(
|
||||||
}
|
"{}:{}",
|
||||||
|
marker,
|
||||||
|
base64::encode_config(bytes, base64::URL_SAFE_NO_PAD)
|
||||||
|
)
|
||||||
|
.into_bytes(),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
38
src/lib.rs
38
src/lib.rs
|
@ -6,7 +6,7 @@
|
||||||
//! use nettext::crypto::*;
|
//! use nettext::crypto::*;
|
||||||
//!
|
//!
|
||||||
//! let final_payload = {
|
//! let final_payload = {
|
||||||
//! let keypair = SigningKeyPair::gen_with_defaults();
|
//! let keypair = gen_signing_keypair();
|
||||||
//!
|
//!
|
||||||
//! // Encode a fist object that represents a payload that will be hashed and signed
|
//! // Encode a fist object that represents a payload that will be hashed and signed
|
||||||
//! let signed_payload = seq([
|
//! let signed_payload = seq([
|
||||||
|
@ -18,12 +18,12 @@
|
||||||
//! ("c", raw(b"{ a = 12; b = 42 }").unwrap()),
|
//! ("c", raw(b"{ a = 12; b = 42 }").unwrap()),
|
||||||
//! ("d", bytes_split(&((0..128u8).collect::<Vec<_>>()))),
|
//! ("d", bytes_split(&((0..128u8).collect::<Vec<_>>()))),
|
||||||
//! ]).unwrap(),
|
//! ]).unwrap(),
|
||||||
//! keypair.public_key.term().unwrap(),
|
//! keypair.public_key().term().unwrap(),
|
||||||
//! ]).unwrap().encode();
|
//! ]).unwrap().encode();
|
||||||
//! eprintln!("{}", std::str::from_utf8(&signed_payload).unwrap());
|
//! eprintln!("{}", std::str::from_utf8(&signed_payload).unwrap());
|
||||||
//!
|
//!
|
||||||
//! let hash = compute_hash(&signed_payload, None);
|
//! let hash = compute_hash(&signed_payload, None);
|
||||||
//! let sign = compute_signature(&signed_payload[..], &keypair.secret_key);
|
//! let sign = compute_signature(&signed_payload[..], &keypair);
|
||||||
//!
|
//!
|
||||||
//! // Encode a second object that represents the signed and hashed payload
|
//! // Encode a second object that represents the signed and hashed payload
|
||||||
//! dict([
|
//! dict([
|
||||||
|
@ -62,13 +62,13 @@
|
||||||
//! d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
|
//! d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
|
||||||
//! MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
|
//! MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
|
||||||
//! YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
|
//! YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
|
||||||
//! } ZCkE-mTMlK3355u_0UzabRbSNcNO3CWAur7dAhglYtI
|
//! } pk.ed25519:inYgWFyL_BzZTsXNKp71r2aVct_3Izi_bkerbzOiz94
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! And the value of `final_payload` would be as follows:
|
//! And the value of `final_payload` would be as follows:
|
||||||
//! ```raw
|
//! ```raw
|
||||||
//! {
|
//! {
|
||||||
//! hash = fTTk8Hm0HLGwaskCIqFBzRVMrVTeXGetmNBK2X3pNyY;
|
//! hash = h.b2:B1AnRocS90DmqxynGyvvBNuh-brucNO7-5hrsGplJr0;
|
||||||
//! payload = CALL myfunction {
|
//! payload = CALL myfunction {
|
||||||
//! a = hello;
|
//! a = hello;
|
||||||
//! b = world;
|
//! b = world;
|
||||||
|
@ -76,8 +76,8 @@
|
||||||
//! d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
|
//! d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
|
||||||
//! MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
|
//! MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
|
||||||
//! YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
|
//! YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
|
||||||
//! } ZCkE-mTMlK3355u_0UzabRbSNcNO3CWAur7dAhglYtI;
|
//! } pk.ed25519:inYgWFyL_BzZTsXNKp71r2aVct_3Izi_bkerbzOiz94;
|
||||||
//! signature = XPMrlhAIMfZb6a5Fh5F_ZaEf61olJ1hK4I2kh7vEPT1n20S-943X5cH35bb0Bfwkvy_ENfOTbb3ep1zn2lSIBg;
|
//! signature = sig.ed25519:LvLC1gHxNxUH44HHQRO-zWtLM4WyXhiYLFr94qTdI311Wa-kmgZsaWqSWe3jcjkS4PnsWSNt5apgbhR68cWWCg;
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
@ -93,30 +93,6 @@ pub mod crypto;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
|
|
||||||
/// Possible encodings for byte strings in NetText
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum BytesEncoding {
|
|
||||||
/// Base64 encoding (default)
|
|
||||||
Base64 { split: bool },
|
|
||||||
/// Hexadecimal encoding
|
|
||||||
Hex { split: 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 },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- syntactic elements of the data format ----
|
// ---- syntactic elements of the data format ----
|
||||||
|
|
||||||
pub(crate) const DICT_OPEN: u8 = b'{';
|
pub(crate) const DICT_OPEN: u8 = b'{';
|
||||||
|
|
|
@ -4,7 +4,6 @@ mod de;
|
||||||
mod error;
|
mod error;
|
||||||
mod ser;
|
mod ser;
|
||||||
|
|
||||||
pub use crate::BytesEncoding;
|
|
||||||
pub use de::{from_bytes, from_term, Deserializer};
|
pub use de::{from_bytes, from_term, Deserializer};
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use ser::{to_bytes, to_term, Serializer};
|
pub use ser::{to_bytes, to_term, Serializer};
|
||||||
|
|
|
@ -2,15 +2,11 @@ use serde::{ser, Serialize};
|
||||||
|
|
||||||
use crate::enc::*;
|
use crate::enc::*;
|
||||||
use crate::serde::error::{Error, Result};
|
use crate::serde::error::{Error, Result};
|
||||||
use crate::BytesEncoding;
|
|
||||||
use serde::ser::Error as SerError;
|
use serde::ser::Error as SerError;
|
||||||
|
|
||||||
/// Serde serializer for nettext
|
/// Serde serializer for nettext
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
pub struct Serializer {
|
pub struct Serializer;
|
||||||
pub string_format: BytesEncoding,
|
|
||||||
pub bytes_format: BytesEncoding,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialize value to nettext encoder term
|
/// Serialize value to nettext encoder term
|
||||||
pub fn to_term<T>(value: &T) -> Result<Term<'static>>
|
pub fn to_term<T>(value: &T) -> Result<Term<'static>>
|
||||||
|
@ -94,11 +90,11 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
|
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
|
||||||
Ok(bytes_format(v.as_bytes(), self.string_format))
|
Ok(bytes(v.as_bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
|
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
|
||||||
Ok(bytes_format(v, self.bytes_format))
|
Ok(bytes(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_none(self) -> Result<Self::Ok> {
|
fn serialize_none(self) -> Result<Self::Ok> {
|
||||||
|
@ -313,10 +309,7 @@ impl ser::SerializeMap for MapSerializer {
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
let mut ser = Serializer {
|
let mut ser = Serializer;
|
||||||
string_format: self.ser.string_format.without_whitespace(),
|
|
||||||
bytes_format: self.ser.bytes_format.without_whitespace(),
|
|
||||||
};
|
|
||||||
self.next = Some(key.serialize(&mut ser)?.encode());
|
self.next = Some(key.serialize(&mut ser)?.encode());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue