Skip to content

Commit 1d0e9b6

Browse files
committed
Crypto seperation of concern, repository stuff mostly done
1 parent ae201fe commit 1d0e9b6

File tree

5 files changed

+394
-160
lines changed

5 files changed

+394
-160
lines changed

src/crypto/key.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use anyhow::{Context, Result};
2+
use ed25519_dalek::{
3+
SigningKey, VerifyingKey,
4+
pkcs8::{
5+
DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey,
6+
spki::der::pem::LineEnding,
7+
},
8+
};
9+
use rand_core::{OsRng, TryRngCore};
10+
use std::{
11+
fs::{self, create_dir_all},
12+
os::unix::fs::PermissionsExt,
13+
path::{Path, PathBuf},
14+
};
15+
16+
use crate::utils::config::get_config_dir;
17+
18+
/// Returns private key, generating it if necessary
19+
pub fn get_private_key(config_path: Option<&Path>) -> Result<SigningKey> {
20+
let path = unwrap_config_path(config_path)?.join("id_ed25519");
21+
22+
if !unwrap_config_path(config_path)?.exists() {
23+
create_dir_all(unwrap_config_path(config_path)?)?;
24+
}
25+
26+
if !path.exists() {
27+
let new_key = generate_private_key();
28+
let pem = new_key
29+
.to_pkcs8_pem(LineEnding::LF)
30+
.map_err(|e| anyhow::anyhow!("failed to encode private key: {e}"))?;
31+
fs::write(&path, pem)?;
32+
33+
let mut perms = fs::metadata(&path)?.permissions();
34+
perms.set_mode(0o600);
35+
fs::set_permissions(&path, perms)?;
36+
}
37+
38+
let pem_str = fs::read_to_string(&path)?;
39+
let key = SigningKey::from_pkcs8_pem(&pem_str)
40+
.map_err(|e| anyhow::anyhow!("failed to decode private key: {e}"))?;
41+
42+
Ok(key)
43+
}
44+
45+
fn generate_private_key() -> SigningKey {
46+
let mut csprng = OsRng.unwrap_err();
47+
SigningKey::generate(&mut csprng)
48+
}
49+
50+
fn unwrap_config_path(config_path: Option<&Path>) -> Result<PathBuf> {
51+
let path = if let Some(config_path) = config_path {
52+
config_path.to_path_buf()
53+
} else {
54+
get_config_dir()?
55+
};
56+
57+
Ok(path)
58+
}
59+
60+
/// Returns public key, generating it AND/OR the private key if necessary
61+
pub fn get_public_key(config_path: Option<&Path>) -> Result<VerifyingKey> {
62+
let path = unwrap_config_path(config_path)?.join("id_ed25519.pub");
63+
64+
if !path.exists() {
65+
let new_key = generate_public_key(&unwrap_config_path(config_path)?)
66+
.with_context(|| "Could not generate new public key")?;
67+
let new_key_serialized = serialize_verifying_key(new_key)?;
68+
fs::write(&path, new_key_serialized).with_context(|| "Could not write new public key?")?;
69+
}
70+
71+
let pem_str = fs::read_to_string(&path)?;
72+
let key = VerifyingKey::from_public_key_pem(&pem_str)
73+
.map_err(|e| anyhow::anyhow!("failed to decode public key: {e}"))?;
74+
75+
Ok(key)
76+
}
77+
78+
fn generate_public_key(config_path: &Path) -> Result<VerifyingKey> {
79+
let signing_key =
80+
get_private_key(Some(config_path)).with_context(|| "Could not get private key")?;
81+
let public_key = signing_key.verifying_key();
82+
83+
Ok(public_key)
84+
}
85+
86+
pub fn serialize_verifying_key(verifying_key: VerifyingKey) -> Result<String> {
87+
let pem = verifying_key
88+
.to_public_key_pem(LineEnding::LF)
89+
.map_err(|e| anyhow::anyhow!("failed to encode public key: {e}"))?;
90+
91+
Ok(pem)
92+
}
93+
94+
pub fn deserialize_verifying_key(verifying_key_serialized: &str) -> Result<VerifyingKey> {
95+
let verifying_key = VerifyingKey::from_public_key_pem(verifying_key_serialized)
96+
.map_err(|e| anyhow::anyhow!("failed to decode public key: {e}"))?;
97+
98+
Ok(verifying_key)
99+
}
100+
101+
#[cfg(test)]
102+
mod tests {
103+
use temp_dir::TempDir;
104+
105+
use super::*;
106+
107+
#[test]
108+
fn test_generate_private_key_and_read_back() -> Result<()> {
109+
let temp = TempDir::new().unwrap();
110+
let config_dir = &temp.path().join("flint");
111+
112+
// Create key
113+
let path = config_dir.join("id_ed25519");
114+
115+
// Should generate new private key
116+
let key = get_private_key(Some(config_dir))?;
117+
assert!(path.exists());
118+
assert!(!key.verifying_key().to_bytes().is_empty());
119+
120+
// Should load the same key on second call
121+
let key2 = get_private_key(Some(config_dir))?;
122+
assert_eq!(key.to_bytes(), key2.to_bytes());
123+
124+
Ok(())
125+
}
126+
127+
#[test]
128+
fn test_generate_private_key_permissions() -> Result<()> {
129+
let temp = TempDir::new().unwrap();
130+
let config_dir = &temp.path().join("flint");
131+
132+
// Create key
133+
let path = config_dir;
134+
135+
// Generate new private key
136+
let _ = get_private_key(Some(path))?;
137+
138+
let permissions = fs::metadata(path.join("id_ed25519"))?.permissions().mode();
139+
assert_eq!(permissions & 0o777, 0o600);
140+
141+
Ok(())
142+
}
143+
144+
#[test]
145+
fn test_generate_public_key_and_read_back() -> Result<()> {
146+
let temp = TempDir::new().unwrap();
147+
let config_dir = &temp.path().join("flint");
148+
149+
let public_path = config_dir.join("id_ed25519.pub");
150+
151+
// Generate private+public
152+
let private_key = get_private_key(Some(config_dir))?;
153+
let public_key = get_public_key(Some(config_dir))?;
154+
155+
assert!(public_path.exists());
156+
assert_eq!(
157+
private_key.verifying_key().to_bytes(),
158+
public_key.to_bytes()
159+
);
160+
161+
Ok(())
162+
}
163+
}

