use crate::{
crypto::{
generic::EncryptionKey,
signing_key::generation_types::{CLIENT_GENERATED, IMPORTED, SERVER_GENERATED},
RemoteStorageKey,
},
types::database::account::UserId,
LockKeeperError,
};
use k256::ecdsa;
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use utilities::crypto::{
error::CryptoError,
signing_key::{Signature, SigningPublicKey},
signing_private_key::SigningPrivateKey,
};
use zeroize::ZeroizeOnDrop;
use super::{generic::AssociatedData, Encrypted, Export, KeyId, StorageKey};
pub trait Signable: AsRef<[u8]> {
fn sign(&self, signing_key: &SigningKeyPair) -> Signature;
fn verify(
&self,
public_key: &SigningPublicKey,
signature: &Signature,
) -> Result<(), CryptoError>;
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SignableBytes(pub Vec<u8>);
impl AsRef<[u8]> for SignableBytes {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Signable for SignableBytes {
fn sign(&self, signing_key_pair: &SigningKeyPair) -> Signature {
signing_key_pair.signing_key.sign(&self.0)
}
fn verify(
&self,
public_key: &SigningPublicKey,
signature: &Signature,
) -> Result<(), CryptoError> {
public_key.verify(&self.0, signature)
}
}
pub mod generation_types {
pub const SERVER_GENERATED: &str = "server-generated";
pub const CLIENT_GENERATED: &str = "client-generated";
pub const IMPORTED: &str = "imported key";
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SigningKeyPair {
signing_key: SigningPrivateKey,
context: AssociatedData,
}
impl SigningKeyPair {
fn generate(rng: &mut (impl CryptoRng + RngCore), context: &AssociatedData) -> Self {
Self {
signing_key: SigningPrivateKey::generate(rng),
context: context.clone(),
}
}
fn domain_separator() -> &'static str {
"ECDSA signing key pair over curve secp256k1"
}
pub fn public_key(&self) -> SigningPublicKey {
self.signing_key.public_key()
}
pub(super) fn context(&self) -> &AssociatedData {
&self.context
}
pub fn remote_generate(
rng: &mut (impl CryptoRng + RngCore),
user_id: &UserId,
key_id: &KeyId,
) -> Self {
let context = AssociatedData::new()
.with_bytes(user_id.clone())
.with_bytes(key_id.clone())
.with_str(SERVER_GENERATED);
Self::generate(rng, &context)
}
pub fn import_and_encrypt(
key_material: &[u8],
rng: &mut (impl CryptoRng + RngCore),
storage_key: &StorageKey,
user_id: &UserId,
key_id: &KeyId,
) -> Result<(Self, Encrypted<Self>), LockKeeperError> {
let context = AssociatedData::new()
.with_bytes(user_id.clone())
.with_bytes(key_id.clone())
.with_str(IMPORTED);
let signing_key = Self {
signing_key: SigningPrivateKey::from_bytes(key_material)?,
context: context.clone(),
};
Ok((
signing_key.clone(),
Encrypted::encrypt(rng, &storage_key.0, signing_key, &context)?,
))
}
pub fn create_and_encrypt(
rng: &mut (impl CryptoRng + RngCore),
storage_key: &StorageKey,
user_id: &UserId,
key_id: &KeyId,
) -> Result<(Self, Encrypted<Self>), LockKeeperError> {
let context = AssociatedData::new()
.with_bytes(user_id.clone())
.with_bytes(key_id.clone())
.with_str(CLIENT_GENERATED);
let signing_key = SigningKeyPair::generate(rng, &context);
Ok((
signing_key.clone(),
Encrypted::encrypt(rng, &storage_key.0, signing_key, &context)?,
))
}
}
impl Encrypted<SigningKeyPair> {
pub fn decrypt_signing_key(
self,
storage_key: StorageKey,
user_id: UserId,
key_id: KeyId,
) -> Result<SigningKeyPair, LockKeeperError> {
self.decrypt(
&storage_key.0,
user_id,
key_id,
vec![IMPORTED, CLIENT_GENERATED],
)
}
pub fn decrypt_signing_key_by_server(
self,
remote_storage_key: &RemoteStorageKey,
user_id: UserId,
key_id: KeyId,
) -> Result<SigningKeyPair, LockKeeperError> {
self.decrypt(
&remote_storage_key.0,
user_id,
key_id,
vec![IMPORTED, SERVER_GENERATED],
)
}
fn decrypt(
self,
encryption_key: &EncryptionKey,
user_id: UserId,
key_id: KeyId,
possible_context_strings: Vec<&str>,
) -> Result<SigningKeyPair, LockKeeperError> {
let identifying_context = AssociatedData::new().with_bytes(user_id).with_bytes(key_id);
if possible_context_strings
.iter()
.map(|context| identifying_context.clone().with_str(context))
.any(|x| x == self.associated_data)
{
Ok(self.decrypt_inner(encryption_key)?)
} else {
Err(CryptoError::DecryptionFailed.into())
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ZeroizeOnDrop)]
pub struct Import {
key_material: Vec<u8>,
}
impl TryFrom<&[u8]> for Import {
type Error = LockKeeperError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let _signing_key =
ecdsa::SigningKey::try_from(bytes).map_err(|_| CryptoError::ConversionError)?;
Ok(Self {
key_material: bytes.into(),
})
}
}
impl Import {
pub fn new(bytes: Vec<u8>) -> Result<Self, LockKeeperError> {
let import = Self::try_from(bytes.as_slice())?;
Ok(import)
}
pub fn into_signing_key(
self,
user_id: &UserId,
key_id: &KeyId,
) -> Result<SigningKeyPair, LockKeeperError> {
let context = AssociatedData::new()
.with_bytes(user_id.clone())
.with_bytes(key_id.clone())
.with_str(IMPORTED);
let signing_key = SigningPrivateKey::from_bytes(&self.key_material)?;
Ok(SigningKeyPair {
signing_key,
context,
})
}
}
impl From<SigningKeyPair> for Export {
fn from(key_pair: SigningKeyPair) -> Self {
Self {
key_material: key_pair.signing_key.as_bytes(),
context: key_pair.context.into(),
}
}
}
impl Export {
pub fn into_signing_key(self) -> Result<SigningKeyPair, LockKeeperError> {
let signing_key = SigningPrivateKey::from_bytes(&self.key_material)?;
let context = self.context.to_owned().try_into()?;
Ok(SigningKeyPair {
signing_key,
context,
})
}
}
impl TryFrom<SigningKeyPair> for Vec<u8> {
type Error = CryptoError;
fn try_from(key_pair: SigningKeyPair) -> Result<Self, Self::Error> {
let domain_separator_bytes: Vec<u8> = SigningKeyPair::domain_separator().into();
let signing_key = key_pair.signing_key.as_bytes();
let sk_length =
u8::try_from(signing_key.len()).map_err(|_| CryptoError::CannotEncodeDataLength)?;
let context = Vec::<u8>::from(key_pair.context);
let bytes = domain_separator_bytes
.into_iter()
.chain(std::iter::once(sk_length))
.chain(signing_key)
.chain(context)
.collect();
Ok(bytes)
}
}
impl TryFrom<Vec<u8>> for SigningKeyPair {
type Error = CryptoError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let separator_offset = SigningKeyPair::domain_separator().len();
let separator = std::str::from_utf8(
value
.get(0..separator_offset)
.ok_or(CryptoError::ConversionError)?,
)
.map_err(|_| CryptoError::ConversionError)?;
if separator != SigningKeyPair::domain_separator() {
return Err(CryptoError::ConversionError);
}
let signing_key_len = *value
.get(separator_offset)
.ok_or(CryptoError::ConversionError)? as usize;
let signing_key_offset = separator_offset + 1;
let signing_key_end = signing_key_offset + signing_key_len;
let signing_key_bytes = value
.get(signing_key_offset..signing_key_end)
.ok_or(CryptoError::ConversionError)?;
let signing_key = SigningPrivateKey::from_bytes(signing_key_bytes)?;
let context_offset = signing_key_end;
let context_bytes = value
.get(context_offset..)
.ok_or(CryptoError::ConversionError)?
.to_vec();
let context: AssociatedData = context_bytes.try_into()?;
Ok(Self {
signing_key,
context,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use ::std::str::FromStr;
use rand::Rng;
use utilities::crypto::signing_key::{Signature, SigningPublicKey};
use crate::{
crypto::{generic::AssociatedData, KeyId, Signable, StorageKey},
types::database::account::UserId,
LockKeeperError,
};
use k256::ecdsa;
const PUBLIC_KEY: &str = r#"
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEEfw/MOmtobnF36IKi6WcN/sSbP2nrdSE
3bKZV9X0j+bukH19wqtyp+JC6OiKY5E8LQn5bWM7ihBy2+0Tl0mHVQ==
-----END PUBLIC KEY-----
"#;
const PRIVATE_KEY: &str = r#"
-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQg5X2FE2dAPaL6hD6hAN93
6Gd61wZkW5i00WrrIQVt9P2hRANCAAQR/D8w6a2hucXfogqLpZw3+xJs/aet1ITd
splX1fSP5u6QfX3Cq3Kn4kLo6IpjkTwtCfltYzuKEHLb7ROXSYdV
-----END PRIVATE KEY-----
"#;
#[test]
fn public_key_from_pem_works() {
let _ = SigningPublicKey::from_pem(PUBLIC_KEY).unwrap();
}
#[test]
fn public_key_from_pem_can_verify() {
let public_key = SigningPublicKey::from_pem(PUBLIC_KEY).unwrap();
let signing_key = SigningPrivateKey(ecdsa::SigningKey::from_str(PRIVATE_KEY).unwrap());
let message = "hello world!".as_bytes();
let signature = signing_key.sign(message);
public_key.verify(message, &signature).unwrap();
}
#[test]
fn signing_keys_conversion_works() {
let mut rng = rand::thread_rng();
for _ in 0..1000 {
let signing_key = ecdsa::SigningKey::random(&mut rng);
let bytes = signing_key.to_bytes();
let output_key = ecdsa::SigningKey::from_bytes(&bytes).unwrap();
assert_eq!(signing_key, output_key);
let bytes = signing_key.to_bytes();
let output_key = ecdsa::SigningKey::from_bytes(&bytes).unwrap();
assert_eq!(signing_key, output_key);
}
}
#[test]
fn signing_key_to_vec_u8_conversion_works() -> Result<(), CryptoError> {
let mut rng = rand::thread_rng();
for i in 0_i32..1000 {
let context = AssociatedData::new().with_bytes(i.to_le_bytes());
let key = SigningKeyPair::generate(&mut rng, &context);
let vec: Vec<u8> = key.clone().try_into()?;
let output_key = vec.try_into()?;
assert_eq!(key, output_key);
}
Ok(())
}
fn check_context(
mut encrypted_key: Encrypted<SigningKeyPair>,
expected_extra_context: &'static str,
user_id: UserId,
key_id: KeyId,
storage_key: StorageKey,
) -> Result<(), LockKeeperError> {
let mut rng = rand::thread_rng();
assert!(encrypted_key
.clone()
.decrypt_signing_key(storage_key.clone(), user_id.clone(), key_id.clone())
.is_ok());
let bad_user_id = UserId::new(&mut rng)?;
let bad_user_context = AssociatedData::new()
.with_bytes(bad_user_id)
.with_bytes(key_id.clone())
.with_str(expected_extra_context);
encrypted_key.associated_data = bad_user_context;
assert!(encrypted_key
.clone()
.decrypt_signing_key(storage_key.clone(), user_id.clone(), key_id.clone())
.is_err());
let bad_key_id = KeyId::generate(&mut rng, &user_id)?;
let bad_key_context = AssociatedData::new()
.with_bytes(user_id.clone())
.with_bytes(bad_key_id)
.with_str(expected_extra_context);
encrypted_key.associated_data = bad_key_context;
assert!(encrypted_key
.clone()
.decrypt_signing_key(storage_key.clone(), user_id.clone(), key_id.clone())
.is_err());
let bad_extra_context = AssociatedData::new()
.with_bytes(user_id.clone())
.with_bytes(key_id.clone())
.with_str(SERVER_GENERATED);
encrypted_key.associated_data = bad_extra_context;
assert!(encrypted_key
.clone()
.decrypt_signing_key(storage_key.clone(), user_id.clone(), key_id.clone())
.is_err());
let random_context = AssociatedData::new()
.with_bytes(user_id.clone())
.with_bytes(key_id.clone())
.with_str(expected_extra_context)
.with_str("here is some interesting context that will fail to decrypt our key!");
encrypted_key.associated_data = random_context;
assert!(encrypted_key
.decrypt_signing_key(storage_key, user_id, key_id)
.is_err());
Ok(())
}
#[test]
fn client_generated_signing_key_decryption_fails_with_bad_associated_data(
) -> Result<(), LockKeeperError> {
let mut rng = rand::thread_rng();
let storage_key = StorageKey::generate(&mut rng);
let user_id = UserId::new(&mut rng)?;
let key_id = KeyId::generate(&mut rng, &user_id)?;
let (_, encrypted_client_key) =
SigningKeyPair::create_and_encrypt(&mut rng, &storage_key, &user_id, &key_id)?;
check_context(
encrypted_client_key,
CLIENT_GENERATED,
user_id,
key_id,
storage_key,
)
}
#[test]
fn imported_signing_key_decryption_fails_with_bad_associated_data(
) -> Result<(), LockKeeperError> {
let mut rng = rand::thread_rng();
let storage_key = StorageKey::generate(&mut rng);
let user_id = UserId::new(&mut rng)?;
let key_id = KeyId::generate(&mut rng, &user_id)?;
let key_material = ecdsa::SigningKey::random(&mut rng).to_bytes().to_vec();
let (_, encrypted_import_key) = SigningKeyPair::import_and_encrypt(
&key_material,
&mut rng,
&storage_key,
&user_id,
&key_id,
)?;
check_context(encrypted_import_key, IMPORTED, user_id, key_id, storage_key)
}
#[test]
fn import_conversion_works() -> Result<(), LockKeeperError> {
let mut rng = rand::thread_rng();
let user_id = UserId::new(&mut rng)?;
let key_id = KeyId::generate(&mut rng, &user_id)?;
let context = AssociatedData::new()
.with_bytes(user_id.clone())
.with_bytes(key_id.clone())
.with_str(IMPORTED);
let key = SigningKeyPair::generate(&mut rng, &context);
let raw_bytes = key.signing_key.as_bytes();
let import: Import = raw_bytes.as_slice().try_into()?;
let output_key: SigningKeyPair = import.into_signing_key(&user_id, &key_id)?;
assert_eq!(key, output_key);
Ok(())
}
#[test]
fn signing_key_encryption_works() -> Result<(), LockKeeperError> {
let mut rng = rand::thread_rng();
let storage_key = StorageKey::generate(&mut rng);
let user_id = UserId::new(&mut rng)?;
let key_id = KeyId::generate(&mut rng, &user_id)?;
let (signing_key, encrypted_signing_key) =
SigningKeyPair::create_and_encrypt(&mut rng, &storage_key, &user_id, &key_id)?;
let decrypted_signing_key =
encrypted_signing_key.decrypt_signing_key(storage_key, user_id, key_id)?;
assert_eq!(decrypted_signing_key, signing_key);
Ok(())
}
#[test]
fn signing_works() {
let mut rng = rand::thread_rng();
let signing_key = SigningKeyPair::generate(&mut rng, &AssociatedData::new());
let public_key = signing_key.public_key();
assert!((0..1000)
.map(|len| -> Vec<u8> { std::iter::repeat_with(|| rng.gen()).take(len).collect() })
.map(|msg| (msg.sign(&signing_key), msg))
.all(|(sig, msg)| msg.verify(&public_key, &sig).is_ok()));
}
#[test]
fn signature_from_der_works() {
const MESSAGE: &str = "Hello World!";
let mut rng = rand::thread_rng();
let signing_key = SigningKeyPair::generate(&mut rng, &AssociatedData::new());
let signature = signing_key.signing_key.sign(MESSAGE);
let der = signature.to_der();
let signature2 = Signature::from_der(&der).expect("Failed to read from der.");
let public_key = signing_key.public_key();
public_key
.verify(MESSAGE, &signature2)
.expect("Verification failed.");
assert_eq!(
signature, signature2,
"Signature mismatch after conversion."
);
}
#[test]
fn verifying_requires_correct_message() {
let mut rng = rand::thread_rng();
let signing_key = SigningKeyPair::generate(&mut rng, &AssociatedData::new());
let public_key = signing_key.public_key();
let message = b"signatures won't verify with a bad message".to_vec();
let sig = message.sign(&signing_key);
let bad_msg = b"this is obviously not the same message".to_vec();
assert!(bad_msg.verify(&public_key, &sig).is_err());
assert!(message.verify(&public_key, &sig).is_ok());
}
#[test]
fn verifying_requires_correct_public_key() {
let mut rng = rand::thread_rng();
let signing_key = SigningKeyPair::generate(&mut rng, &AssociatedData::new());
let message = b"signatures won't verify with a bad public key".to_vec();
let sig = message.sign(&signing_key);
let bad_key = SigningKeyPair::generate(&mut rng, &AssociatedData::new()).public_key();
assert!(message.verify(&bad_key, &sig).is_err());
assert!(message.verify(&signing_key.public_key(), &sig).is_ok());
}
#[test]
fn signature_bits_cannot_be_flipped() {
let mut rng = rand::thread_rng();
let signing_key = SigningKeyPair::generate(&mut rng, &AssociatedData::new());
let message = b"the signature on this message will get tweaked".to_vec();
let sig = message.sign(&signing_key);
let sig_bytes = sig.0.to_bytes();
for i in 0..sig_bytes.len() {
let mut tweaked = sig_bytes.to_vec();
tweaked[i] ^= 1;
let signature = match k256::ecdsa::Signature::from_slice(&tweaked) {
Ok(sig) => sig,
Err(_) => continue,
};
let tweaked_sig = Signature(signature);
assert!(message
.verify(&signing_key.public_key(), &tweaked_sig)
.is_err());
}
assert!(message.verify(&signing_key.public_key(), &sig).is_ok());
}
#[test]
fn into_signing_key_works() -> Result<(), LockKeeperError> {
let mut rng = rand::thread_rng();
let user_id = UserId::new(&mut rng)?;
let key_id = KeyId::generate(&mut rng, &user_id)?;
let key_material: [u8; 32] = rng.gen();
let import: Import = key_material.as_ref().try_into()?;
let key_pair = import.into_signing_key(&user_id, &key_id)?;
let bytes: Vec<u8> = key_pair.try_into()?;
assert!(bytes.windows(32).any(|c| c == key_material));
let not_enough_key_material: [u8; 12] = rng.gen();
let short_import: Result<Import, _> = not_enough_key_material.as_ref().try_into();
assert!(short_import.is_err());
let too_much_key_material: Vec<u8> =
std::iter::repeat_with(|| rng.gen()).take(64).collect();
let long_import: Result<Import, _> = too_much_key_material.as_slice().try_into();
assert!(long_import.is_err());
Ok(())
}
#[test]
fn import_and_encrypt_encrypts_correct_key() -> Result<(), LockKeeperError> {
let mut rng = rand::thread_rng();
let storage_key = StorageKey::generate(&mut rng);
let user_id = UserId::new(&mut rng)?;
let key_id = KeyId::generate(&mut rng, &user_id)?;
let key_material = ecdsa::SigningKey::random(&mut rng).to_bytes().to_vec();
let (key, encrypted_key) = SigningKeyPair::import_and_encrypt(
&key_material,
&mut rng,
&storage_key,
&user_id,
&key_id,
)?;
let decrypted_key = encrypted_key.decrypt_signing_key(storage_key, user_id, key_id)?;
assert_eq!(key, decrypted_key);
let bytes: Vec<u8> = key.try_into()?;
assert!(bytes.windows(32).any(|c| c == key_material));
Ok(())
}
#[test]
fn keys_are_labelled_with_origin() -> Result<(), LockKeeperError> {
let mut rng = rand::thread_rng();
let storage_key = StorageKey::generate(&mut rng);
let user_id = UserId::new(&mut rng)?;
let key_id = KeyId::generate(&mut rng, &user_id)?;
let contains_str = |container: SigningKeyPair, subset: &'static str| -> bool {
let container_ad: Vec<u8> = container.context().to_owned().into();
let subset: Vec<u8> = subset.as_bytes().into();
container_ad.windows(subset.len()).any(|c| c == subset)
};
let (secret, _) =
SigningKeyPair::create_and_encrypt(&mut rng, &storage_key, &user_id, &key_id)?;
assert!(!contains_str(secret.clone(), IMPORTED));
assert!(!contains_str(secret.clone(), SERVER_GENERATED));
assert!(contains_str(secret, CLIENT_GENERATED));
let secret = SigningKeyPair::remote_generate(&mut rng, &user_id, &key_id);
assert!(!contains_str(secret.clone(), IMPORTED));
assert!(!contains_str(secret.clone(), CLIENT_GENERATED));
assert!(contains_str(secret, SERVER_GENERATED));
let key_material = ecdsa::SigningKey::random(&mut rng).to_bytes();
let (imported_secret, _) = SigningKeyPair::import_and_encrypt(
&key_material,
&mut rng,
&storage_key,
&user_id,
&key_id,
)?;
assert!(!contains_str(imported_secret.clone(), CLIENT_GENERATED));
assert!(!contains_str(imported_secret.clone(), SERVER_GENERATED));
assert!(contains_str(imported_secret, IMPORTED));
let import: Import = key_material.as_slice().try_into()?;
let key_pair = import.into_signing_key(&user_id, &key_id)?;
assert!(!contains_str(key_pair.clone(), CLIENT_GENERATED));
assert!(!contains_str(key_pair.clone(), SERVER_GENERATED));
assert!(contains_str(key_pair, IMPORTED));
Ok(())
}
impl Signable for Vec<u8> {
fn sign(&self, signing_key: &SigningKeyPair) -> Signature {
signing_key.signing_key.sign(self)
}
fn verify(
&self,
public_key: &SigningPublicKey,
signature: &Signature,
) -> Result<(), CryptoError> {
(&self).verify(public_key, signature)
}
}
impl Signable for &Vec<u8> {
fn sign(&self, signing_key: &SigningKeyPair) -> Signature {
signing_key.signing_key.sign(self)
}
fn verify(
&self,
public_key: &SigningPublicKey,
signature: &Signature,
) -> Result<(), CryptoError> {
public_key.verify(self, signature)
}
}
}