mirror of
https://github.com/ssh-vault/ssh-vault.git
synced 2025-07-31 05:24:22 +03:00
1.1.0
This commit is contained in:
14
.justfile
Normal file
14
.justfile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
test: clippy fmt
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
cargo clippy --all -- -W clippy::all -W clippy::nursery -D warnings
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage-%p-%m.profraw' cargo test
|
||||||
|
grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
|
||||||
|
firefox target/coverage/html/index.html
|
||||||
|
rm -rf *.profraw
|
@ -1,3 +1,9 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
* using `rsa::RsaPrivatekey::from_components` to create the private key from `ssh_key::PrivateKey::read_openssh_file`
|
||||||
|
* edition 2024
|
||||||
|
|
||||||
## 1.0.13
|
## 1.0.13
|
||||||
* bump versions, cargo update
|
* bump versions, cargo update
|
||||||
|
|
||||||
|
872
Cargo.lock
generated
872
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ssh-vault"
|
name = "ssh-vault"
|
||||||
version = "1.0.14"
|
version = "1.1.0"
|
||||||
authors = ["Nicolas Embriz <nbari@tequila.io>"]
|
authors = ["Nicolas Embriz <nbari@tequila.io>"]
|
||||||
description = "encrypt/decrypt using ssh keys"
|
description = "encrypt/decrypt using ssh keys"
|
||||||
documentation = "https://ssh-vault.com/"
|
documentation = "https://ssh-vault.com/"
|
||||||
@ -10,27 +10,27 @@ readme = "README.md"
|
|||||||
keywords = ["ssh", "encryption", "fingerprint"]
|
keywords = ["ssh", "encryption", "fingerprint"]
|
||||||
categories = ["command-line-utilities", "cryptography"]
|
categories = ["command-line-utilities", "cryptography"]
|
||||||
license = "BSD-3-Clause"
|
license = "BSD-3-Clause"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes-gcm = "0.10.3"
|
aes-gcm = "0.10.3"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
base58 = "0.2.0"
|
base58 = "0.2.0"
|
||||||
base64ct = { version = "1.6.0", features = ["alloc"] }
|
base64ct = { version = "1.7.3", features = ["alloc"] }
|
||||||
chacha20poly1305 = "0.10.1"
|
chacha20poly1305 = "0.10.1"
|
||||||
clap = { version = "4.5", features = ["env", "color"] }
|
clap = { version = "4.5", features = ["env", "color"] }
|
||||||
config = { version = "0.14", default-features = false, features = ["yaml"] }
|
config = { version = "0.14", default-features = false, features = ["yaml"] }
|
||||||
ed25519-dalek = { version = "2.1.1", features = ["pkcs8"] }
|
ed25519-dalek = { version = "2.1.1", features = ["pkcs8"] }
|
||||||
hex-literal = "0.4.1"
|
hex-literal = "1.0.0"
|
||||||
hkdf = "0.12.4"
|
hkdf = "0.12.4"
|
||||||
home = "0.5.9"
|
home = "0.5.11"
|
||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
openssl = { version = "0.10", optional = true, features = ["vendored"] }
|
openssl = { version = "0.10", optional = true, features = ["vendored"] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
regex = "1.11"
|
regex = "1.11"
|
||||||
reqwest = { version = "0.12", features = ["blocking"] }
|
reqwest = { version = "0.12", features = ["blocking"] }
|
||||||
rpassword = "7.3"
|
rpassword = "7.3"
|
||||||
rsa = { version = "0.9.6", features = ["sha2"] }
|
rsa = { version = "0.9.8", features = ["sha2"] }
|
||||||
secrecy = "0.10.3"
|
secrecy = "0.10.3"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
@ -38,7 +38,7 @@ sha2 = "0.10.8"
|
|||||||
shell-words = "1.1.0"
|
shell-words = "1.1.0"
|
||||||
ssh-key = { version = "0.6.7", features = ["ed25519", "rsa", "encryption"] }
|
ssh-key = { version = "0.6.7", features = ["ed25519", "rsa", "encryption"] }
|
||||||
temp-env = "0.3.6"
|
temp-env = "0.3.6"
|
||||||
tempfile = "3.13"
|
tempfile = "3.19"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
x25519-dalek = { version = "2.0.1", features = ["getrandom", "static_secrets"] }
|
x25519-dalek = { version = "2.0.1", features = ["getrandom", "static_secrets"] }
|
||||||
zeroize = "1.8.1"
|
zeroize = "1.8.1"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::tools::get_home;
|
use crate::tools::get_home;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::cli::actions::{process_input, Action};
|
use crate::cli::actions::{Action, process_input};
|
||||||
use crate::vault::{crypto, dio, find, online, remote, SshVault};
|
use crate::vault::{SshVault, crypto, dio, find, online, remote};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use secrecy::SecretSlice;
|
use secrecy::SecretSlice;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ssh_key::PublicKey;
|
use ssh_key::PublicKey;
|
||||||
@ -60,7 +60,7 @@ pub fn handle(action: Action) -> Result<()> {
|
|||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
// check if we need to skip the editor filename == "-"
|
// check if we need to skip the editor filename == "-"
|
||||||
let skip_editor = input.as_ref().map_or(false, |stdin| stdin == "-");
|
let skip_editor = input.as_ref().is_some_and(|stdin| stdin == "-");
|
||||||
|
|
||||||
// setup Reader(input) and Writer (output)
|
// setup Reader(input) and Writer (output)
|
||||||
let (mut input, output) = dio::setup_io(input, vault)?;
|
let (mut input, output) = dio::setup_io(input, vault)?;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::cli::actions::{process_input, Action};
|
use crate::cli::actions::{Action, process_input};
|
||||||
use crate::vault::{crypto, dio, find, parse, ssh::decrypt_private_key, SshVault};
|
use crate::vault::{SshVault, crypto, dio, find, parse, ssh::decrypt_private_key};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use secrecy::{SecretSlice, SecretString};
|
use secrecy::{SecretSlice, SecretString};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
@ -4,7 +4,7 @@ pub mod fingerprint;
|
|||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
use crate::tools;
|
use crate::tools;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use secrecy::{ExposeSecret, SecretString};
|
use secrecy::{ExposeSecret, SecretString};
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
@ -79,7 +79,7 @@ pub fn process_input(buf: &mut Vec<u8>, data: Option<SecretString>) -> Result<us
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::cli::actions::{create, edit, fingerprint, view, Action};
|
use crate::cli::actions::{Action, create, edit, fingerprint, view};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
@ -93,24 +93,24 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_view_edit_with_input() {
|
fn test_create_view_edit_with_input() {
|
||||||
let tests =[
|
let tests = [
|
||||||
Test {
|
Test {
|
||||||
input: "Machs na",
|
input: "Machs na",
|
||||||
public_key: "test_data/ed25519.pub",
|
public_key: "test_data/ed25519.pub",
|
||||||
private_key: "test_data/ed25519",
|
private_key: "test_data/ed25519",
|
||||||
header: "SSH-VAULT;CHACHA20-POLY1305"
|
header: "SSH-VAULT;CHACHA20-POLY1305",
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
input: "Machs na",
|
input: "Machs na",
|
||||||
public_key: "test_data/id_rsa.pub",
|
public_key: "test_data/id_rsa.pub",
|
||||||
private_key: "test_data/id_rsa",
|
private_key: "test_data/id_rsa",
|
||||||
header: "SSH-VAULT;AES256"
|
header: "SSH-VAULT;AES256",
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
input: "Arrachera is a Mexican dish made from marinated and grilled skirt steak. The steak is seasoned with a mixture of spices and marinades, giving it a rich and savory flavor. Commonly served in tacos or fajitas, arrachera is known for its tenderness and versatility in Mexican cuisine",
|
input: "Arrachera is a Mexican dish made from marinated and grilled skirt steak. The steak is seasoned with a mixture of spices and marinades, giving it a rich and savory flavor. Commonly served in tacos or fajitas, arrachera is known for its tenderness and versatility in Mexican cuisine",
|
||||||
public_key: "test_data/ed25519.pub",
|
public_key: "test_data/ed25519.pub",
|
||||||
private_key: "test_data/ed25519",
|
private_key: "test_data/ed25519",
|
||||||
header: "SSH-VAULT;CHACHA20-POLY1305"
|
header: "SSH-VAULT;CHACHA20-POLY1305",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::cli::actions::Action;
|
use crate::cli::actions::Action;
|
||||||
use crate::vault::{dio, find, parse, ssh::decrypt_private_key, SshVault};
|
use crate::vault::{SshVault, dio, find, parse, ssh::decrypt_private_key};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use clap::{builder::ValueParser, Arg, Command};
|
use clap::{Arg, Command, builder::ValueParser};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
const REGEX_MD5_FINGERPRINT: &str = r"^([0-9a-f]{2}:){15}([0-9a-f]{2})$";
|
const REGEX_MD5_FINGERPRINT: &str = r"^([0-9a-f]{2}:){15}([0-9a-f]{2})$";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use clap::{builder::ValueParser, Arg, Command};
|
use clap::{Arg, Command, builder::ValueParser};
|
||||||
|
|
||||||
pub fn validator_user() -> ValueParser {
|
pub fn validator_user() -> ValueParser {
|
||||||
ValueParser::from(move |s: &str| -> std::result::Result<String, String> {
|
ValueParser::from(move |s: &str| -> std::result::Result<String, String> {
|
||||||
|
@ -4,8 +4,8 @@ pub mod fingerprint;
|
|||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
use clap::{
|
use clap::{
|
||||||
builder::styling::{AnsiColor, Effects, Styles},
|
|
||||||
ColorChoice, Command,
|
ColorChoice, Command,
|
||||||
|
builder::styling::{AnsiColor, Effects, Styles},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub fn get_home() -> Result<PathBuf> {
|
pub fn get_home() -> Result<PathBuf> {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use aes_gcm::{
|
use aes_gcm::{
|
||||||
aead::{generic_array::GenericArray, Aead, AeadCore, KeyInit, OsRng, Payload},
|
|
||||||
Aes256Gcm,
|
Aes256Gcm,
|
||||||
|
aead::{Aead, AeadCore, KeyInit, OsRng, Payload, generic_array::GenericArray},
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use secrecy::{ExposeSecret, SecretSlice};
|
use secrecy::{ExposeSecret, SecretSlice};
|
||||||
|
|
||||||
pub struct Aes256Crypto {
|
pub struct Aes256Crypto {
|
||||||
@ -55,7 +55,7 @@ impl super::Crypto for Aes256Crypto {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::vault::crypto::Crypto;
|
use crate::vault::crypto::Crypto;
|
||||||
use rand::{rngs::OsRng, RngCore};
|
use rand::{RngCore, rngs::OsRng};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
const TEST_DATA: &str = "The quick brown fox jumps over the lazy dog";
|
const TEST_DATA: &str = "The quick brown fox jumps over the lazy dog";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use chacha20poly1305::{
|
use chacha20poly1305::{
|
||||||
aead::{Aead, AeadCore, KeyInit, OsRng, Payload},
|
|
||||||
ChaCha20Poly1305,
|
ChaCha20Poly1305,
|
||||||
|
aead::{Aead, AeadCore, KeyInit, OsRng, Payload},
|
||||||
};
|
};
|
||||||
use secrecy::{ExposeSecret, SecretSlice};
|
use secrecy::{ExposeSecret, SecretSlice};
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ impl super::Crypto for ChaCha20Poly1305Crypto {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::vault::crypto::Crypto;
|
use crate::vault::crypto::Crypto;
|
||||||
use rand::{rngs::OsRng, RngCore};
|
use rand::{RngCore, rngs::OsRng};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
const TEST_DATA: &str = "The quick brown fox jumps over the lazy dog";
|
const TEST_DATA: &str = "The quick brown fox jumps over the lazy dog";
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
pub mod aes256;
|
pub mod aes256;
|
||||||
pub mod chacha20poly1305;
|
pub mod chacha20poly1305;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use hkdf::Hkdf;
|
use hkdf::Hkdf;
|
||||||
use rand::{rngs::OsRng, RngCore};
|
use rand::{RngCore, rngs::OsRng};
|
||||||
use rsa::sha2;
|
use rsa::sha2;
|
||||||
use secrecy::SecretSlice;
|
use secrecy::SecretSlice;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
tools,
|
tools,
|
||||||
vault::{remote, SshKeyType},
|
vault::{SshKeyType, remote},
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{Context, Result, anyhow};
|
||||||
use ssh_key::{Algorithm, PrivateKey, PublicKey};
|
use ssh_key::{Algorithm, PrivateKey, PublicKey};
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
@ -80,7 +80,9 @@ pub fn private_key(key: Option<String>, ssh_type: &SshKeyType) -> Result<Private
|
|||||||
|
|
||||||
// check if it's a legacy rsa key
|
// check if it's a legacy rsa key
|
||||||
if private_key.starts_with("-----BEGIN RSA PRIVATE KEY-----") {
|
if private_key.starts_with("-----BEGIN RSA PRIVATE KEY-----") {
|
||||||
return Err(anyhow!("Legacy RSA key not supported, use ssh-keygen -p -f <key> to convert it to openssh format"));
|
return Err(anyhow!(
|
||||||
|
"Legacy RSA key not supported, use ssh-keygen -p -f <key> to convert it to openssh format"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// read openssh key and return it as a PrivateKey
|
// read openssh key and return it as a PrivateKey
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::tools;
|
use crate::tools;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use rsa::{pkcs8::EncodePublicKey, RsaPublicKey};
|
use rsa::{RsaPublicKey, pkcs8::EncodePublicKey};
|
||||||
use ssh_key::{HashAlg, PublicKey};
|
use ssh_key::{HashAlg, PublicKey};
|
||||||
use std::{fmt, fs, path::Path};
|
use std::{fmt, fs, path::Path};
|
||||||
|
|
||||||
@ -200,13 +200,12 @@ mod tests {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKdb5/i8sIEZ84k+LpJCAxRwxUZsP2MHFWApeB2TSUux ssh-vault",
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKdb5/i8sIEZ84k+LpJCAxRwxUZsP2MHFWApeB2TSUux ssh-vault",
|
||||||
fingerprint: "SHA256:HcSHlMDnxnmeh6dsxdTrqOGUPp8Ei78VaF9t3ED21S8"
|
fingerprint: "SHA256:HcSHlMDnxnmeh6dsxdTrqOGUPp8Ei78VaF9t3ED21S8",
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINixf2m2nj8TDeazbWuemUY8ZHNg7znA7hVPN8TJLr2W",
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINixf2m2nj8TDeazbWuemUY8ZHNg7znA7hVPN8TJLr2W",
|
||||||
fingerprint: "SHA256:hgIL5fEHz5zuOWY1CDlUuotdaUl4MvYG7vAgE4q4TzM"
|
fingerprint: "SHA256:hgIL5fEHz5zuOWY1CDlUuotdaUl4MvYG7vAgE4q4TzM",
|
||||||
}
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for test in tests.iter() {
|
for test in tests.iter() {
|
||||||
|
@ -61,8 +61,8 @@ pub trait Vault {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::vault::{
|
use crate::vault::{
|
||||||
crypto, parse, ssh::decrypt_private_key, ssh::ed25519::Ed25519Vault, ssh::rsa::RsaVault,
|
Vault, crypto, parse, ssh::decrypt_private_key, ssh::ed25519::Ed25519Vault,
|
||||||
Vault,
|
ssh::rsa::RsaVault,
|
||||||
};
|
};
|
||||||
use secrecy::{SecretSlice, SecretString};
|
use secrecy::{SecretSlice, SecretString};
|
||||||
use ssh_key::PublicKey;
|
use ssh_key::PublicKey;
|
||||||
@ -192,9 +192,11 @@ mod tests {
|
|||||||
private_key =
|
private_key =
|
||||||
decrypt_private_key(&private_key, Some(SecretString::from(test.passphrase)))?;
|
decrypt_private_key(&private_key, Some(SecretString::from(test.passphrase)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let key_type = find::key_type(&private_key.algorithm())?;
|
let key_type = find::key_type(&private_key.algorithm())?;
|
||||||
|
|
||||||
let v = SshVault::new(&key_type, None, Some(private_key))?;
|
let v = SshVault::new(&key_type, None, Some(private_key))?;
|
||||||
|
|
||||||
let vault = v.view(&password, &data, &fingerprint)?;
|
let vault = v.view(&password, &data, &fingerprint)?;
|
||||||
|
|
||||||
assert_eq!(vault, SECRET);
|
assert_eq!(vault, SECRET);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use base64ct::{Base64, Encoding};
|
use base64ct::{Base64, Encoding};
|
||||||
|
|
||||||
// check if it's a valid SSH-VAULT file and return the data
|
// check if it's a valid SSH-VAULT file and return the data
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{cache, config, tools, vault::fingerprint};
|
use crate::{cache, config, tools, vault::fingerprint};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
use rsa::RsaPublicKey;
|
use rsa::RsaPublicKey;
|
||||||
use ssh_key::{HashAlg, PublicKey};
|
use ssh_key::{HashAlg, PublicKey};
|
||||||
@ -135,8 +135,8 @@ pub fn get_user_key(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::vault::fingerprint::get_remote_fingerprints;
|
|
||||||
use crate::vault::fingerprint::Fingerprint;
|
use crate::vault::fingerprint::Fingerprint;
|
||||||
|
use crate::vault::fingerprint::get_remote_fingerprints;
|
||||||
|
|
||||||
const KEYS: &str = "
|
const KEYS: &str = "
|
||||||
# random comment
|
# random comment
|
||||||
@ -176,13 +176,17 @@ Fin
|
|||||||
},
|
},
|
||||||
Fingerprint {
|
Fingerprint {
|
||||||
key: "ID: 3".to_string(),
|
key: "ID: 3".to_string(),
|
||||||
fingerprints: vec!["SHA256:hgIL5fEHz5zuOWY1CDlUuotdaUl4MvYG7vAgE4q4TzM".to_string()],
|
fingerprints: vec![
|
||||||
|
"SHA256:hgIL5fEHz5zuOWY1CDlUuotdaUl4MvYG7vAgE4q4TzM".to_string(),
|
||||||
|
],
|
||||||
comment: "".to_string(),
|
comment: "".to_string(),
|
||||||
algorithm: "ssh-ed25519".to_string(),
|
algorithm: "ssh-ed25519".to_string(),
|
||||||
},
|
},
|
||||||
Fingerprint {
|
Fingerprint {
|
||||||
key: "ID: 4".to_string(),
|
key: "ID: 4".to_string(),
|
||||||
fingerprints: vec!["SHA256:HcSHlMDnxnmeh6dsxdTrqOGUPp8Ei78VaF9t3ED21S8".to_string()],
|
fingerprints: vec![
|
||||||
|
"SHA256:HcSHlMDnxnmeh6dsxdTrqOGUPp8Ei78VaF9t3ED21S8".to_string(),
|
||||||
|
],
|
||||||
comment: "ssh-vault".to_string(),
|
comment: "ssh-vault".to_string(),
|
||||||
algorithm: "ssh-ed25519".to_string(),
|
algorithm: "ssh-ed25519".to_string(),
|
||||||
},
|
},
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
use crate::vault::{
|
use crate::vault::{
|
||||||
crypto, crypto::chacha20poly1305::ChaCha20Poly1305Crypto, crypto::Crypto, Vault,
|
Vault, crypto, crypto::Crypto, crypto::chacha20poly1305::ChaCha20Poly1305Crypto,
|
||||||
};
|
};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use base64ct::{Base64, Encoding};
|
use base64ct::{Base64, Encoding};
|
||||||
use secrecy::{ExposeSecret, SecretSlice};
|
use secrecy::{ExposeSecret, SecretSlice};
|
||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
use ssh_key::{
|
use ssh_key::{
|
||||||
|
HashAlg, PrivateKey, PublicKey,
|
||||||
private::{Ed25519PrivateKey, KeypairData},
|
private::{Ed25519PrivateKey, KeypairData},
|
||||||
public::KeyData,
|
public::KeyData,
|
||||||
HashAlg, PrivateKey, PublicKey,
|
|
||||||
};
|
};
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey as X25519PublicKey, StaticSecret};
|
use x25519_dalek::{EphemeralSecret, PublicKey as X25519PublicKey, StaticSecret};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
use crate::vault::{
|
use crate::vault::{
|
||||||
crypto::aes256::Aes256Crypto, crypto::Crypto, fingerprint::md5_fingerprint, Vault,
|
Vault, crypto::Crypto, crypto::aes256::Aes256Crypto, fingerprint::md5_fingerprint,
|
||||||
};
|
};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use base64ct::{Base64, Encoding};
|
use base64ct::{Base64, Encoding};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rsa::{Oaep, RsaPrivateKey, RsaPublicKey};
|
use rsa::{BigUint, Oaep, RsaPrivateKey, RsaPublicKey};
|
||||||
use secrecy::{ExposeSecret, SecretSlice};
|
use secrecy::{ExposeSecret, SecretSlice};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use ssh_key::{private::KeypairData, public::KeyData, PrivateKey, PublicKey};
|
use ssh_key::{PrivateKey, PublicKey, private::KeypairData, public::KeyData};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct RsaVault {
|
pub struct RsaVault {
|
||||||
public_key: RsaPublicKey,
|
public_key: RsaPublicKey,
|
||||||
private_key: Option<RsaPrivateKey>,
|
private_key: Option<RsaPrivateKey>,
|
||||||
@ -22,6 +23,7 @@ impl Vault for RsaVault {
|
|||||||
KeyData::Rsa(key_data) => {
|
KeyData::Rsa(key_data) => {
|
||||||
let public_key =
|
let public_key =
|
||||||
RsaPublicKey::try_from(key_data).context("Could not load key")?;
|
RsaPublicKey::try_from(key_data).context("Could not load key")?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
public_key,
|
public_key,
|
||||||
private_key: None,
|
private_key: None,
|
||||||
@ -31,12 +33,48 @@ impl Vault for RsaVault {
|
|||||||
},
|
},
|
||||||
|
|
||||||
(None, Some(private)) => match private.key_data() {
|
(None, Some(private)) => match private.key_data() {
|
||||||
KeypairData::Rsa(key_data) => {
|
KeypairData::Rsa(rsa_keypair) => {
|
||||||
if private.is_encrypted() {
|
if private.is_encrypted() {
|
||||||
return Err(anyhow::anyhow!("Private key is encrypted"));
|
return Err(anyhow::anyhow!("Private key is encrypted"));
|
||||||
}
|
}
|
||||||
let private_key = RsaPrivateKey::try_from(key_data)?;
|
|
||||||
|
// Extract components from ssh-key's RSA representation
|
||||||
|
// Use as_bytes() or a similar method to get the &[u8] from Mpint
|
||||||
|
//
|
||||||
|
// <https://docs.rs/ssh-key/latest/ssh_key/private/struct.RsaPrivateKey.html>
|
||||||
|
//
|
||||||
|
// pub struct RsaPrivateKey {
|
||||||
|
// pub d: Mpint,
|
||||||
|
// pub iqmp: Mpint,
|
||||||
|
// pub p: Mpint,
|
||||||
|
// pub q: Mpint,
|
||||||
|
// }
|
||||||
|
let n = BigUint::from_bytes_be(rsa_keypair.public.n.as_ref());
|
||||||
|
let e = BigUint::from_bytes_be(rsa_keypair.public.e.as_ref());
|
||||||
|
let d = BigUint::from_bytes_be(rsa_keypair.private.d.as_ref());
|
||||||
|
let p = BigUint::from_bytes_be(rsa_keypair.private.p.as_ref());
|
||||||
|
let q = BigUint::from_bytes_be(rsa_keypair.private.q.as_ref());
|
||||||
|
|
||||||
|
// Create the RSA private key
|
||||||
|
//
|
||||||
|
// Constructs an RSA key pair from individual components:
|
||||||
|
//
|
||||||
|
// n: RSA modulus
|
||||||
|
// e: public exponent (i.e. encrypting exponent)
|
||||||
|
// d: private exponent (i.e. decrypting exponent)
|
||||||
|
// primes: prime factors of n: typically two primes p and q. More than two
|
||||||
|
// primes can be provided for multiprime RSA, however this is generally not
|
||||||
|
// recommended. If no primes are provided, a prime factor recovery algorithm
|
||||||
|
// will be employed to attempt to recover the factors (as described in NIST SP
|
||||||
|
// 800-56B Revision 2 Appendix C.2). This algorithm only works if there are
|
||||||
|
// just two prime factors p and q (as opposed to multiprime), and e is between
|
||||||
|
// 2^16 and 2^256.
|
||||||
|
let private_key = RsaPrivateKey::from_components(n, e, d, vec![p, q])?;
|
||||||
|
|
||||||
|
// let private_key = RsaPrivateKey::try_from(key_data)?;
|
||||||
|
|
||||||
let public_key = private_key.to_public_key();
|
let public_key = private_key.to_public_key();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
public_key,
|
public_key,
|
||||||
private_key: Some(private_key),
|
private_key: Some(private_key),
|
||||||
@ -44,9 +82,11 @@ impl Vault for RsaVault {
|
|||||||
}
|
}
|
||||||
_ => Err(anyhow::anyhow!("Invalid key type for RsaVault")),
|
_ => Err(anyhow::anyhow!("Invalid key type for RsaVault")),
|
||||||
},
|
},
|
||||||
|
|
||||||
(Some(_), Some(_)) => Err(anyhow::anyhow!(
|
(Some(_), Some(_)) => Err(anyhow::anyhow!(
|
||||||
"Only one of public and private key is required"
|
"Only one of public and private key is required"
|
||||||
)),
|
)),
|
||||||
|
|
||||||
_ => Err(anyhow::anyhow!("Missing public and private key")),
|
_ => Err(anyhow::anyhow!("Missing public and private key")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,6 +159,33 @@ mod tests {
|
|||||||
let private_key = PrivateKey::read_openssh_file(&private_key_file)?;
|
let private_key = PrivateKey::read_openssh_file(&private_key_file)?;
|
||||||
let vault = RsaVault::new(Some(public_key), Some(private_key));
|
let vault = RsaVault::new(Some(public_key), Some(private_key));
|
||||||
assert_eq!(vault.is_err(), true);
|
assert_eq!(vault.is_err(), true);
|
||||||
|
|
||||||
|
let err = vault.unwrap_err();
|
||||||
|
|
||||||
|
// Convert the error to a string and check the message
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"Only one of public and private key is required"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rsa_vault_using_public_key() -> Result<()> {
|
||||||
|
let public_key_file = Path::new("test_data/id_rsa.pub");
|
||||||
|
let public_key = PublicKey::read_openssh_file(&public_key_file)?;
|
||||||
|
let vault = RsaVault::new(Some(public_key), None);
|
||||||
|
assert_eq!(vault.is_ok(), true);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rsa_vault_using_private_key() -> Result<()> {
|
||||||
|
let private_key_file = Path::new("test_data/id_rsa");
|
||||||
|
let private_key = PrivateKey::read_openssh_file(&private_key_file)?;
|
||||||
|
let vault = RsaVault::new(None, Some(private_key));
|
||||||
|
assert_eq!(vault.is_ok(), true);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user