src/crypto/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use anyhow::Result;
2+
use ed25519_dalek::{VerifyingKey, pkcs8::DecodePublicKey};
3+
4+
pub mod key;
5+
pub mod signing;
6+
7+
pub fn get_public_key_from_pem(pem: &str) -> Result<VerifyingKey> {
8+
let verifying_key = VerifyingKey::from_public_key_pem(pem)
9+
.map_err(|e| anyhow::anyhow!("failed to encode public key: {e}"))?;
10+
11+
Ok(verifying_key)
12+
}

src/crypto/signing.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use std::{fs, path::Path};
2+
3+
use anyhow::Result;
4+
use ed25519_dalek::{Signature, VerifyingKey, ed25519::signature::SignerMut};
5+
6+
use crate::crypto::key::get_private_key;
7+
8+
/// Signs and inserts the signature into the filesystem.
9+
pub fn sign(repo_path: &Path, manifest_serialized: &str) -> Result<Signature> {
10+
let mut signing_key = get_private_key(None)?;
11+
let signature = signing_key.sign(manifest_serialized.as_bytes());
12+
13+
fs::write(repo_path.join("manifest.yml.sig"), signature.to_bytes())?;
14+
15+
verify_signature(
16+
manifest_serialized,
17+
&signature.to_bytes(),
18+
signing_key.verifying_key(),
19+
)?;
20+
21+
Ok(signature)
22+
}
23+
24+
/// Verifies the signature, and errors out if its incorrect
25+
pub fn verify_signature(
26+
manifest_serialized: &str,
27+
signature: &[u8],
28+
verifying_key: VerifyingKey,
29+
) -> Result<()> {
30+
let signature = Signature::try_from(signature)?;
31+
32+
verifying_key.verify_strict(manifest_serialized.as_bytes(), &signature)?;
33+
34+
Ok(())
35+
}

0 commit comments

Comments
 (0)