phase 6
This commit is contained in:
Generated
+220
@@ -160,6 +160,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
@@ -367,6 +373,18 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -396,6 +414,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"digest",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
@@ -453,9 +472,65 @@ dependencies = [
|
||||
"age",
|
||||
"clap",
|
||||
"flate2",
|
||||
"rand",
|
||||
"ssh-key",
|
||||
"tar",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.16.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
|
||||
dependencies = [
|
||||
"der",
|
||||
"digest",
|
||||
"elliptic-curve",
|
||||
"rfc6979",
|
||||
"signature",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"sha2",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.13.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"digest",
|
||||
"ff",
|
||||
"generic-array",
|
||||
"group",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"sec1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
@@ -466,6 +541,16 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
@@ -641,6 +726,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -664,6 +750,17 @@ dependencies = [
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
@@ -927,6 +1024,44 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p384"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p521"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"rand_core",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -960,6 +1095,15 @@ dependencies = [
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.12"
|
||||
@@ -1039,6 +1183,15 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primeorder"
|
||||
version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
@@ -1118,6 +1271,16 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.10"
|
||||
@@ -1132,6 +1295,7 @@ dependencies = [
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"sha2",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
@@ -1247,6 +1411,20 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"der",
|
||||
"generic-array",
|
||||
"pkcs8",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secrecy"
|
||||
version = "0.10.3"
|
||||
@@ -1362,6 +1540,48 @@ dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ssh-cipher"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
"ssh-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ssh-encoding"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"pem-rfc7468",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ssh-key"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3"
|
||||
dependencies = [
|
||||
"ed25519-dalek",
|
||||
"p256",
|
||||
"p384",
|
||||
"p521",
|
||||
"rand_core",
|
||||
"rsa",
|
||||
"sec1",
|
||||
"sha2",
|
||||
"signature",
|
||||
"ssh-cipher",
|
||||
"ssh-encoding",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
|
||||
@@ -7,4 +7,6 @@ edition = "2024"
|
||||
age = { version = "0.11.3", features = ["ssh"] }
|
||||
clap = { version = "4.6.1", features = ["derive"] }
|
||||
flate2 = "1.1.9"
|
||||
rand = "0.8"
|
||||
ssh-key = { version = "0.6", features = ["ed25519", "getrandom"] }
|
||||
tar = "0.4.45"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Phase 6
|
||||
|
||||
For phase 6, the app is almost finished but I want to include a couple quality of life features:
|
||||
|
||||
- If the user runs `drawer open somedrawer.drawer`, this is currently an error because there is no target path given.
|
||||
If the file basename of the drawer path would be a valid target path (`./somedrawer` in this case) then use that as the
|
||||
target path, but also print to stdout that that's what it's using. This should still not open the drawer if the target
|
||||
path exists and is nonempty, unless the user passes `-f`
|
||||
|
||||
- Add a new command, `drawer key <filename>` which will create a new key and store it in the given filename. Before
|
||||
creating the new key, make sure the filename does not exist and is writable. This will need a good source of entropy to
|
||||
generate a key, so as part of your plan, recommend one.
|
||||
+5
-1
@@ -17,6 +17,8 @@ pub enum DrawerError {
|
||||
EncryptFailed,
|
||||
DecryptFailed(PathBuf, PathBuf),
|
||||
UntarFailed,
|
||||
KeyOutputFailed(PathBuf),
|
||||
KeyGenerateFailed,
|
||||
}
|
||||
|
||||
impl fmt::Display for DrawerError {
|
||||
@@ -24,7 +26,7 @@ impl fmt::Display for DrawerError {
|
||||
match self {
|
||||
DrawerError::ParseError(s) => write!(f, "{s}"),
|
||||
DrawerError::NoKeyPath => write!(f, "no key specified: set $DRAWER_KEY or use -i"),
|
||||
DrawerError::DrawerFileInvalidExtension(p) => write!(f, "drawer file must have a .drawer extension: {}", p.display()),
|
||||
DrawerError::DrawerFileInvalidExtension(p) => write!(f, "drawer file must end in .drawer: {}", p.display()),
|
||||
DrawerError::DrawerFileNotFound(p) => write!(f, "drawer file not found: {}", p.display()),
|
||||
DrawerError::TargetInvalid(p) => write!(f, "target path does not exist, is not a directory, or is not readable: {}", p.display()),
|
||||
DrawerError::KeyInvalid(p) => write!(f, "key not found or not readable: {}", p.display()),
|
||||
@@ -36,6 +38,8 @@ impl fmt::Display for DrawerError {
|
||||
DrawerError::EncryptFailed => write!(f, "encryption failed"),
|
||||
DrawerError::DecryptFailed(drawer, key) => write!(f, "failed to decrypt {} with key {}", drawer.display(), key.display()),
|
||||
DrawerError::UntarFailed => write!(f, "drawer file does not contain a valid archive"),
|
||||
DrawerError::KeyOutputFailed(p) => write!(f, "cannot create key file: {} (already exists or directory is not writable)", p.display()),
|
||||
DrawerError::KeyGenerateFailed => write!(f, "failed to generate key"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
@@ -31,6 +31,7 @@ mod tests {
|
||||
drawer_file: "blah.drawer".into(),
|
||||
key: Path("foo.key".into()),
|
||||
target_path: "blah".into(),
|
||||
target_inferred: false,
|
||||
force: false,
|
||||
}
|
||||
))
|
||||
@@ -45,6 +46,22 @@ mod tests {
|
||||
drawer_file: "blah.drawer".into(),
|
||||
key: Path("foo.key".into()),
|
||||
target_path: "blah".into(),
|
||||
target_inferred: false,
|
||||
force: false,
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inferred_open() {
|
||||
let op = parse_args(vec!["drawer", "open", "blah.drawer", "-i", "foo.key"]);
|
||||
assert_eq!(op, Ok(
|
||||
DrawerOperation {
|
||||
operation_type: Open,
|
||||
drawer_file: "blah.drawer".into(),
|
||||
key: Path("foo.key".into()),
|
||||
target_path: "blah".into(),
|
||||
target_inferred: true,
|
||||
force: false,
|
||||
}
|
||||
))
|
||||
|
||||
+61
-6
@@ -6,6 +6,7 @@ use flate2::Compression;
|
||||
use flate2::read::GzDecoder;
|
||||
use flate2::write::GzEncoder;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ssh_key::{Algorithm, LineEnding, PrivateKey};
|
||||
use crate::error::DrawerError;
|
||||
use crate::parsing::KeyType::*;
|
||||
|
||||
@@ -13,6 +14,7 @@ use crate::parsing::KeyType::*;
|
||||
pub enum OperationType {
|
||||
Open,
|
||||
Close,
|
||||
Key,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -22,6 +24,7 @@ pub struct DrawerOperation {
|
||||
pub key: KeyType,
|
||||
pub target_path: PathBuf,
|
||||
pub force: bool,
|
||||
pub target_inferred: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -52,6 +55,9 @@ enum Command {
|
||||
#[arg(short = 'i')]
|
||||
key: Option<PathBuf>,
|
||||
},
|
||||
Key {
|
||||
filename: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn parse_args<I, T>(args: I) -> Result<DrawerOperation, DrawerError>
|
||||
@@ -65,15 +71,17 @@ where
|
||||
match cli.command {
|
||||
Command::Open { drawer_file, target_path, key, force } => {
|
||||
let key = resolve_key(key);
|
||||
let target = target_path.unwrap_or_else(|| {
|
||||
PathBuf::from(drawer_file.file_stem().unwrap_or_default())
|
||||
});
|
||||
let (target, target_inferred) = match target_path {
|
||||
Some(p) => (p, false),
|
||||
None => (PathBuf::from(drawer_file.file_stem().unwrap_or_default()), true),
|
||||
};
|
||||
Ok(DrawerOperation {
|
||||
operation_type: OperationType::Open,
|
||||
drawer_file,
|
||||
key,
|
||||
target_path: target,
|
||||
force,
|
||||
target_inferred,
|
||||
})
|
||||
}
|
||||
Command::Close { drawer_file, target_path, key } => {
|
||||
@@ -85,6 +93,17 @@ where
|
||||
key,
|
||||
target_path: target,
|
||||
force: false,
|
||||
target_inferred: false,
|
||||
})
|
||||
}
|
||||
Command::Key { filename } => {
|
||||
Ok(DrawerOperation {
|
||||
operation_type: OperationType::Key,
|
||||
drawer_file: filename,
|
||||
key: KeyType::EnvVar,
|
||||
target_path: PathBuf::from("."),
|
||||
force: false,
|
||||
target_inferred: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -103,10 +122,14 @@ impl DrawerOperation {
|
||||
match self.operation_type {
|
||||
OperationType::Open => self.perform_open(),
|
||||
OperationType::Close => self.perform_close(),
|
||||
OperationType::Key => self.perform_key(),
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_open(&self) -> Result<(), DrawerError> {
|
||||
if self.target_inferred {
|
||||
println!("(assuming into {})", self.target_path.display());
|
||||
}
|
||||
let encrypted = std::fs::read(&self.drawer_file)
|
||||
.map_err(|_| DrawerError::DrawerFileNotFound(self.drawer_file.clone()))?;
|
||||
|
||||
@@ -179,16 +202,39 @@ impl DrawerOperation {
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), DrawerError> {
|
||||
if self.drawer_file.extension().and_then(|e| e.to_str()) != Some("drawer") {
|
||||
return Err(DrawerError::DrawerFileInvalidExtension(self.drawer_file.clone()));
|
||||
if self.operation_type != OperationType::Key {
|
||||
if self.drawer_file.extension().and_then(|e| e.to_str()) != Some("drawer") {
|
||||
return Err(DrawerError::DrawerFileInvalidExtension(self.drawer_file.clone()));
|
||||
}
|
||||
self.validate_key()?;
|
||||
}
|
||||
self.validate_key()?;
|
||||
match self.operation_type {
|
||||
OperationType::Open => self.validate_open(),
|
||||
OperationType::Close => self.validate_close(),
|
||||
OperationType::Key => self.validate_key_gen(),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_key_gen(&self) -> Result<(), DrawerError> {
|
||||
if self.drawer_file.exists() {
|
||||
return Err(DrawerError::KeyOutputFailed(self.drawer_file.clone()));
|
||||
}
|
||||
let parent = match self.drawer_file.parent() {
|
||||
None => Path::new("."),
|
||||
Some(p) => {
|
||||
if p == Path::new("") {
|
||||
Path::new(".")
|
||||
} else {
|
||||
p
|
||||
}
|
||||
},
|
||||
};
|
||||
if !parent.exists() {
|
||||
return Err(DrawerError::KeyOutputFailed(self.drawer_file.clone()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key_path(&self) -> Result<PathBuf, DrawerError> {
|
||||
match &self.key {
|
||||
EnvVar => {
|
||||
@@ -225,6 +271,15 @@ impl DrawerOperation {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn perform_key(&self) -> Result<(), DrawerError> {
|
||||
let private_key = PrivateKey::random(&mut rand::rngs::OsRng, Algorithm::Ed25519)
|
||||
.map_err(|_| DrawerError::KeyGenerateFailed)?;
|
||||
let pem = private_key.to_openssh(LineEnding::LF)
|
||||
.map_err(|_| DrawerError::KeyGenerateFailed)?;
|
||||
std::fs::write(&self.drawer_file, pem.as_bytes())
|
||||
.map_err(|_| DrawerError::KeyOutputFailed(self.drawer_file.clone()))
|
||||
}
|
||||
|
||||
fn validate_close(&self) -> Result<(), DrawerError> {
|
||||
let meta = std::fs::metadata(&self.target_path)
|
||||
.map_err(|_| DrawerError::TargetInvalid(self.target_path.clone()))?;
|
||||
|
||||
Reference in New Issue
Block a user