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:
parent
28644079bc
commit
23471c155c
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "kdt"
|
||||
version = "0.1.0-alpha"
|
||||
version = "0.1.1-alpha"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -5,3 +5,4 @@ control_brace_style = "AlwaysSameLine"
|
|||
fn_params_layout = "Compressed"
|
||||
reorder_imports = true
|
||||
imports_layout = "Vertical"
|
||||
chain_width = 50
|
||||
|
|
|
@ -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!"
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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::*;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// -- imports --
|
||||
use std::fmt;
|
||||
|
||||
pub trait KdtMessage {
|
||||
fn from_str<S: fmt::Display>(message_str: S) -> Self;
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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::*;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
21
src/main.rs
21
src/main.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
30
src/tests.rs
30
src/tests.rs
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue