feat: minor internal rework; improved performance; slightly improved ux; improved code readability; added clippy to format script; bumped to v0.1.1-alpha

This commit is contained in:
Artemis 2023-06-03 00:18:42 +00:00
parent 28644079bc
commit 23471c155c
Signed by: artemismirai
GPG Key ID: 7D04E4915F2181D8
21 changed files with 343 additions and 281 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "kdt"
version = "0.1.0-alpha"
version = "0.1.1-alpha"
edition = "2021"
[dependencies]

View File

@ -8,7 +8,7 @@ KDT is the experimental Kyber-Dilithium Toolset - like GPG, but post-quantum! It
## To-dos
- [x] Store keyset and private keys in local files
- [x] Asymmetric encryption and decryption (CRYSTALS-Kyber-backed 256 bit AES)
- [ ] Wrap encryption with RSA to undoubtedly achieve the verified cryptographic strength of RSA (because CRYSTALS-Kyber's security hasn't been completely verified)
- [ ] Implement Dilithium-DSA and Kyber-RSA hybrid mode
- [x] Signing and signature verification (CRYSTALS-Dilithium)
- [ ] Improve user friendliness

View File

@ -5,3 +5,4 @@ control_brace_style = "AlwaysSameLine"
fn_params_layout = "Compressed"
reorder_imports = true
imports_layout = "Vertical"
chain_width = 50

View File

