mirror of
https://github.com/rosenpass/rosenpass.git
synced 2026-02-28 06:23:08 -08:00
feat(deps): Change base64 to base64ct crate (#295)
This commit is contained in:
13
Cargo.lock
generated
13
Cargo.lock
generated
@@ -166,10 +166,10 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64ct"
|
||||||
version = "0.21.7"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
@@ -1639,11 +1639,13 @@ dependencies = [
|
|||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
"allocator-api2-tests",
|
"allocator-api2-tests",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"base64ct",
|
||||||
"log",
|
"log",
|
||||||
"memsec",
|
"memsec",
|
||||||
"rand",
|
"rand",
|
||||||
"rosenpass-to",
|
"rosenpass-to",
|
||||||
"rosenpass-util",
|
"rosenpass-util",
|
||||||
|
"tempfile",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1659,9 +1661,10 @@ name = "rosenpass-util"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64ct",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"typenum",
|
"typenum",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1669,7 +1672,7 @@ name = "rp"
|
|||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64ct",
|
||||||
"ctrlc-async",
|
"ctrlc-async",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
|||||||
17
Cargo.toml
17
Cargo.toml
@@ -32,12 +32,8 @@ rosenpass-ciphers = { path = "ciphers" }
|
|||||||
rosenpass-to = { path = "to" }
|
rosenpass-to = { path = "to" }
|
||||||
rosenpass-secret-memory = { path = "secret-memory" }
|
rosenpass-secret-memory = { path = "secret-memory" }
|
||||||
rosenpass-oqs = { path = "oqs" }
|
rosenpass-oqs = { path = "oqs" }
|
||||||
criterion = "0.4.0"
|
|
||||||
test_bin = "0.4.0"
|
|
||||||
libfuzzer-sys = "0.4"
|
|
||||||
stacker = "0.1.15"
|
|
||||||
doc-comment = "0.3.3"
|
doc-comment = "0.3.3"
|
||||||
base64 = "0.21.7"
|
base64ct = {version = "1.6.0", default-features=false}
|
||||||
zeroize = "1.7.0"
|
zeroize = "1.7.0"
|
||||||
memoffset = "0.9.1"
|
memoffset = "0.9.1"
|
||||||
thiserror = "1.0.59"
|
thiserror = "1.0.59"
|
||||||
@@ -46,7 +42,6 @@ env_logger = "0.10.2"
|
|||||||
toml = "0.7.8"
|
toml = "0.7.8"
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
allocator-api2 = "0.2.14"
|
allocator-api2 = "0.2.14"
|
||||||
allocator-api2-tests = "0.2.15"
|
|
||||||
memsec = "0.6.3"
|
memsec = "0.6.3"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
typenum = "1.17.0"
|
typenum = "1.17.0"
|
||||||
@@ -61,5 +56,13 @@ blake2 = "0.10.6"
|
|||||||
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [ "std", "heapless" ] }
|
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [ "std", "heapless" ] }
|
||||||
zerocopy = { version = "0.7.32", features = ["derive"] }
|
zerocopy = { version = "0.7.32", features = ["derive"] }
|
||||||
home = "0.5.9"
|
home = "0.5.9"
|
||||||
serial_test = "3.1.1"
|
|
||||||
derive_builder = "0.20.0"
|
derive_builder = "0.20.0"
|
||||||
|
|
||||||
|
#Dev dependencies
|
||||||
|
serial_test = "3.1.1"
|
||||||
|
tempfile="3"
|
||||||
|
stacker = "0.1.15"
|
||||||
|
libfuzzer-sys = "0.4"
|
||||||
|
test_bin = "0.4.0"
|
||||||
|
criterion = "0.4.0"
|
||||||
|
allocator-api2-tests = "0.2.15"
|
||||||
@@ -5,10 +5,9 @@ use derive_builder::Builder;
|
|||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use mio::Interest;
|
use mio::Interest;
|
||||||
use mio::Token;
|
use mio::Token;
|
||||||
use rosenpass_util::file::{fopen_w, Visibility};
|
use rosenpass_util::file::{StoreValueB64, StoreValueB64Writer};
|
||||||
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
@@ -31,7 +30,10 @@ use crate::{
|
|||||||
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
|
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
|
||||||
};
|
};
|
||||||
use rosenpass_util::attempt;
|
use rosenpass_util::attempt;
|
||||||
use rosenpass_util::b64::{b64_writer, fmt_b64};
|
use rosenpass_util::b64::B64Display;
|
||||||
|
|
||||||
|
const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
|
||||||
|
const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
|
||||||
|
|
||||||
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
||||||
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
||||||
@@ -746,7 +748,7 @@ impl AppServer {
|
|||||||
KeyOutputReason::Exchanged => "Exchanged key with peer",
|
KeyOutputReason::Exchanged => "Exchanged key with peer",
|
||||||
KeyOutputReason::Stale => "Erasing outdated key from peer",
|
KeyOutputReason::Stale => "Erasing outdated key from peer",
|
||||||
};
|
};
|
||||||
info!("{} {}", msg, fmt_b64(&*peerid));
|
info!("{} {}", msg, peerid.fmt_b64::<MAX_B64_PEER_ID_SIZE>());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(of) = ap.outfile.as_ref() {
|
if let Some(of) = ap.outfile.as_ref() {
|
||||||
@@ -757,7 +759,7 @@ impl AppServer {
|
|||||||
// data will linger in the linux page cache anyways with the current
|
// data will linger in the linux page cache anyways with the current
|
||||||
// implementation, going to great length to erase the secret here is
|
// implementation, going to great length to erase the secret here is
|
||||||
// not worth it right now.
|
// not worth it right now.
|
||||||
b64_writer(fopen_w(of, Visibility::Secret)?).write_all(key.secret())?;
|
key.store_b64::<MAX_B64_KEY_SIZE, _>(of)?;
|
||||||
let why = match why {
|
let why = match why {
|
||||||
KeyOutputReason::Exchanged => "exchanged",
|
KeyOutputReason::Exchanged => "exchanged",
|
||||||
KeyOutputReason::Stale => "stale",
|
KeyOutputReason::Stale => "stale",
|
||||||
@@ -767,7 +769,7 @@ impl AppServer {
|
|||||||
// it is meant to allow external detection of a successful key-exchange
|
// it is meant to allow external detection of a successful key-exchange
|
||||||
println!(
|
println!(
|
||||||
"output-key peer {} key-file {of:?} {why}",
|
"output-key peer {} key-file {of:?} {why}",
|
||||||
fmt_b64(&*peerid)
|
peerid.fmt_b64::<MAX_B64_PEER_ID_SIZE>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,7 +794,10 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
b64_writer(child.stdin.take().unwrap()).write_all(key.secret())?;
|
if let Err(e) = key.store_b64_writer::<MAX_B64_KEY_SIZE, _>(child.stdin.take().unwrap())
|
||||||
|
{
|
||||||
|
error!("could not write psk to wg: {:?}", e);
|
||||||
|
}
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let status = child.wait();
|
let status = child.wait();
|
||||||
|
|||||||
@@ -300,6 +300,7 @@ impl CliCommand {
|
|||||||
config: config::Rosenpass,
|
config: config::Rosenpass,
|
||||||
test_helpers: Option<AppServerTest>,
|
test_helpers: Option<AppServerTest>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
const MAX_PSK_SIZE: usize = 1000;
|
||||||
// load own keys
|
// load own keys
|
||||||
let sk = SSk::load(&config.secret_key)?;
|
let sk = SSk::load(&config.secret_key)?;
|
||||||
let pk = SPk::load(&config.public_key)?;
|
let pk = SPk::load(&config.public_key)?;
|
||||||
@@ -316,7 +317,10 @@ impl CliCommand {
|
|||||||
for cfg_peer in config.peers {
|
for cfg_peer in config.peers {
|
||||||
srv.add_peer(
|
srv.add_peer(
|
||||||
// psk, pk, outfile, outwg, tx_addr
|
// psk, pk, outfile, outwg, tx_addr
|
||||||
cfg_peer.pre_shared_key.map(SymKey::load_b64).transpose()?,
|
cfg_peer
|
||||||
|
.pre_shared_key
|
||||||
|
.map(SymKey::load_b64::<MAX_PSK_SIZE, _>)
|
||||||
|
.transpose()?,
|
||||||
SPk::load(&cfg_peer.public_key)?,
|
SPk::load(&cfg_peer.public_key)?,
|
||||||
cfg_peer.key_out,
|
cfg_peer.key_out,
|
||||||
cfg_peer.wg.map(|cfg| app_server::WireguardOut {
|
cfg_peer.wg.map(|cfg| app_server::WireguardOut {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ repository = "https://github.com/rosenpass/rosenpass"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64ct = { workspace = true }
|
||||||
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
@@ -34,5 +34,5 @@ netlink-packet-generic = "0.3"
|
|||||||
netlink-packet-wireguard = "0.2"
|
netlink-packet-wireguard = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = {workspace = true}
|
||||||
stacker = "0.1.15"
|
stacker = {workspace = true}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::{net::SocketAddr, path::PathBuf};
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::key::WG_B64_LEN;
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ExchangePeer {
|
pub struct ExchangePeer {
|
||||||
pub public_keys_dir: PathBuf,
|
pub public_keys_dir: PathBuf,
|
||||||
@@ -160,7 +161,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
|||||||
|
|
||||||
let wgsk_path = options.private_keys_dir.join("wgsk");
|
let wgsk_path = options.private_keys_dir.join("wgsk");
|
||||||
|
|
||||||
let wgsk = Secret::<WG_KEY_LEN>::load_b64(wgsk_path)?;
|
let wgsk = Secret::<WG_KEY_LEN>::load_b64::<WG_B64_LEN, _>(wgsk_path)?;
|
||||||
|
|
||||||
let mut attr: Vec<WgDeviceAttrs> = Vec::with_capacity(2);
|
let mut attr: Vec<WgDeviceAttrs> = Vec::with_capacity(2);
|
||||||
attr.push(WgDeviceAttrs::PrivateKey(*wgsk.secret()));
|
attr.push(WgDeviceAttrs::PrivateKey(*wgsk.secret()));
|
||||||
@@ -221,7 +222,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
|||||||
|
|
||||||
srv.add_peer(
|
srv.add_peer(
|
||||||
if psk.exists() {
|
if psk.exists() {
|
||||||
Some(SymKey::load_b64(psk))
|
Some(SymKey::load_b64::<WG_B64_LEN, _>(psk))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs::{self, DirBuilder, OpenOptions},
|
fs::{self, DirBuilder},
|
||||||
io::Write,
|
os::unix::fs::{DirBuilderExt, PermissionsExt},
|
||||||
os::unix::fs::{DirBuilderExt, OpenOptionsExt, PermissionsExt},
|
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use base64::Engine;
|
use rosenpass_util::file::{LoadValueB64, StoreValueB64};
|
||||||
use rosenpass_util::file::LoadValueB64;
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use rosenpass::protocol::{SPk, SSk};
|
use rosenpass::protocol::{SPk, SSk};
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::kem::StaticKem;
|
||||||
use rosenpass_secret_memory::{file::StoreSecret as _, Secret};
|
use rosenpass_secret_memory::{file::StoreSecret as _, Public, Secret};
|
||||||
|
|
||||||
|
pub const WG_B64_LEN: usize = 32 * 5 / 3;
|
||||||
|
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
pub fn genkey(_: &Path) -> Result<()> {
|
pub fn genkey(_: &Path) -> Result<()> {
|
||||||
@@ -45,18 +45,7 @@ pub fn genkey(private_keys_dir: &Path) -> Result<()> {
|
|||||||
|
|
||||||
if !wgsk_path.exists() {
|
if !wgsk_path.exists() {
|
||||||
let wgsk: Secret<32> = Secret::random();
|
let wgsk: Secret<32> = Secret::random();
|
||||||
|
wgsk.store_b64::<WG_B64_LEN, _>(wgsk_path)?;
|
||||||
let mut wgsk_file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.mode(0o600)
|
|
||||||
.open(wgsk_path)?;
|
|
||||||
|
|
||||||
wgsk_file.write_all(
|
|
||||||
base64::engine::general_purpose::STANDARD
|
|
||||||
.encode(wgsk.secret())
|
|
||||||
.as_bytes(),
|
|
||||||
)?;
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"WireGuard secret key already exists at {:#?}: not regenerating",
|
"WireGuard secret key already exists at {:#?}: not regenerating",
|
||||||
@@ -92,18 +81,15 @@ pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> {
|
|||||||
let private_pqpk = private_keys_dir.join("pqpk");
|
let private_pqpk = private_keys_dir.join("pqpk");
|
||||||
let public_pqpk = public_keys_dir.join("pqpk");
|
let public_pqpk = public_keys_dir.join("pqpk");
|
||||||
|
|
||||||
let wgsk = Secret::load_b64(private_wgsk)?;
|
let wgsk = Secret::load_b64::<WG_B64_LEN, _>(private_wgsk)?;
|
||||||
let mut wgpk: x25519_dalek::PublicKey = {
|
let mut wgpk: Public<32> = {
|
||||||
let mut secret = x25519_dalek::StaticSecret::from(*wgsk.secret());
|
let mut secret = x25519_dalek::StaticSecret::from(*wgsk.secret());
|
||||||
let public = x25519_dalek::PublicKey::from(&secret);
|
let public = x25519_dalek::PublicKey::from(&secret);
|
||||||
secret.zeroize();
|
secret.zeroize();
|
||||||
public
|
Public::from_slice(public.as_bytes())
|
||||||
};
|
};
|
||||||
|
|
||||||
fs::write(
|
wgpk.store_b64::<WG_B64_LEN, _>(public_wgpk)?;
|
||||||
public_wgpk,
|
|
||||||
base64::engine::general_purpose::STANDARD.encode(wgpk.as_bytes()),
|
|
||||||
)?;
|
|
||||||
wgpk.zeroize();
|
wgpk.zeroize();
|
||||||
|
|
||||||
fs::copy(private_pqpk, public_pqpk)?;
|
fs::copy(private_pqpk, public_pqpk)?;
|
||||||
@@ -115,12 +101,13 @@ pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use base64::Engine;
|
|
||||||
use rosenpass::protocol::{SPk, SSk};
|
use rosenpass::protocol::{SPk, SSk};
|
||||||
|
use rosenpass_secret_memory::Secret;
|
||||||
use rosenpass_util::file::LoadValue;
|
use rosenpass_util::file::LoadValue;
|
||||||
|
use rosenpass_util::file::LoadValueB64;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use crate::key::{genkey, pubkey};
|
use crate::key::{genkey, pubkey, WG_B64_LEN};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_works() {
|
fn it_works() {
|
||||||
@@ -136,9 +123,9 @@ mod tests {
|
|||||||
assert!(private_keys_dir.path().is_dir());
|
assert!(private_keys_dir.path().is_dir());
|
||||||
assert!(SPk::load(private_keys_dir.path().join("pqpk")).is_ok());
|
assert!(SPk::load(private_keys_dir.path().join("pqpk")).is_ok());
|
||||||
assert!(SSk::load(private_keys_dir.path().join("pqsk")).is_ok());
|
assert!(SSk::load(private_keys_dir.path().join("pqsk")).is_ok());
|
||||||
assert!(base64::engine::general_purpose::STANDARD
|
assert!(
|
||||||
.decode(&fs::read_to_string(private_keys_dir.path().join("wgsk")).unwrap())
|
Secret::<32>::load_b64::<WG_B64_LEN, _>(private_keys_dir.path().join("wgsk")).is_ok()
|
||||||
.is_ok());
|
);
|
||||||
|
|
||||||
let public_keys_dir = tempdir().unwrap();
|
let public_keys_dir = tempdir().unwrap();
|
||||||
fs::remove_dir(public_keys_dir.path()).unwrap();
|
fs::remove_dir(public_keys_dir.path()).unwrap();
|
||||||
@@ -151,9 +138,9 @@ mod tests {
|
|||||||
assert!(public_keys_dir.path().exists());
|
assert!(public_keys_dir.path().exists());
|
||||||
assert!(public_keys_dir.path().is_dir());
|
assert!(public_keys_dir.path().is_dir());
|
||||||
assert!(SPk::load(public_keys_dir.path().join("pqpk")).is_ok());
|
assert!(SPk::load(public_keys_dir.path().join("pqpk")).is_ok());
|
||||||
assert!(base64::engine::general_purpose::STANDARD
|
assert!(
|
||||||
.decode(&fs::read_to_string(public_keys_dir.path().join("wgpk")).unwrap())
|
Secret::<32>::load_b64::<WG_B64_LEN, _>(public_keys_dir.path().join("wgpk")).is_ok()
|
||||||
.is_ok());
|
);
|
||||||
|
|
||||||
let pk_1 = fs::read(private_keys_dir.path().join("pqpk")).unwrap();
|
let pk_1 = fs::read(private_keys_dir.path().join("pqpk")).unwrap();
|
||||||
let pk_2 = fs::read(public_keys_dir.path().join("pqpk")).unwrap();
|
let pk_2 = fs::read(public_keys_dir.path().join("pqpk")).unwrap();
|
||||||
|
|||||||
@@ -21,3 +21,5 @@ log = { workspace = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
allocator-api2-tests = { workspace = true }
|
allocator-api2-tests = { workspace = true }
|
||||||
|
tempfile = {workspace = true}
|
||||||
|
base64ct = {workspace = true}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
use crate::debug::debug_crypto_array;
|
use crate::debug::debug_crypto_array;
|
||||||
|
use anyhow::Context;
|
||||||
use rand::{Fill as Randomize, Rng};
|
use rand::{Fill as Randomize, Rng};
|
||||||
use rosenpass_to::{ops::copy_slice, To};
|
use rosenpass_to::{ops::copy_slice, To};
|
||||||
use rosenpass_util::file::{fopen_r, LoadValue, ReadExactToEnd, StoreValue};
|
use rosenpass_util::b64::{b64_decode, b64_encode};
|
||||||
|
use rosenpass_util::file::{
|
||||||
|
fopen_r, fopen_w, LoadValue, LoadValueB64, ReadExactToEnd, ReadSliceToEnd, StoreValue,
|
||||||
|
StoreValueB64, StoreValueB64Writer, Visibility,
|
||||||
|
};
|
||||||
use rosenpass_util::functional::mutating;
|
use rosenpass_util::functional::mutating;
|
||||||
use std::borrow::{Borrow, BorrowMut};
|
use std::borrow::{Borrow, BorrowMut};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::io::Write;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -110,3 +116,164 @@ impl<const N: usize> StoreValue for Public<N> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> LoadValueB64 for Public<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let mut f = [0u8; F];
|
||||||
|
let mut v = Public::zero();
|
||||||
|
let p = path.as_ref();
|
||||||
|
|
||||||
|
let len = fopen_r(p)?
|
||||||
|
.read_slice_to_end(&mut f)
|
||||||
|
.with_context(|| format!("Could not load file {p:?}"))?;
|
||||||
|
|
||||||
|
b64_decode(&f[0..len], &mut v.value)
|
||||||
|
.with_context(|| format!("Could not decode base64 file {p:?}"))?;
|
||||||
|
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> StoreValueB64 for Public<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
|
let p = path.as_ref();
|
||||||
|
let mut f = [0u8; F];
|
||||||
|
let encoded_str = b64_encode(&self.value, &mut f)
|
||||||
|
.with_context(|| format!("Could not encode base64 file {p:?}"))?;
|
||||||
|
fopen_w(p, Visibility::Public)?
|
||||||
|
.write_all(encoded_str.as_bytes())
|
||||||
|
.with_context(|| format!("Could not write file {p:?}"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> StoreValueB64Writer for Public<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||||
|
&self,
|
||||||
|
mut writer: W,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let mut f = [0u8; F];
|
||||||
|
let encoded_str = b64_encode(&self.value, &mut f)
|
||||||
|
.with_context(|| format!("Could not encode secret to base64"))?;
|
||||||
|
|
||||||
|
writer
|
||||||
|
.write_all(encoded_str.as_bytes())
|
||||||
|
.with_context(|| format!("Could not write base64 to writer"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::Public;
|
||||||
|
use rosenpass_util::{
|
||||||
|
b64::b64_encode,
|
||||||
|
file::{
|
||||||
|
fopen_w, LoadValue, LoadValueB64, StoreValue, StoreValueB64, StoreValueB64Writer,
|
||||||
|
Visibility,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::{fs, os::unix::fs::PermissionsExt};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
/// test loading a public from an example file, and then storing it again in a different file
|
||||||
|
#[test]
|
||||||
|
fn test_public_load_store() {
|
||||||
|
const N: usize = 100;
|
||||||
|
|
||||||
|
// Generate original random bytes
|
||||||
|
let original_bytes: [u8; N] = [rand::random(); N];
|
||||||
|
|
||||||
|
// Create a temporary directory
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
|
||||||
|
// Store the original public to an example file in the temporary directory
|
||||||
|
let example_file = temp_dir.path().join("example_file");
|
||||||
|
std::fs::write(example_file.clone(), &original_bytes).unwrap();
|
||||||
|
|
||||||
|
// Load the public from the example file
|
||||||
|
|
||||||
|
let loaded_public = Public::load(&example_file).unwrap();
|
||||||
|
|
||||||
|
// Check that the loaded public matches the original bytes
|
||||||
|
assert_eq!(&loaded_public.value, &original_bytes);
|
||||||
|
|
||||||
|
// Store the loaded public to a different file in the temporary directory
|
||||||
|
let new_file = temp_dir.path().join("new_file");
|
||||||
|
loaded_public.store(&new_file).unwrap();
|
||||||
|
|
||||||
|
// Read the contents of the new file
|
||||||
|
let new_file_contents = fs::read(&new_file).unwrap();
|
||||||
|
|
||||||
|
// Read the contents of the original file
|
||||||
|
let original_file_contents = fs::read(&example_file).unwrap();
|
||||||
|
|
||||||
|
// Check that the contents of the new file match the original file
|
||||||
|
assert_eq!(new_file_contents, original_file_contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// test loading a base64 encoded public from an example file, and then storing it again in a different file
|
||||||
|
#[test]
|
||||||
|
fn test_public_load_store_base64() {
|
||||||
|
const N: usize = 100;
|
||||||
|
// Generate original random bytes
|
||||||
|
let original_bytes: [u8; N] = [rand::random(); N];
|
||||||
|
// Create a temporary directory
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let example_file = temp_dir.path().join("example_file");
|
||||||
|
let mut encoded_public = [0u8; N * 2];
|
||||||
|
let encoded_public = b64_encode(&original_bytes, &mut encoded_public).unwrap();
|
||||||
|
std::fs::write(&example_file, encoded_public).unwrap();
|
||||||
|
|
||||||
|
// Load the public from the example file
|
||||||
|
let loaded_public = Public::load_b64::<{ N * 2 }, _>(&example_file).unwrap();
|
||||||
|
// Check that the loaded public matches the original bytes
|
||||||
|
assert_eq!(&loaded_public.value, &original_bytes);
|
||||||
|
|
||||||
|
// Store the loaded public to a different file in the temporary directory
|
||||||
|
let new_file = temp_dir.path().join("new_file");
|
||||||
|
loaded_public.store_b64::<{ N * 2 }, _>(&new_file).unwrap();
|
||||||
|
|
||||||
|
// Read the contents of the new file
|
||||||
|
let new_file_contents = fs::read(&new_file).unwrap();
|
||||||
|
// Read the contents of the original file
|
||||||
|
let original_file_contents = fs::read(&example_file).unwrap();
|
||||||
|
// Check that the contents of the new file match the original file
|
||||||
|
assert_eq!(new_file_contents, original_file_contents);
|
||||||
|
|
||||||
|
//Check new file permissions are public
|
||||||
|
let metadata = fs::metadata(&new_file).unwrap();
|
||||||
|
assert_eq!(metadata.permissions().mode() & 0o000777, 0o644);
|
||||||
|
|
||||||
|
// Store the loaded public to a different file in the temporary directory for a second time
|
||||||
|
let new_file = temp_dir.path().join("new_file_writer");
|
||||||
|
let new_file_writer = fopen_w(new_file.clone(), Visibility::Public).unwrap();
|
||||||
|
loaded_public
|
||||||
|
.store_b64_writer::<{ N * 2 }, _>(&new_file_writer)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Read the contents of the new file
|
||||||
|
let new_file_contents = fs::read(&new_file).unwrap();
|
||||||
|
// Read the contents of the original file
|
||||||
|
let original_file_contents = fs::read(&example_file).unwrap();
|
||||||
|
// Check that the contents of the new file match the original file
|
||||||
|
assert_eq!(new_file_contents, original_file_contents);
|
||||||
|
|
||||||
|
//Check new file permissions are public
|
||||||
|
let metadata = fs::metadata(&new_file).unwrap();
|
||||||
|
assert_eq!(metadata.permissions().mode() & 0o000777, 0o644);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ use anyhow::Context;
|
|||||||
use rand::{Fill as Randomize, Rng};
|
use rand::{Fill as Randomize, Rng};
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
use rosenpass_util::b64::b64_reader;
|
use rosenpass_util::b64::{b64_decode, b64_encode};
|
||||||
use rosenpass_util::file::{fopen_r, LoadValue, LoadValueB64, ReadExactToEnd};
|
use rosenpass_util::file::{
|
||||||
|
fopen_r, LoadValue, LoadValueB64, ReadExactToEnd, ReadSliceToEnd, StoreValueB64,
|
||||||
|
StoreValueB64Writer,
|
||||||
|
};
|
||||||
use rosenpass_util::functional::mutating;
|
use rosenpass_util::functional::mutating;
|
||||||
|
|
||||||
use crate::alloc::{secret_box, SecretBox, SecretVec};
|
use crate::alloc::{secret_box, SecretBox, SecretVec};
|
||||||
@@ -251,25 +254,57 @@ impl<const N: usize> LoadValue for Secret<N> {
|
|||||||
impl<const N: usize> LoadValueB64 for Secret<N> {
|
impl<const N: usize> LoadValueB64 for Secret<N> {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
fn load_b64<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||||
use std::io::Read;
|
let mut f: Secret<F> = Secret::random();
|
||||||
|
|
||||||
let mut v = Self::random();
|
let mut v = Self::random();
|
||||||
let p = path.as_ref();
|
let p = path.as_ref();
|
||||||
// This might leave some fragments of the secret on the stack;
|
|
||||||
// in practice this is likely not a problem because the stack likely
|
let len = fopen_r(p)?
|
||||||
// will be overwritten by something else soon but this is not exactly
|
.read_slice_to_end(f.secret_mut())
|
||||||
// guaranteed. It would be possible to remedy this, but since the secret
|
.with_context(|| format!("Could not load file {p:?}"))?;
|
||||||
// data will linger in the Linux page cache anyways with the current
|
|
||||||
// implementation, going to great length to erase the secret here is
|
b64_decode(&f.secret()[0..len], v.secret_mut())
|
||||||
// not worth it right now.
|
.with_context(|| format!("Could not decode base64 file {p:?}"))?;
|
||||||
b64_reader(&mut fopen_r(p)?)
|
|
||||||
.read_exact(v.secret_mut())
|
|
||||||
.with_context(|| format!("Could not load base64 file {p:?}"))?;
|
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> StoreValueB64 for Secret<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
|
let p = path.as_ref();
|
||||||
|
|
||||||
|
let mut f: Secret<F> = Secret::random();
|
||||||
|
let encoded_str = b64_encode(self.secret(), f.secret_mut())
|
||||||
|
.with_context(|| format!("Could not encode base64 file {p:?}"))?;
|
||||||
|
|
||||||
|
fopen_w(p, Visibility::Secret)?
|
||||||
|
.write_all(encoded_str.as_bytes())
|
||||||
|
.with_context(|| format!("Could not write file {p:?}"))?;
|
||||||
|
f.zeroize();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> StoreValueB64Writer for Secret<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn store_b64_writer<const F: usize, W: Write>(&self, mut writer: W) -> anyhow::Result<()> {
|
||||||
|
let mut f: Secret<F> = Secret::random();
|
||||||
|
let encoded_str = b64_encode(self.secret(), f.secret_mut())
|
||||||
|
.with_context(|| format!("Could not encode secret to base64"))?;
|
||||||
|
|
||||||
|
writer
|
||||||
|
.write_all(encoded_str.as_bytes())
|
||||||
|
.with_context(|| format!("Could not write base64 to writer"))?;
|
||||||
|
f.zeroize();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<const N: usize> StoreSecret for Secret<N> {
|
impl<const N: usize> StoreSecret for Secret<N> {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
@@ -287,6 +322,8 @@ impl<const N: usize> StoreSecret for Secret<N> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::{fs, os::unix::fs::PermissionsExt};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
/// check that we can alloc using the magic pool
|
/// check that we can alloc using the magic pool
|
||||||
#[test]
|
#[test]
|
||||||
@@ -297,7 +334,7 @@ mod test {
|
|||||||
assert_eq!(secret.as_ref(), &[0; N]);
|
assert_eq!(secret.as_ref(), &[0; N]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// check that a secrete lives, even if its [SecretMemoryPool] is deleted
|
/// check that a secret lives, even if its [SecretMemoryPool] is deleted
|
||||||
#[test]
|
#[test]
|
||||||
fn secret_memory_pool_drop() {
|
fn secret_memory_pool_drop() {
|
||||||
const N: usize = 0x100;
|
const N: usize = 0x100;
|
||||||
@@ -307,7 +344,7 @@ mod test {
|
|||||||
assert_eq!(secret.as_ref(), &[0; N]);
|
assert_eq!(secret.as_ref(), &[0; N]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// check that a secrete can be reborn, freshly initialized with zero
|
/// check that a secret can be reborn, freshly initialized with zero
|
||||||
#[test]
|
#[test]
|
||||||
fn secret_memory_pool_release() {
|
fn secret_memory_pool_release() {
|
||||||
const N: usize = 1;
|
const N: usize = 1;
|
||||||
@@ -325,4 +362,92 @@ mod test {
|
|||||||
// and that the secret was zeroized
|
// and that the secret was zeroized
|
||||||
assert_eq!(new_secret.as_ref(), &[0; N]);
|
assert_eq!(new_secret.as_ref(), &[0; N]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// test loading a secret from an example file, and then storing it again in a different file
|
||||||
|
#[test]
|
||||||
|
fn test_secret_load_store() {
|
||||||
|
const N: usize = 100;
|
||||||
|
|
||||||
|
// Generate original random bytes
|
||||||
|
let original_bytes: [u8; N] = [rand::random(); N];
|
||||||
|
|
||||||
|
// Create a temporary directory
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
|
||||||
|
// Store the original secret to an example file in the temporary directory
|
||||||
|
let example_file = temp_dir.path().join("example_file");
|
||||||
|
std::fs::write(example_file.clone(), &original_bytes).unwrap();
|
||||||
|
|
||||||
|
// Load the secret from the example file
|
||||||
|
let loaded_secret = Secret::load(&example_file).unwrap();
|
||||||
|
|
||||||
|
// Check that the loaded secret matches the original bytes
|
||||||
|
assert_eq!(loaded_secret.secret(), &original_bytes);
|
||||||
|
|
||||||
|
// Store the loaded secret to a different file in the temporary directory
|
||||||
|
let new_file = temp_dir.path().join("new_file");
|
||||||
|
loaded_secret.store(&new_file).unwrap();
|
||||||
|
|
||||||
|
// Read the contents of the new file
|
||||||
|
let new_file_contents = fs::read(&new_file).unwrap();
|
||||||
|
|
||||||
|
// Read the contents of the original file
|
||||||
|
let original_file_contents = fs::read(&example_file).unwrap();
|
||||||
|
|
||||||
|
// Check that the contents of the new file match the original file
|
||||||
|
assert_eq!(new_file_contents, original_file_contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// test loading a base64 encoded secret from an example file, and then storing it again in a different file
|
||||||
|
#[test]
|
||||||
|
fn test_secret_load_store_base64() {
|
||||||
|
const N: usize = 100;
|
||||||
|
// Generate original random bytes
|
||||||
|
let original_bytes: [u8; N] = [rand::random(); N];
|
||||||
|
// Create a temporary directory
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let example_file = temp_dir.path().join("example_file");
|
||||||
|
let mut encoded_secret = [0u8; N * 2];
|
||||||
|
let encoded_secret = b64_encode(&original_bytes, &mut encoded_secret).unwrap();
|
||||||
|
|
||||||
|
std::fs::write(&example_file, encoded_secret).unwrap();
|
||||||
|
|
||||||
|
// Load the secret from the example file
|
||||||
|
let loaded_secret = Secret::load_b64::<{ N * 2 }, _>(&example_file).unwrap();
|
||||||
|
// Check that the loaded secret matches the original bytes
|
||||||
|
assert_eq!(loaded_secret.secret(), &original_bytes);
|
||||||
|
|
||||||
|
// Store the loaded secret to a different file in the temporary directory
|
||||||
|
let new_file = temp_dir.path().join("new_file");
|
||||||
|
loaded_secret.store_b64::<{ N * 2 }, _>(&new_file).unwrap();
|
||||||
|
|
||||||
|
// Read the contents of the new file
|
||||||
|
let new_file_contents = fs::read(&new_file).unwrap();
|
||||||
|
// Read the contents of the original file
|
||||||
|
let original_file_contents = fs::read(&example_file).unwrap();
|
||||||
|
// Check that the contents of the new file match the original file
|
||||||
|
assert_eq!(new_file_contents, original_file_contents);
|
||||||
|
|
||||||
|
//Check new file permissions are secret
|
||||||
|
let metadata = fs::metadata(&new_file).unwrap();
|
||||||
|
assert_eq!(metadata.permissions().mode() & 0o000777, 0o600);
|
||||||
|
|
||||||
|
// Store the loaded secret to a different file in the temporary directory for a second time
|
||||||
|
let new_file = temp_dir.path().join("new_file_writer");
|
||||||
|
let new_file_writer = fopen_w(new_file.clone(), Visibility::Secret).unwrap();
|
||||||
|
loaded_secret
|
||||||
|
.store_b64_writer::<{ N * 2 }, _>(&new_file_writer)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Read the contents of the new file
|
||||||
|
let new_file_contents = fs::read(&new_file).unwrap();
|
||||||
|
// Read the contents of the original file
|
||||||
|
let original_file_contents = fs::read(&example_file).unwrap();
|
||||||
|
// Check that the contents of the new file match the original file
|
||||||
|
assert_eq!(new_file_contents, original_file_contents);
|
||||||
|
|
||||||
|
//Check new file permissions are secret
|
||||||
|
let metadata = fs::metadata(&new_file).unwrap();
|
||||||
|
assert_eq!(metadata.permissions().mode() & 0o000777, 0o600);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ readme = "readme.md"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = { workspace = true }
|
base64ct = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
typenum = { workspace = true }
|
typenum = { workspace = true }
|
||||||
static_assertions = { workspace = true }
|
static_assertions = { workspace = true }
|
||||||
|
zeroize = {workspace = true}
|
||||||
136
util/src/b64.rs
136
util/src/b64.rs
@@ -1,20 +1,130 @@
|
|||||||
use base64::{
|
use base64ct::{Base64, Decoder as B64Reader, Encoder as B64Writer};
|
||||||
display::Base64Display as B64Display, read::DecoderReader as B64Reader,
|
use zeroize::Zeroize;
|
||||||
write::EncoderWriter as B64Writer,
|
|
||||||
};
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
|
|
||||||
use base64::engine::general_purpose::GeneralPurpose as Base64Engine;
|
use std::fmt::Display;
|
||||||
const B64ENGINE: Base64Engine = base64::engine::general_purpose::STANDARD;
|
|
||||||
|
|
||||||
pub fn fmt_b64<'a>(payload: &'a [u8]) -> B64Display<'a, 'static, Base64Engine> {
|
pub struct B64DisplayHelper<'a, const F: usize>(&'a [u8]);
|
||||||
B64Display::<'a, 'static>::new(payload, &B64ENGINE)
|
|
||||||
|
impl<const F: usize> Display for B64DisplayHelper<'_, F> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut bytes = [0u8; F];
|
||||||
|
let string = b64_encode(&self.0, &mut bytes).map_err(|_| std::fmt::Error)?;
|
||||||
|
let result = f.write_str(string);
|
||||||
|
bytes.zeroize();
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn b64_writer<W: Write>(w: W) -> B64Writer<'static, Base64Engine, W> {
|
pub trait B64Display {
|
||||||
B64Writer::new(w, &B64ENGINE)
|
fn fmt_b64<'o, const F: usize>(&'o self) -> B64DisplayHelper<'o, F>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn b64_reader<R: Read>(r: R) -> B64Reader<'static, Base64Engine, R> {
|
impl B64Display for [u8] {
|
||||||
B64Reader::new(r, &B64ENGINE)
|
fn fmt_b64<'o, const F: usize>(&'o self) -> B64DisplayHelper<'o, F> {
|
||||||
|
B64DisplayHelper(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]>> B64Display for T {
|
||||||
|
fn fmt_b64<'o, const F: usize>(&'o self) -> B64DisplayHelper<'o, F> {
|
||||||
|
B64DisplayHelper(self.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn b64_decode(input: &[u8], output: &mut [u8]) -> anyhow::Result<()> {
|
||||||
|
let mut reader = B64Reader::<Base64>::new(input).map_err(|e| anyhow::anyhow!(e))?;
|
||||||
|
match reader.decode(output) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(base64ct::Error::InvalidLength) => (),
|
||||||
|
Err(e) => {
|
||||||
|
return Err(anyhow::anyhow!(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reader.is_finished() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Input not decoded completely (buffer size too small?)"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn b64_encode<'o>(input: &[u8], output: &'o mut [u8]) -> anyhow::Result<&'o str> {
|
||||||
|
let mut writer = B64Writer::<Base64>::new(output).map_err(|e| anyhow::anyhow!(e))?;
|
||||||
|
writer.encode(input).map_err(|e| anyhow::anyhow!(e))?;
|
||||||
|
writer.finish().map_err(|e| anyhow::anyhow!(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_b64_encode() {
|
||||||
|
let input = b"Hello, World!";
|
||||||
|
let mut output = [0u8; 20];
|
||||||
|
let result = b64_encode(input, &mut output);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(result.unwrap(), "SGVsbG8sIFdvcmxkIQ==");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_b64_encode_small_buffer() {
|
||||||
|
let input = b"Hello, World!";
|
||||||
|
let mut output = [0u8; 10]; // Small output buffer
|
||||||
|
let result = b64_encode(input, &mut output);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err().to_string(), "invalid Base64 length");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_b64_encode_empty_buffer() {
|
||||||
|
let input = b"";
|
||||||
|
let mut output = [0u8; 16];
|
||||||
|
let result = b64_encode(input, &mut output);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(result.unwrap(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_b64_decode() {
|
||||||
|
let input = b"SGVsbG8sIFdvcmxkIQ==";
|
||||||
|
let mut output = [0u8; 1000];
|
||||||
|
b64_decode(input, &mut output).unwrap();
|
||||||
|
assert_eq!(&output[..13], b"Hello, World!");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_b64_decode_small_buffer() {
|
||||||
|
let input = b"SGVsbG8sIFdvcmxkIQ==";
|
||||||
|
let mut output = [0u8; 10]; // Small output buffer
|
||||||
|
let result = b64_decode(input, &mut output);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
"Input not decoded completely (buffer size too small?)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_b64_decode_empty_buffer() {
|
||||||
|
let input = b"";
|
||||||
|
let mut output = [0u8; 16];
|
||||||
|
let result = b64_decode(input, &mut output);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fmt_b64() {
|
||||||
|
let input = b"Hello, World!";
|
||||||
|
let result = input.fmt_b64::<20>().to_string();
|
||||||
|
assert_eq!(result, "SGVsbG8sIFdvcmxkIQ==");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fmt_b64_empty_input() {
|
||||||
|
let input = b"";
|
||||||
|
let result = input.fmt_b64::<16>().to_string();
|
||||||
|
assert_eq!(result, "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,30 @@ pub fn fopen_r<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
|
|||||||
.open(path)
|
.open(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ReadSliceToEnd {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn read_slice_to_end(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> ReadSliceToEnd for R {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn read_slice_to_end(&mut self, buf: &mut [u8]) -> anyhow::Result<usize> {
|
||||||
|
let mut dummy = [0u8; 8];
|
||||||
|
let mut read = 0;
|
||||||
|
while read < buf.len() {
|
||||||
|
let bytes_read = self.read(&mut buf[read..])?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
read += bytes_read;
|
||||||
|
}
|
||||||
|
ensure!(self.read(&mut dummy)? == 0, "File too long!");
|
||||||
|
Ok(read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ReadExactToEnd {
|
pub trait ReadExactToEnd {
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
@@ -58,13 +82,36 @@ pub trait LoadValue {
|
|||||||
pub trait LoadValueB64 {
|
pub trait LoadValueB64 {
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait StoreValueB64 {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait StoreValueB64Writer {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||||
|
&self,
|
||||||
|
writer: W,
|
||||||
|
) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait StoreValue {
|
pub trait StoreValue {
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait DisplayValueB64 {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn display_b64<'o>(&self, output: &'o mut [u8]) -> Result<&'o str, Self::Error>;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user