Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use super::*;

pub(crate) fn current_dir() -> Result<Utf8PathBuf> {
Utf8PathBuf::from_path_buf(env::current_dir().context(error::CurrentDir)?)
.map_err(|path| error::PathUnicode { path }.build())
}

pub(crate) fn decode_path(path: &Path) -> Result<&Utf8Path> {
Utf8Path::from_path(path).context(error::PathUnicode { path })
}

pub(crate) fn is_lowercase_hex(s: &str) -> bool {
s.chars()
.all(|c| c.is_ascii_hexdigit() && (c.is_numeric() || c.is_lowercase()))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn lowercase_hex() {
assert!(is_lowercase_hex("0123456789abcdef"));
assert!(!is_lowercase_hex("0123456789ABCDEF"));
assert!(!is_lowercase_hex("xyz"));
}
}
42 changes: 26 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,31 @@

use {
self::{
arguments::Arguments, component::Component, count::Count, display_path::DisplayPath,
display_secret::DisplaySecret, entries::Entries, fingerprint_hasher::FingerprintHasher,
fingerprint_prefix::FingerprintPrefix, key_identifier::KeyIdentifier, key_name::KeyName,
lint::Lint, lint_group::LintGroup, message::Message, metadata::Metadata, mode::Mode,
options::Options, owo_colorize_ext::OwoColorizeExt, path_error::PathError,
public_key_error::PublicKeyError, signature_error::SignatureError, style::Style,
subcommand::Subcommand, template::Template, utf8_path_ext::Utf8PathExt,
arguments::Arguments,
component::Component,
count::Count,
display_path::DisplayPath,
display_secret::DisplaySecret,
entries::Entries,
fingerprint_hasher::FingerprintHasher,
fingerprint_prefix::FingerprintPrefix,
functions::{current_dir, decode_path, is_lowercase_hex},
key_identifier::KeyIdentifier,
key_name::KeyName,
lint::Lint,
lint_group::LintGroup,
message::Message,
metadata::Metadata,
mode::Mode,
options::Options,
owo_colorize_ext::OwoColorizeExt,
path_error::PathError,
public_key_error::PublicKeyError,
signature_error::SignatureError,
style::Style,
subcommand::Subcommand,
template::Template,
utf8_path_ext::Utf8PathExt,
},
blake3::Hasher,
camino::{Utf8Component, Utf8Path, Utf8PathBuf},
Expand Down Expand Up @@ -87,6 +105,7 @@ mod file;
mod filesystem;
mod fingerprint_hasher;
mod fingerprint_prefix;
mod functions;
mod hash;
mod key_identifier;
mod key_name;
Expand Down Expand Up @@ -116,15 +135,6 @@ const SEPARATORS: [char; 2] = ['/', '\\'];

type Result<T = (), E = Error> = std::result::Result<T, E>;

fn current_dir() -> Result<Utf8PathBuf> {
Utf8PathBuf::from_path_buf(env::current_dir().context(error::CurrentDir)?)
.map_err(|path| error::PathUnicode { path }.build())
}

fn decode_path(path: &Path) -> Result<&Utf8Path> {
Utf8Path::from_path(path).context(error::PathUnicode { path })
}

pub fn run() {
if let Err(err) = Arguments::parse().run() {
let style = Style::stderr();
Expand Down
20 changes: 20 additions & 0 deletions src/private_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use super::*;
#[derive(Debug, Snafu)]
#[snafu(context(suffix(Error)))]
pub enum Error {
#[snafu(display("private keys must be lowercase hex"))]
Case,
#[snafu(display("invalid private key hex"))]
Hex { source: hex::FromHexError },
#[snafu(display("invalid private key byte length {length}"))]
Expand Down Expand Up @@ -74,6 +76,10 @@ impl FromStr for PrivateKey {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(s).context(HexError)?;

if !is_lowercase_hex(s) {
return Err(CaseError.build());
}

let secret: [u8; Self::LEN] = bytes.as_slice().try_into().ok().context(LengthError {
length: bytes.len(),
})?;
Expand Down Expand Up @@ -140,6 +146,20 @@ mod tests {
key.parse::<PublicKey>().unwrap_err();
}

#[test]
fn uppercase_is_forbidden() {
let key = "0e56ae8b43aa93fd4c179ceaff96f729522622d26b4b5357bc959e476e59e107";
key.parse::<PrivateKey>().unwrap();
assert_eq!(
key
.to_uppercase()
.parse::<PrivateKey>()
.unwrap_err()
.to_string(),
"private keys must be lowercase hex",
);
}

#[test]
fn whitespace_is_not_trimmed_when_parsing_from_string() {
"0e56ae8b43aa93fd4c179ceaff96f729522622d26b4b5357bc959e476e59e107"
Expand Down
19 changes: 19 additions & 0 deletions src/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ impl FromStr for PublicKey {
fn from_str(key: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(key).context(public_key_error::HexError { key })?;

if !is_lowercase_hex(key) {
return Err(public_key_error::CaseError { key }.build());
}

let array: [u8; Self::LEN] =
bytes
.as_slice()
Expand Down Expand Up @@ -124,6 +128,21 @@ mod tests {
);
}

#[test]
fn uppercase_is_forbidden() {
let key = "0f6d444f09eb336d3cc94d66cc541fea0b70b36be291eb3ecf5b49113f34c8d3";
key.parse::<PublicKey>().unwrap();
assert_eq!(
key
.to_uppercase()
.parse::<PublicKey>()
.unwrap_err()
.to_string(),
"public keys must be lowercase hex: \
`0F6D444F09EB336D3CC94D66CC541FEA0B70B36BE291EB3ECF5B49113F34C8D3`"
);
}

#[test]
fn weak_public_keys_are_forbidden() {
assert!(matches!(
Expand Down
2 changes: 2 additions & 0 deletions src/public_key_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use super::*;
#[derive(Debug, Snafu)]
#[snafu(context(suffix(Error)), visibility(pub(crate)))]
pub enum PublicKeyError {
#[snafu(display("public keys must be lowercase hex: `{key}`"))]
Case { key: String },
#[snafu(display("invalid public key hex: `{key}`"))]
Hex {
key: String,
Expand Down
26 changes: 24 additions & 2 deletions src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use super::*;
#[derive(Debug, Snafu)]
#[snafu(context(suffix(Error)))]
pub enum Error {
#[snafu(display("signatures must be lowercase hex: `{signature}`"))]
Case { signature: String },
#[snafu(display("invalid signature hex: `{signature}`"))]
Hex {
signature: String,
Expand Down Expand Up @@ -56,6 +58,10 @@ impl FromStr for Signature {
fn from_str(signature: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(signature).context(HexError { signature })?;

if !is_lowercase_hex(signature) {
return Err(CaseError { signature }.build());
}

let array: [u8; Self::LEN] = bytes.as_slice().try_into().context(LengthError {
signature,
length: bytes.len(),
Expand All @@ -72,8 +78,7 @@ mod tests {
#[test]
fn display_is_lowercase_hex() {
let s = "0f6d444f09eb336d3cc94d66cc541fea0b70b36be291eb3ecf5b49113f34c8d3\
0f6d444f09eb336d3cc94d66cc541fea0b70b36be291eb3ecf5b49113f34c8d3";

0f6d444f09eb336d3cc94d66cc541fea0b70b36be291eb3ecf5b49113f34c8d3";
assert_eq!(s.parse::<Signature>().unwrap().to_string(), s);
}

Expand All @@ -98,4 +103,21 @@ mod tests {
signature
);
}

#[test]
fn uppercase_is_forbidden() {
let signature = "0f6d444f09eb336d3cc94d66cc541fea0b70b36be291eb3ecf5b49113f34c8d3\
0f6d444f09eb336d3cc94d66cc541fea0b70b36be291eb3ecf5b49113f34c8d3";
signature.parse::<Signature>().unwrap();
assert_eq!(
signature
.to_uppercase()
.parse::<Signature>()
.unwrap_err()
.to_string(),
"signatures must be lowercase hex: \
`0F6D444F09EB336D3CC94D66CC541FEA0B70B36BE291EB3ECF5B49113F34C8D3\
0F6D444F09EB336D3CC94D66CC541FEA0B70B36BE291EB3ECF5B49113F34C8D3`",
);
}
}