Skip to content

Commit

Permalink
refactor: ♻️ Add docs and move keygen functions to module key_pair.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jisu-Woniu committed Nov 12, 2023
1 parent 733f975 commit e07085f
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 62 deletions.
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"cSpell.words": [
"smallvec",
"Subpacket",
"subpackets",
"thiserror",
"unhashed",
"zeroize",
"Zeroizing"
]
}
6 changes: 6 additions & 0 deletions src-tauri/crypto/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
//! Error types wrapper
use std::io;

use thiserror::Error;

/// Error type
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
/// Error propagated from [`io::Error`]
#[error("IO error: {0}")]
Io(#[from] io::Error),

/// Error propagated from [`pgp::errors::Error`]
#[error("PGP error: {0}")]
Pgp(#[from] pgp::errors::Error),
}

/// Result type for use in the crate.
pub type Result<T> = std::result::Result<T, Error>;
60 changes: 59 additions & 1 deletion src-tauri/crypto/src/key_pair.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,57 @@
use pgp::{SignedPublicKey, SignedSecretKey};
use std::path::Path;

use pgp::{
crypto::{hash::HashAlgorithm, sym::SymmetricKeyAlgorithm},
types::{CompressionAlgorithm, SecretKeyTrait as _},
Deserializable, KeyType, SecretKeyParamsBuilder, SignedPublicKey, SignedSecretKey,
};
use smallvec::smallvec;
use tokio::fs;

use crate::Result;

pub(crate) struct KeyPair {
secret_key: SignedSecretKey,
public_key: SignedPublicKey,
}

impl KeyPair {
pub(crate) fn generate(name: &str, email: &str) -> Result<Self> {
let secret_key = SecretKeyParamsBuilder::default()
// Set keygen params.
// TODO: Allow user to choose between RSA, EdDSA and more.
.key_type(KeyType::EdDSA)
.primary_user_id(format!("{} <{}>", name, email))
.preferred_symmetric_algorithms(smallvec![
SymmetricKeyAlgorithm::AES256,
SymmetricKeyAlgorithm::AES192,
SymmetricKeyAlgorithm::AES128,
SymmetricKeyAlgorithm::TripleDES,
])
.preferred_hash_algorithms(smallvec![
HashAlgorithm::SHA2_512,
HashAlgorithm::SHA2_384,
HashAlgorithm::SHA2_256,
HashAlgorithm::SHA2_224,
HashAlgorithm::SHA1
])
.preferred_compression_algorithms(smallvec![
CompressionAlgorithm::ZLIB,
CompressionAlgorithm::BZip2,
CompressionAlgorithm::ZIP
])
.can_sign(true)
.build()
.expect("msg")
.generate()?;
let passwd_fn = String::new;
let signed_secret_key = secret_key.sign(passwd_fn)?;
let public_key = signed_secret_key.public_key();
let signed_public_key = public_key.sign(&signed_secret_key, passwd_fn)?;

Ok(KeyPair::from_keys(signed_secret_key, signed_public_key))
}

pub(crate) fn from_keys(secret_key: SignedSecretKey, public_key: SignedPublicKey) -> Self {
Self {
secret_key,
Expand All @@ -20,3 +66,15 @@ impl KeyPair {
&self.public_key
}
}

#[allow(dead_code)]
pub(crate) async fn secret_key_from_file(path: impl AsRef<Path>) -> Result<SignedSecretKey> {
let input = fs::read_to_string(path).await?;
Ok(SignedSecretKey::from_string(&input)?.0)
}

#[allow(dead_code)]
pub(crate) async fn public_key_from_file(path: impl AsRef<Path>) -> Result<SignedPublicKey> {
let input = fs::read_to_string(path).await?;
Ok(SignedPublicKey::from_string(&input)?.0)
}
73 changes: 16 additions & 57 deletions src-tauri/crypto/src/keygen.rs
Original file line number Diff line number Diff line change
@@ -1,90 +1,52 @@
//! Utilities for key generation.
use std::path::Path;

use pgp::{
crypto::{hash::HashAlgorithm, sym::SymmetricKeyAlgorithm},
types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait as _},
KeyType, SecretKeyParamsBuilder,
};
use smallvec::smallvec;
use pgp::types::KeyTrait;
use tokio::{
fs::{self, DirBuilder},
try_join,
};
use zeroize::Zeroizing;

use crate::{error::Result, key_pair::KeyPair, secret_file::write_secret_file};
use crate::{key_pair::KeyPair, secret_file::write_secret_file, Result};

/// Write a key pair generated from `name` and `email` to a given path, in armored form as RFC 4880.
pub async fn write_key_pair(name: &str, email: &str, path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();

// Create output directory if not exist.
DirBuilder::new().recursive(true).create(path).await?;

let key_pair = gen_key_pair(name, email)?;
let key_pair = KeyPair::generate(name, email)?;
let signed_secret_key = key_pair.secret_key();
let signed_public_key = key_pair.public_key();
let keyid = &hex::encode_upper(&signed_secret_key.key_id().as_ref()[4..]);
let key_id = &hex::encode_upper(&signed_secret_key.key_id().as_ref()[4..]);

let secret_key = path.join(format!("{}_0x{}_SECRET.asc", name, keyid));
let public_key = path.join(format!("{}_0x{}_public.asc", name, keyid));
let secret_key = path.join(format!("{}_0x{}_SECRET.asc", name, key_id));
let public_key = path.join(format!("{}_0x{}_public.asc", name, key_id));

let secret_key_pem = Zeroizing::new(signed_secret_key.to_armored_bytes(None)?);
let pub_key_pem = signed_public_key.to_armored_bytes(None)?;
let secret_key_armored = Zeroizing::new(signed_secret_key.to_armored_bytes(None)?);
let public_key_armored = signed_public_key.to_armored_bytes(None)?;

try_join!(
write_secret_file(&secret_key, &secret_key_pem),
fs::write(&public_key, &pub_key_pem)
write_secret_file(&secret_key, &secret_key_armored),
fs::write(&public_key, &public_key_armored)
)?;

Ok(())
}

pub(crate) fn gen_key_pair(name: &str, email: &str) -> Result<KeyPair> {
let secret_key = SecretKeyParamsBuilder::default()
// Set keygen params.
.key_type(KeyType::EdDSA)
.primary_user_id(format!("{} <{}>", name, email))
.preferred_symmetric_algorithms(smallvec![
SymmetricKeyAlgorithm::AES256,
SymmetricKeyAlgorithm::AES192,
SymmetricKeyAlgorithm::AES128,
SymmetricKeyAlgorithm::TripleDES,
])
.preferred_hash_algorithms(smallvec![
HashAlgorithm::SHA2_512,
HashAlgorithm::SHA2_384,
HashAlgorithm::SHA2_256,
HashAlgorithm::SHA2_224,
HashAlgorithm::SHA1
])
.preferred_compression_algorithms(smallvec![
CompressionAlgorithm::ZLIB,
CompressionAlgorithm::BZip2,
CompressionAlgorithm::ZIP
])
.can_sign(true)
.build()
.expect("msg")
.generate()
.expect("Failed to generate a plain key.");
let passwd_fn = String::new;
let signed_secret_key = secret_key.sign(passwd_fn)?;
let public_key = signed_secret_key.public_key();
let signed_public_key = public_key.sign(&signed_secret_key, passwd_fn)?;

Ok(KeyPair::from_keys(signed_secret_key, signed_public_key))
}

#[cfg(test)]
mod tests {
use pgp::{types::KeyTrait, Deserializable, SignedSecretKey};

use super::{gen_key_pair, Result};
use crate::{key_pair::KeyPair, Result};

#[tokio::test]
#[ignore = "Manual testing for file generation."]
async fn test() -> Result<()> {
let key_pair = gen_key_pair("DS", "[email protected]")?;
let key_pair = KeyPair::generate("DS", "[email protected]")?;
let (secret_key, public_key) = (key_pair.secret_key(), key_pair.public_key());
println!("{}", secret_key.to_armored_string(None)?);
println!("{}", public_key.to_armored_string(None)?);
Expand All @@ -95,18 +57,15 @@ mod tests {
#[tokio::test]
#[ignore = "Manual testing for file parsing."]
async fn extract_key_info() -> Result<()> {
let secret_key_str = gen_key_pair("DS", "[email protected]")?
let secret_key_str = KeyPair::generate("DS", "[email protected]")?
.secret_key()
.to_armored_string(None)?;

let secret_key = SignedSecretKey::from_string(&secret_key_str)?.0;
dbg!(&secret_key);
let key_id = secret_key.key_id();

dbg!(&key_id);

dbg!(&hex::encode_upper(&key_id.as_ref()[4..]));

Ok(())
}
}
5 changes: 5 additions & 0 deletions src-tauri/crypto/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#![warn(missing_docs)]
//! Cryptographic functions for the digital signature app.
pub mod error;
mod key_pair;
pub mod keygen;
mod secret_file;
pub mod signing;

pub use error::Result;
12 changes: 8 additions & 4 deletions src-tauri/crypto/src/signing.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
//! Utilities for signing files.
use chrono::Utc;
use pgp::{
packet::{SignatureConfigBuilder, SignatureType, Subpacket, SubpacketData},
types::{PublicKeyTrait, SecretKeyTrait},
Signature,
};

use crate::error::Result;
use crate::Result;

/// Generate a signature of the given data.
pub fn sign(data: &[u8], secret_key: &impl SecretKeyTrait) -> Result<Signature> {
let now = Utc::now();
let sig_conf = SignatureConfigBuilder::default()
Expand All @@ -23,18 +26,19 @@ pub fn sign(data: &[u8], secret_key: &impl SecretKeyTrait) -> Result<Signature>
Ok(sig_conf.sign(secret_key, String::new, data)?)
}

/// Verify a signature of the given data.
pub fn verify(data: &[u8], public_key: &impl PublicKeyTrait, signature: &Signature) -> Result<()> {
Ok(signature.verify(public_key, data)?)
}

#[cfg(test)]
mod tests {
use super::{sign, verify, Result};
use crate::keygen::gen_key_pair;
use crate::key_pair::KeyPair;

#[test]
fn test_sign() -> Result<()> {
let key_pair = gen_key_pair("DS", "[email protected]")?;
let key_pair = KeyPair::generate("DS", "[email protected]")?;
let (secret_key, public_key) = (key_pair.secret_key(), key_pair.public_key());
let signature = sign(b"Hello", &secret_key)?;
verify(b"Hello", &public_key, &signature)?;
Expand All @@ -43,7 +47,7 @@ mod tests {

#[test]
fn test_sign_error() -> Result<()> {
let key_pair = gen_key_pair("DS", "[email protected]")?;
let key_pair = KeyPair::generate("DS", "[email protected]")?;
let (secret_key, public_key) = (key_pair.secret_key(), key_pair.public_key());
let signature = sign(b"Hello", &secret_key)?;
eprintln!(
Expand Down

0 comments on commit e07085f

Please sign in to comment.