@ -21,7 +21,15 @@ _main () {
# alias for easy usage
alias rustup="$HOME/.cargo/bin/rustup"
echo "Updating nightly toolchain..."
rustup toolchain install nightly-x86_64-unknown-linux-gnu &>/dev/null
echo "Updated nightly toolchain!"
echo "Updating cargo-clippy..."
rustup component add clippy &>/dev/null
echo "Updated cargo-clippy!"
echo "Using clippy to fix common mistakes..."
rustup run stable cargo clippy
echo "Clippy is done!"
echo "Formatting code..."
rustup run nightly cargo fmt
echo "Formatted code!"

View File

@ -6,7 +6,7 @@ use std::error::Error;
// -- clap options --
/// Mirai's experimental, quantum-safe successor to GPG
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[command(author, version, about, long_about = None, arg_required_else_help = true)]
pub struct Args {
/// Generates a new KDT owned key set and stores it in the
/// local owned key database
@ -79,6 +79,7 @@ impl Args {
.count()
}
#[inline(always)]
pub fn fail_if_invalid(&self) -> Result<(), Box<dyn Error>> {
if self.get_num_called() > 1 {
return Err(Box::new(KdtErr::TooManyArgs));

View File

@ -0,0 +1,73 @@
// -- imports --
use crate::core::*;
use std::fmt;
pub struct KdtEncryptedMessage {
/// The shared secret established by Kyber, encrypted
/// asymmetrically so only you can see it.
pub encrypted_secret: Vec<u8>,
/// The actual encrypted data.
pub encrypted_message: Vec<u8>,
/// The AES-GCM nonce. From
/// https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption/#cryptography.hazmat.primitives.ciphers.algorithms.ChaCha20:
/// `The nonce does not need to be kept secret and may be included with the ciphertext.`
pub nonce: Vec<u8>,
}
impl KdtEncryptedMessage {
/// Creates a new `Message` object from the given encrypted secret,
/// encrypted message, and unencrypted nonce.
pub fn new(encrypted_secret: Vec<u8>, encrypted_message: Vec<u8>, nonce: Vec<u8>) -> Self {
Self {
encrypted_secret,
encrypted_message,
nonce,
}
}
/// Restores a `Message` object from the given message string.
pub fn from_str(message: String) -> Self {
let message_split: Vec<String> = message
.chars()
.skip(27)
.take(message.len() - 27 - 26)
.collect::<String>()
.replace('\n', "")
.split('*')
.map(String::from)
.collect();
Self {
encrypted_secret: Base64::decode_string(&message_split[0]),
encrypted_message: Base64::decode_string(&message_split[1]),
nonce: Base64::decode_string(&message_split[2]),
}
}
}
impl fmt::Display for KdtEncryptedMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let encoded_secret = Base64::encode_bytes(&self.encrypted_secret);
let encoded_message = Base64::encode_bytes(&self.encrypted_message);
let encoded_nonce = Base64::encode_bytes(&self.nonce);
let message = format!("{}*{}*{}", encoded_secret, encoded_message, encoded_nonce,)
.chars()
.enumerate()
.flat_map(|(i, c)| {
if (i + 1) % 64 == 0 {
vec![c, '\n']
} else {
vec![c]
}
})
.collect::<String>();
write!(
f,
"-----BEGIN KDT MESSAGE-----\n{}\n-----END KDT MESSAGE-----",
message.trim()
)
}
}

View File

@ -0,0 +1,75 @@
// -- imports --
use crate::core::*;
use aes_gcm::{
aead::{
Aead,
AeadCore,
KeyInit,
OsRng,
},
Aes256Gcm,
Key,
};
use generic_array::GenericArray;
use pqc_kyber::{
decapsulate,
encapsulate,
};
use std::error::Error;
// -- base crypto handling --
/// Core cryptography handler for KDT. Handles everything when
/// it comes to AES and Kyber. Signatures are handled by the
/// `KdtSignageHandler` though.
pub struct KdtCryptoHandler;
impl KdtCryptoHandler {
/// Encrypts a string of text against the provided Kyber
/// public key. We use AES in the backend here because
/// the way Kyber works is that it establishes a shared
/// symmetric key inside of the asymmetric stuff. Magic!
pub fn encrypt_text(
text: String, pubkey: Vec<u8>,
) -> Result<KdtEncryptedMessage, Box<dyn Error>> {
let mut rng = rand::thread_rng();
let (encrypted_secret, secret_bytes) = encapsulate(&pubkey, &mut rng)?;
let key = Key::<Aes256Gcm>::from_slice(&secret_bytes);
let cipher = Aes256Gcm::new(key);
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let encrypted_message = cipher.encrypt(&nonce, text.as_ref()).unwrap();
let nonce = nonce.into_iter().collect::<Vec<u8>>();
Ok(KdtEncryptedMessage::new(
encrypted_secret.to_vec(),
encrypted_message,
nonce,
))
}
/// Decrypts a pre-deserialized `Message` object with the
/// provided private key. Note that, as stated above, this uses
/// AES under the hood because of the magic way Kyber works -
/// a shared symmetric key is established using the asymmetric
/// keys, and then both parties can encrypt sensitive data
/// with that! Pure magic, obviously.
pub fn decrypt_msg(message: KdtEncryptedMessage, privkey: Vec<u8>) -> String {
// Uses the private key we have to decrypt the symmetric
// shared secret.
let secret_bytes = decapsulate(&message.encrypted_secret, &privkey)
.expect("You used the wrong private key!");
let key = Key::<Aes256Gcm>::from_slice(&secret_bytes);
let cipher = Aes256Gcm::new(key);
let nonce = message.nonce;
// Converts the raw text bytes to a UTF-8 encoded string.
String::from_utf8_lossy(
&cipher
.decrypt(
&GenericArray::clone_from_slice(&nonce),
message.encrypted_message.as_ref(),
)
.unwrap(),
)
.into()
}
}

View File

@ -1,154 +1,5 @@
// -- imports --
use crate::core::*;
use aes_gcm::{
aead::{
Aead,
AeadCore,
KeyInit,
OsRng,
},
Aes256Gcm,
Key,
};
use generic_array::GenericArray;
use pqc_kyber::{
decapsulate,
encapsulate,
SecretKey,
};
use std::{
error::Error,
fmt,
};
pub mod encrypted_message;
pub mod handler;
// -- base crypto handling --
/// Core cryptography handler for KDT. Handles everything when
/// it comes to AES and Kyber. Signatures are handled by the
/// `KdtSignageHandler` though.
pub struct KdtCryptoHandler;
impl KdtCryptoHandler {
/// Encrypts a string of text against the provided Kyber
/// public key. We use AES in the backend here because
/// the way Kyber works is that it establishes a shared
/// symmetric key inside of the asymmetric stuff. Magic!
pub fn encrypt_text(text: String, pubkey_str: String) -> Result<Message, Box<dyn Error>> {
let pubkey = PubKeyPair::from_str(pubkey_str).init();
let mut rng = rand::thread_rng();
let (encrypted_secret, secret_bytes) =
encapsulate(&Base64::decode_string(pubkey.crypto_key), &mut rng)?;
let key = Key::<Aes256Gcm>::from_slice(&secret_bytes);
let cipher = Aes256Gcm::new(&key);
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let encrypted_message =
Base64::encode_bytes(&cipher.encrypt(&nonce, text.as_ref()).unwrap());
let encoded_nonce = Base64::encode_bytes(&nonce.into_iter().collect::<Vec<u8>>());
Ok(Message::new(
Base64::encode_bytes(&encrypted_secret),
encrypted_message,
encoded_nonce,
))
}
/// Decrypts a pre-deserialized `Message` object with the
/// provided private key. Note that, as stated above, this uses
/// AES under the hood because of the magic way Kyber works -
/// a shared symmetric key is established using the asymmetric
/// keys, and then both parties can encrypt sensitive data
/// with that! Pure magic, obviously.
pub fn decrypt_msg(message: Message, privkey_str: String) -> String {
// Uses the private key we have to decrypt the symmetric
// shared secret.
let secret_bytes = decapsulate(
&Base64::decode_string(message.encrypted_secret),
&Base64::decode_string(privkey_str),
)
.expect("You used the wrong private key!");
let key = Key::<Aes256Gcm>::from_slice(&secret_bytes);
let cipher = Aes256Gcm::new(&key);
let nonce = Base64::decode_string(message.nonce);
// Converts the raw text bytes to a UTF-8 encoded string.
String::from_utf8_lossy(
&cipher
.decrypt(
&GenericArray::clone_from_slice(&nonce),
Base64::decode_string(message.encrypted_message).as_ref(),
)
.unwrap(),
)
.into()
}
}
pub struct Message {
/// The shared secret established by Kyber, encrypted
/// asymmetrically so only you can see it.
pub encrypted_secret: String,
/// The actual encrypted data. This is a base64 encoded
/// representation of the shifted bytes.
pub encrypted_message: String,
/// The AES-GCM nonce. From
/// https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption/#cryptography.hazmat.primitives.ciphers.algorithms.ChaCha20:
/// `The nonce does not need to be kept secret and may be included with the ciphertext.`
pub nonce: String,
}
impl Message {
/// Creates a new `Message` object from the given encrypted secret,
/// encrypted message, and unencrypted nonce.
pub fn new(encrypted_secret: String, encrypted_message: String, nonce: String) -> Self {
Self {
encrypted_secret,
encrypted_message,
nonce,
}
}
/// Restores a `Message` object from the given message string.
#[inline(always)]
pub fn from_str(message: String) -> Self {
let message_split: Vec<String> = message
.chars()
.skip(27)
.take(message.len() - 27 - 26)
.collect::<String>()
.replace("\n", "")
.split('*')
.map(String::from)
.collect();
Self {
encrypted_secret: message_split[0].to_owned(),
encrypted_message: message_split[1].to_owned(),
nonce: message_split[2].to_owned(),
}
}
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = format!(
"{}*{}*{}",
self.encrypted_secret, self.encrypted_message, self.nonce
)
.chars()
.enumerate()
.flat_map(|(i, c)| {
if (i + 1) % 64 == 0 {
vec![c, '\n']
} else {
vec![c]
}
})
.collect::<String>();
write!(
f,
"-----BEGIN KDT MESSAGE-----\n{}\n-----END KDT MESSAGE-----",
message.trim_end()
)
}
}
pub use encrypted_message::*;
pub use handler::*;

View File

@ -21,6 +21,8 @@ impl Base64 {
/// Converts a base64 string to a bytearray.
#[inline(always)]
pub fn decode_string<S: fmt::Display>(s: S) -> Vec<u8> {
general_purpose::STANDARD.decode(s.to_string()).unwrap()
general_purpose::STANDARD
.decode(s.to_string())
.unwrap()
}
}

View File

@ -12,10 +12,10 @@ pub enum KdtErr {
PrivDbOpenFailed,
DbDumpFailed,
KeyAlreadyExists,
BadKeyId,
}
impl fmt::Display for KdtErr {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::TooManyArgs => write!(
@ -26,6 +26,7 @@ impl fmt::Display for KdtErr {
Self::PrivDbOpenFailed => write!(f, "Failed to open private keys database!"),
Self::DbDumpFailed => write!(f, "Failed to dump to database!"),
Self::KeyAlreadyExists => write!(f, "This key already exists in the database!"),
Self::BadKeyId => write!(f, "The key id you passed is invalid!"),
}
}
}

View File

@ -17,12 +17,16 @@ pub struct PubKeyDb {
impl PubKeyDb {
/// Takes in the hexadecimal string id of a public key, and returns
/// the public key object.
pub fn get_by_id(&self, id: String) -> PubKeyPair {
let filtered = self.keys.iter().filter(|k| k.id == id).collect::<Vec<_>>();
if filtered.len() > 1 {
unreachable!();
pub fn get_by_id(&self, id: String) -> Result<PubKeyPair, Box<dyn Error>> {
let filtered = self
.keys
.iter()
.filter(|k| k.id == id)
.collect::<Vec<_>>();
if filtered.len() != 1 {
Err(Box::new(KdtErr::BadKeyId))
} else {
filtered[0].clone()
Ok(filtered[0].clone())
}
}
@ -47,16 +51,16 @@ pub struct OwnedKeyDb {
impl OwnedKeyDb {
/// Takes in the hexadecimal string id of a private key,
/// and returns the private-public key pair.
pub fn get_by_id(&self, id: String) -> OwnedKeySet {
pub fn get_by_id(&self, id: String) -> Result<OwnedKeySet, Box<dyn Error>> {
let filtered = self
.keys
.iter()
.filter(|k| k.privkey_pair.id == id)
.collect::<Vec<_>>();
if filtered.len() > 1 {
unreachable!();
if filtered.len() != 1 {
Err(Box::new(KdtErr::BadKeyId))
} else {
filtered[0].clone()
Ok(filtered[0].clone())
}
}

View File

@ -18,15 +18,15 @@ impl OwnedKeySet {
let encryption_keys = kyber_keypair(&mut rand::thread_rng());
let signage_keys = dilithium_keypair::generate();
let pubkey_pair = PubKeyPair::new(
Base64::encode_bytes(&encryption_keys.public),
Base64::encode_bytes(&signage_keys.public),
Base64::encode_bytes(owner_name.as_bytes()),
encryption_keys.public.to_vec(),
signage_keys.public.to_vec(),
owner_name.clone(),
)
.init();
let privkey_pair = PrivKeyPair::new(
Base64::encode_bytes(&encryption_keys.secret),
Base64::encode_bytes(&signage_keys.expose_secret()),
Base64::encode_bytes(owner_name.as_bytes()),
encryption_keys.secret.to_vec(),
signage_keys.expose_secret().to_vec(),
owner_name,
)
.init();

View File

@ -1,4 +1,5 @@
// -- imports --
use crate::core::*;
use serde::{
Deserialize,
Serialize,
@ -12,16 +13,13 @@ use std::fmt;
// -- private key pair (signing key + crypto key) --
#[derive(Serialize, Deserialize, Clone)]
pub struct PrivKeyPair {
/// Base64-encoded bytes for private cryptographic
/// (ie Kyber) key.
pub crypto_key: String,
/// Kyber private key bytes
pub crypto_key: Vec<u8>,
/// Base64-encoded bytes for private signage
/// (ie Dilithium) key.
pub signage_key: String,
/// Dilithium private key bytes
pub signage_key: Vec<u8>,
/// Base64-encoded representation of the key
/// owner's name.
/// Key owner's name as a string
pub owner: String,
/// Sha256 hashsum of this object when the two
@ -34,11 +32,10 @@ pub struct PrivKeyPair {
}
impl PrivKeyPair {
/// Creates a new `PrivKeyPair` object from the provided cryptographic key
/// base64 string, the provided signage key string, and the owner base64
/// string. This doesn't validate the passed inputs, so it *will* panic if
/// you pass bad inputs.
pub fn new(crypto_key: String, signage_key: String, owner: String) -> Self {
/// Creates a new `PrivKeyPair` object from the provided key
/// bytearrays and owner string
#[inline(always)]
pub fn new(crypto_key: Vec<u8>, signage_key: Vec<u8>, owner: String) -> Self {
Self {
crypto_key,
signage_key,
@ -70,16 +67,16 @@ impl PrivKeyPair {
.collect::<String>()
// Turns the human-readable formatting to something that can be parsed
// programmatically.
.replace("\n", "")
.replace('\n', "")
// Splits the private key into a cryptographic key and signage key.
.split('*')
.map(String::from)
.collect();
Self {
crypto_key: privkey[0].to_owned(),
signage_key: privkey[1].to_owned(),
owner: privkey[2].to_owned(),
crypto_key: Base64::decode_string(privkey[0].to_owned()),
signage_key: Base64::decode_string(privkey[1].to_owned()),
owner: String::from_utf8_lossy(&Base64::decode_string(&privkey[2])).to_string(),
id: String::new(),
}
}
@ -88,9 +85,12 @@ impl PrivKeyPair {
// -- human-readable key output impl --
impl fmt::Display for PrivKeyPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let crypto_key = Base64::encode_bytes(&self.crypto_key);
let signage_key = Base64::encode_bytes(&self.signage_key);
let owner = Base64::encode_bytes(self.owner.as_bytes());
// An asterisk separates the encryption key from the
// signing key during key exchanges.
let keypair = format!("{}*{}*{}", &self.crypto_key, &self.signage_key, &self.owner)
let keypair = format!("{}*{}*{}", crypto_key, signage_key, owner)
.chars()
.enumerate()
// This helps maintain readability when printing messages. It

View File

@ -1,4 +1,5 @@
// -- imports --
use crate::core::*;
use serde::{
Deserialize,
Serialize,
@ -12,16 +13,13 @@ use std::fmt;
// -- public key pair (signing key + crypto key) --
#[derive(Serialize, Deserialize, Clone)]
pub struct PubKeyPair {
/// Base64-encoded bytes for public cryptographic
/// (ie Kyber) key.
pub crypto_key: String,
/// Kyber public key bytes
pub crypto_key: Vec<u8>,
/// Base64-encoded bytes for public signage
/// (ie Dilithium) key.
pub signage_key: String,
/// Dilithium public key bytes
pub signage_key: Vec<u8>,
/// Base64-encoded representation of the key
/// owner's name.
/// Key owner's name as a string.
pub owner: String,
/// Sha256 hashsum of this object when the two
@ -34,11 +32,10 @@ pub struct PubKeyPair {
}
impl PubKeyPair {
/// Creates a new `PubKeyPair` object from the provided cryptographic key
/// base64 string, the provided signage key string, and the owner base64
/// string. This doesn't validate the passed inputs, so it *will* panic if
/// you pass bad inputs.
pub fn new(crypto_key: String, signage_key: String, owner: String) -> Self {
/// Creates a new `PubKeyPair` object from the provided key
/// bytearrays and owner string.
#[inline(always)]
pub fn new(crypto_key: Vec<u8>, signage_key: Vec<u8>, owner: String) -> Self {
Self {
crypto_key,
signage_key,
@ -61,7 +58,7 @@ impl PubKeyPair {
/// key string. Doesn't validate input, so it *will* panic if you pass
/// invalid inputs.
pub fn from_str(pubkey_str: String) -> Self {
let pubkey: Vec<String> = pubkey_str
let pubkey: Vec<Vec<u8>> = pubkey_str
.chars()
// Removes the `-----BEGIN KDT PUBKEY BLOCK-----` header.
.skip(32)
@ -70,16 +67,17 @@ impl PubKeyPair {
.collect::<String>()
// Turns the human-readable formatting to something that can be parsed
// programmatically.
.replace("\n", "")
.replace('\n', "")
// Splits the public key into a cryptographic key and signage key.
.split('*')
.map(String::from)
.map(Base64::decode_string)
.collect();
Self {
crypto_key: pubkey[0].to_owned(),
signage_key: pubkey[1].to_owned(),
owner: pubkey[2].to_owned(),
owner: String::from_utf8_lossy(&pubkey[2]).to_string(),
id: String::new(),
}
}
@ -88,9 +86,12 @@ impl PubKeyPair {
// -- human-readable key output impl --
impl fmt::Display for PubKeyPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let crypto_key = Base64::encode_bytes(&self.crypto_key);
let signage_key = Base64::encode_bytes(&self.signage_key);
let owner = Base64::encode_bytes(self.owner.as_bytes());
// An asterisk separates the encryption key from the
// signing key during key exchanges.
let keypair = format!("{}*{}*{}", &self.crypto_key, &self.signage_key, &self.owner)
let keypair = format!("{}*{}*{}", crypto_key, signage_key, owner)
.chars()
.enumerate()
// This helps maintain readability when printing messages. It

6
src/core/message.rs Normal file
View File

@ -0,0 +1,6 @@
// -- imports --
use std::fmt;
pub trait KdtMessage {
fn from_str<S: fmt::Display>(message_str: S) -> Self;
}

View File

@ -1,5 +1,5 @@
// -- compiler flags --
#![allow(unused_imports)]
//#![allow(unused_imports)]
// -- local modules (+ exports) --
pub mod crypto;
@ -7,6 +7,7 @@ pub mod encoding;
pub mod errors;
pub mod keys;
pub mod logging;
pub mod message;
pub mod signing;
pub use crypto::*;
@ -14,9 +15,11 @@ pub use encoding::*;
pub use errors::*;
pub use keys::*;
pub use logging::*;
pub use message::*;
pub use signing::*;
// -- external imports --
use pqc_dilithium::Keypair;
use ron::{
de::from_reader,
ser::{
@ -31,14 +34,8 @@ use serde::{
use std::{
error::Error,
fmt,
fs::{
self,
File,
},
io::{
Read,
Write,
},
fs::File,
io::Write,
path::Path,
};
@ -138,14 +135,13 @@ impl CoreKdtHandler {
// Construct a public key using the given string
let pubkey = PubKeyPair::from_str(pubkey_str.to_string()).init();
// Make sure this public key isn't already registered to the database
if self
if !self
.pubkey_db
.keys
.iter()
.filter(|k| k.id == pubkey.id)
.collect::<Vec<_>>()
.len()
!= 0
.is_empty()
{
Err(Box::new(KdtErr::KeyAlreadyExists))
} else {
@ -181,42 +177,48 @@ impl CoreKdtHandler {
/// Encrypts the given message against the public key of the given
/// id.
pub fn encrypt(&self, pubkey_id: String, text: String) -> String {
let public_key = self.pubkey_db.get_by_id(pubkey_id).to_string();
KdtCryptoHandler::encrypt_text(text, public_key)
let public_key = self.pubkey_db.get_by_id(pubkey_id).unwrap();
KdtCryptoHandler::encrypt_text(text, public_key.crypto_key)
.unwrap()
.to_string()
}
/// Decrypts the given message with the private key of the given id.
pub fn decrypt(&self, privkey_id: String, message: String) -> String {
let message = Message::from_str(message);
let message = KdtEncryptedMessage::from_str(message);
let private_key = self
.ownedkey_db
.get_by_id(privkey_id)
.unwrap()
.privkey_pair
.crypto_key;
KdtCryptoHandler::decrypt_msg(message, private_key).to_string()
KdtCryptoHandler::decrypt_msg(message, private_key)
}
/// Signs the given message with the private key of the given id.
pub fn sign(&self, privkey_id: String, text: String) -> String {
pub fn sign(&self, privkey_id: String, text: String) -> Result<String, Box<dyn Error>> {
let signing_pubkey = self
.ownedkey_db
.get_by_id(privkey_id.clone())
.get_by_id(privkey_id.clone())?
.pubkey_pair
.signage_key;
let signing_privkey = self
.ownedkey_db
.get_by_id(privkey_id.clone())
.get_by_id(privkey_id)?
.privkey_pair
.signage_key;
KdtSignageHandler::sign_text(text, signing_privkey, signing_pubkey).to_string()
let keypair = Keypair::restore_from_keys(signing_pubkey, signing_privkey);
Ok(KdtSignageHandler::sign_text(text, keypair))
}
/// Verifies the given KDT-signed message with the public key of the
/// given id.
pub fn verify(&self, pubkey_id: String, full_text: String) -> Option<bool> {
let verification_pubkey = self.pubkey_db.get_by_id(pubkey_id).signage_key;
let verification_pubkey = self
.pubkey_db
.get_by_id(pubkey_id)
.unwrap()
.signage_key;
let parts: Vec<String> = full_text
.chars()
.skip(35)
@ -227,11 +229,8 @@ impl CoreKdtHandler {
.map(String::from)
.collect();
let text = parts.first()?.trim().to_owned();
let signature = parts.last()?.replace("\n", "");
Some(KdtSignageHandler::verify(
text,
signature,
verification_pubkey,
))
let signature = parts.last()?.replace('\n', "");
let message = KdtSignedMessage::new(text, Base64::decode_string(signature));
Some(KdtSignageHandler::verify(message, verification_pubkey))
}
}

View File

@ -13,11 +13,7 @@ impl KdtSignageHandler {
/// in a visually appealing way (mostly just stole GPG's output
/// styling).
#[inline(always)]
pub fn sign_text(text: String, privkey_str: String, pubkey_str: String) -> String {
let signkey = Keypair::restore_from_keys(
Base64::decode_string(pubkey_str),
Base64::decode_string(privkey_str),
);
pub fn sign_text(text: String, signkey: Keypair) -> String {
format!("-----BEGIN KDT SIGNED MESSAGE-----\n{}\n\n-----BEGIN KDT SIGNATURE-----\n{}\n-----END KDT SIGNATURE-----", text, Base64::encode_bytes(&signkey.sign(text.as_bytes())).chars()
.enumerate()
.flat_map(|(i, c)| {
@ -33,10 +29,9 @@ impl KdtSignageHandler {
/// Verifies a KDT dilithium-signed message against its
/// corresponding public key.
#[inline(always)]
pub fn verify(text: String, signature: String, pubkey_str: String) -> bool {
let text_bytes = text.as_bytes();
let sig_bytes = Base64::decode_string(signature);
let pubkey = Base64::decode_string(pubkey_str);
dilithium_verify(&sig_bytes, &text_bytes, &pubkey).is_ok()
pub fn verify(signed_message: KdtSignedMessage, pubkey: Vec<u8>) -> bool {
let text_bytes = signed_message.message.as_bytes();
let sig_bytes = signed_message.signature;
dilithium_verify(&sig_bytes, text_bytes, &pubkey).is_ok()
}
}

View File

@ -1,6 +1,8 @@
// -- compiler flags --
#![allow(dead_code)]
pub mod signatures;
pub mod handler;
pub mod signed_message;
pub use signatures::*;
pub use handler::*;
pub use signed_message::*;

View File

@ -0,0 +1,60 @@
// -- imports --
use crate::core::*;
use std::fmt;
pub struct KdtSignedMessage {
/// Message string
pub message: String,
/// Dilithium signature bytes
pub signature: Vec<u8>,
}
impl KdtSignedMessage {
#[inline(always)]
pub fn new<S: fmt::Display>(message: S, signature: Vec<u8>) -> Self {
Self {
message: message.to_string(),
signature,
}
}
}
impl KdtMessage for KdtSignedMessage {
fn from_str<S: fmt::Display>(full_signature: S) -> Self {
let parts: Vec<String> = full_signature
.to_string()
.chars()
.skip(35)
.take(full_signature.to_string().len() - 35 - 27)
.collect::<String>()
.split("-----BEGIN KDT SIGNATURE-----")
.map(|x| x.trim())
.map(String::from)
.collect();
let text = parts.first().unwrap().trim().to_owned();
let signature_str = parts.last().unwrap().replace('\n', "");
Self {
message: text,
signature: Base64::decode_string(signature_str),
}
}
}
impl fmt::Display for KdtSignedMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let fmt_sig = Base64::encode_bytes(&self.signature);
let sig = format!("-----BEGIN KDT SIGNED MESSAGE-----\n{}\n\n-----BEGIN KDT SIGNATURE-----\n{}\n-----END KDT SIGNATURE-----", self.message, fmt_sig.chars()
.enumerate()
.flat_map(|(i, c)| {
if (i + 1) % 64 == 0 {
vec![c, '\n']
} else {
vec![c]
}
})
.collect::<String>());
write!(f, "{}", sig)
}
}

View File

@ -1,3 +1,4 @@
// TODO: finish refactor!
// -- unit testing module --
#[cfg(test)]
mod tests;
@ -15,11 +16,6 @@ fn main() {
if let Err(e) = args.fail_if_invalid() {
logger.fatal(e);
}
if args.get_num_called() == 0 {
logger.fatal(
"You didn't pass any arguments! Run `kdt -h` for a list of possible flags and args.",
);
}
let mut kdt = match CoreKdtHandler::new() {
Ok(k) => k,
Err(e) => logger.fatal(e),
@ -35,7 +31,10 @@ fn main() {
logger.success(format!("Public key for key id {}:", pubkey_id));
println!(
"{}",
kdt.ownedkey_db.get_by_id(pubkey_id).pubkey_pair.to_string()
kdt.ownedkey_db
.get_by_id(pubkey_id)
.unwrap()
.pubkey_pair
);
}
// `--del-pubkey`
@ -72,7 +71,7 @@ fn main() {
logger.info("Input the message to sign below (CTRL-D to finish):");
let message = logger.input();
logger.info("Signed message:");
println!("{}", kdt.sign(privkey_id, message));
println!("{}", kdt.sign(privkey_id, message).unwrap());
}
// `-v | --verify`
if let Some(pubkey_id) = args.verify {
@ -102,7 +101,7 @@ fn main() {
println!(
"ID: {}\nOwner: {}",
key.privkey_pair.id,
String::from_utf8_lossy(&Base64::decode_string(key.clone().privkey_pair.owner))
key.clone().privkey_pair.owner
);
}
}
@ -143,11 +142,7 @@ fn main() {
}
logger.info("Keys in your public key database:");
for key in &kdt.pubkey_db.keys {
println!(
"ID: {}\nOwner: {}",
key.id,
String::from_utf8_lossy(&Base64::decode_string(key.clone().owner))
);
println!("ID: {}\nOwner: {}", key.id, key.clone().owner);
}
}
}

View File

@ -1,5 +1,6 @@
// -- imports --
use crate::core::*;
use pqc_dilithium::Keypair;
// -- tests --
#[test]
@ -10,7 +11,7 @@ fn kyber_with_correct_privkey() {
let keyset = OwnedKeySet::generate("Test Key".into());
(
keyset.pubkey_pair.to_string(),
keyset.pubkey_pair.crypto_key,
keyset.privkey_pair.crypto_key,
)
};
@ -29,7 +30,7 @@ fn kyber_with_incorrect_privkey() {
let keyset_2 = OwnedKeySet::generate("Test Key".into());
(
keyset_1.pubkey_pair.to_string(),
keyset_1.pubkey_pair.crypto_key,
keyset_2.privkey_pair.crypto_key,
)
};
@ -41,30 +42,17 @@ fn kyber_with_incorrect_privkey() {
#[test]
fn dilithium_with_correct_pubkey() {
let text = String::from("This is a test message");
let (pubkey, privkey) = {
let keypair = {
let keyset = OwnedKeySet::generate("Test Key".into());
(
Keypair::restore_from_keys(
keyset.pubkey_pair.signage_key,
keyset.privkey_pair.signage_key,
)
};
let signed_text = KdtSignageHandler::sign_text(text, privkey, pubkey.clone());
let signature_is_valid = {
let parts: Vec<String> = signed_text
.chars()
.skip(35)
.take(signed_text.len() - 35 - 27)
.collect::<String>()
.split("-----BEGIN KDT SIGNATURE-----")
.map(|x| x.trim())
.map(String::from)
.collect();
let derived_text = parts.first().unwrap().trim().to_owned();
let derived_signature = parts.last().unwrap().replace("\n", "");
KdtSignageHandler::verify(derived_text, derived_signature, pubkey)
};
assert!(signature_is_valid);
let signed_text = KdtSignageHandler::sign_text(text, keypair);
let msg = KdtSignedMessage::from_str(signed_text);
let signature_validity = KdtSignageHandler::verify(msg, keypair.public.to_vec());
assert!(signature_validity);
}