sentc-crypto-core 0.15.0

The core of sentc encryption sdk with all alg.
Documentation
use alloc::vec::Vec;

use sha2::{Digest, Sha256};

use crate::cryptomat::{
	ClientRandomValue,
	CryptoAlg,
	DeriveAuthKeyForAuth,
	DeriveMasterKeyForAuth,
	HashedAuthenticationKey,
	Pk,
	PwHash,
	SignK,
	SignKeyComposer,
	SignKeyPair,
	Sk,
	SkComposer,
	StaticKeyPair,
	SymKeyGen,
	VerifyK,
};
use crate::error::Error;

pub struct RegisterOutPut<P: Pk, V: VerifyK, CRV: ClientRandomValue, HAK: HashedAuthenticationKey>
{
	//info about the raw master key (not the encrypted by the password!)
	pub master_key_alg: &'static str,

	//from key derived
	pub client_random_value: CRV,
	pub hashed_authentication_key_bytes: HAK,
	pub encrypted_master_key: Vec<u8>,
	pub encrypted_master_key_alg: &'static str,
	pub derived_alg: &'static str,

	//the key pairs incl. the encrypted secret keys
	pub public_key: P,
	pub encrypted_private_key: Vec<u8>,
	pub keypair_encrypt_alg: &'static str,
	pub verify_key: V,
	pub encrypted_sign_key: Vec<u8>,
	pub keypair_sign_alg: &'static str,
}

pub struct LoginDoneOutput<S: Sk, Si: SignK>
{
	pub private_key: S,
	pub sign_key: Si,
}

pub struct ChangePasswordOutput<CRV: ClientRandomValue, HAK: HashedAuthenticationKey, DAK: DeriveAuthKeyForAuth>
{
	pub client_random_value: CRV,
	pub hashed_authentication_key_bytes: HAK,
	pub encrypted_master_key: Vec<u8>,
	pub encrypted_master_key_alg: &'static str,
	pub derived_alg: &'static str,
	pub old_auth_key: DAK,
}

pub struct ResetPasswordOutput<CRV: ClientRandomValue, HAK: HashedAuthenticationKey>
{
	pub master_key_alg: &'static str,
	pub client_random_value: CRV,
	pub hashed_authentication_key_bytes: HAK,
	pub encrypted_master_key: Vec<u8>,
	pub encrypted_master_key_alg: &'static str,
	pub derived_alg: &'static str,
	pub encrypted_private_key: Vec<u8>,
	pub encrypted_sign_key: Vec<u8>,
}

pub struct PrepareLoginOutput<DMK: DeriveMasterKeyForAuth, DAK: DeriveAuthKeyForAuth>
{
	pub master_key_encryption_key: DMK,
	pub auth_key: DAK,
}

/**

# Register a new user

Every user got a master key, every other user key is encrypted by this master key.

The master key is encrypted by a derived key from password. The password turned into two different keys via a password hash.
The first key is the derived key and encrypts the master key. The second key is the authentication for the server.
Only this key gets send to the server not the password.

A public / private encryption key pair and a sign / verify key pair get created too.

Both private and sign key gets encrypted by the master key.

<br>

## For key update:
Just register the user again with a password, it should be the same password for usability, but it can a different password too.
Then login again and encrypt everything what was encrypted by the old keys with the new keys
(e.g. group keys (encrypted by public key), or direct encrypted data).
*/
#[allow(clippy::type_complexity)]
pub fn register<S: SymKeyGen, St: StaticKeyPair, Sign: SignKeyPair, H: PwHash>(
	password: &str,
) -> Result<RegisterOutPut<St::PublicKey, Sign::VerifyKey, H::CRV, H::HAK>, Error>
{
	//1. create master key
	let master_key = S::generate()?;

	//2. create static pub/pri key pair for encrypt and decrypt
	let (sk, public_key) = St::generate_static_keypair()?;

	//3. create sign key pair for sign and verify
	let (sign_k, verify_key) = Sign::generate_key_pair()?;

	let encrypted_private_key = sk.encrypt_by_master_key(&master_key)?;
	let encrypted_sign_key = sign_k.encrypt_by_master_key(&master_key)?;

	let (client_random_value, hashed_authentication_key_bytes, encrypted_master_key, encrypted_master_key_alg) =
		H::derived_keys_from_password(password.as_bytes(), &master_key, None)?;

	Ok(RegisterOutPut {
		master_key_alg: master_key.get_alg_str(),
		derived_alg: client_random_value.get_alg_str(),
		client_random_value,
		hashed_authentication_key_bytes,
		encrypted_master_key,
		encrypted_master_key_alg,
		encrypted_sign_key,
		verify_key,
		keypair_sign_alg: sign_k.get_alg_str(),
		encrypted_private_key,
		public_key,
		keypair_encrypt_alg: sk.get_alg_str(),
	})
}

/**

# Start the login process

The salt was generated by the server when the user first interact with the server.
*/
pub fn prepare_login<H: PwHash>(password: &str, salt: &[u8], derived_encryption_key_alg: &str) -> Result<PrepareLoginOutput<H::DMK, H::DAK>, Error>
{
	let (master_key_encryption_key, auth_key) = H::derive_keys_for_auth(password.as_bytes(), salt, derived_encryption_key_alg)?;

	Ok(PrepareLoginOutput {
		master_key_encryption_key,
		auth_key,
	})
}

/**

# End the login process

Get all information about the current used login keys
*/
pub fn done_login<SkC: SkComposer, SiC: SignKeyComposer>(
	derived_encryption_key: &impl DeriveMasterKeyForAuth, //the value from prepare_login
	encrypted_master_key: &[u8],                          //as base64 encoded string from the server
	encrypted_private_key: &[u8],
	keypair_encrypt_alg: &str,
	encrypted_sign_key: &[u8],
	keypair_sign_alg: &str,
) -> Result<LoginDoneOutput<SkC::SecretKey, SiC::Key>, Error>
{
	//decrypt the master key from the derived key from password
	let master_key = derived_encryption_key.get_master_key(encrypted_master_key)?;

	//decode the private keys to the enums to use them later
	let private_key = SkC::decrypt_by_master_key(&master_key, encrypted_private_key, keypair_encrypt_alg)?;
	let sign_key = SiC::decrypt_by_master_key(&master_key, encrypted_sign_key, keypair_sign_alg)?;

	Ok(LoginDoneOutput {
		private_key,
		sign_key,
	})
}

/**

# Prepare Password change

before calling this function make a request to the login api endpoint with the username
to get the salt from the api
if the old pw was wrong this will check in the api later after this function

get the old auth key from @see prepare_login()
with the old pw and the old salt

decrypt the master key with the old pw (because it is stored in the client but encrypted)

use the function @see derivedKeysFromPassword with the new pw and the decrypted master key
to create the new client random value, the new encrypted master key and the new hashed authkey

send the return data back to the server
*/
#[allow(clippy::type_complexity)]
pub fn change_password<H: PwHash>(
	old_pw: &str,
	new_pw: &str,
	old_salt: &[u8],
	encrypted_master_key: &[u8],
	derived_encryption_key_alg: &str,
) -> Result<ChangePasswordOutput<H::CRV, H::HAK, H::DAK>, Error>
{
	//first make a request to login endpoint -> prepareLogin() with the username to get the salt
	//get the old auth key
	let prepare_login_output = prepare_login::<H>(old_pw, old_salt, derived_encryption_key_alg)?;

	//decrypt the master key with the old pw.
	let master_key = prepare_login_output
		.master_key_encryption_key
		.get_master_key(encrypted_master_key)?;

	//encrypt the master key with the new pw and create a new salt with a new random value
	//the 2nd check is necessary because master key from different alg can have different length
	let (client_random_value, hashed_authentication_key_bytes, encrypted_master_key, encrypted_master_key_alg) =
		H::derived_keys_from_password(new_pw.as_bytes(), &master_key, Some(derived_encryption_key_alg))?;

	Ok(ChangePasswordOutput {
		derived_alg: client_random_value.get_alg_str(),
		client_random_value,
		hashed_authentication_key_bytes,
		encrypted_master_key,
		encrypted_master_key_alg,
		old_auth_key: prepare_login_output.auth_key,
	})
}

/**

# Reset the users password

Only works if the user still got access to the decrypted private key and the decrypted sign key (which are encrypted by the old password).

1. create a new master key like register()
2. encrypt the private and sign keys with the new master key. Use here the alg from the actual selected feature
3. finally encrypt the new master key by the new password

## Security hint:
- the server still awaits a valid auth token when sending the reset password request
- an attacker needs access to the decrypted private key, decrypted sign key and the auth token
*/
pub fn password_reset<S: SymKeyGen, H: PwHash>(
	new_pw: &str,
	decrypted_private_key: &impl Sk,
	decrypted_sign_key: &impl SignK,
) -> Result<ResetPasswordOutput<H::CRV, H::HAK>, Error>
{
	//1. create a new master key (because the old key was encrypted by the forgotten password and can't be restored)
	let master_key = S::generate()?;

	//2. encrypt the private and the sign key with the new master key
	let encrypted_private_key = decrypted_private_key.encrypt_by_master_key(&master_key)?;
	let encrypted_sign_key = decrypted_sign_key.encrypt_by_master_key(&master_key)?;

	//3. encrypt the new master key with the new password
	let (client_random_value, hashed_authentication_key_bytes, encrypted_master_key, encrypted_master_key_alg) =
		H::derived_keys_from_password(new_pw.as_bytes(), &master_key, None)?;

	Ok(ResetPasswordOutput {
		master_key_alg: master_key.get_alg_str(),
		derived_alg: client_random_value.get_alg_str(),
		client_random_value,
		hashed_authentication_key_bytes,
		encrypted_master_key,
		encrypted_master_key_alg,
		encrypted_private_key,
		encrypted_sign_key,
	})
}

/**

Creates a safety number in byte of a given verify key and additional user information like the user id or username.

## combined number

To create a combination of two identities set for user_2 another SafetyNumberUser struct.
Make sure to keep the order of user_1 and user_2 on the other user too, otherwise the number will not be the same.
 */
pub fn safety_number<Vk: VerifyK>(
	user_1_verify_key: &Vk,
	user_1_user_info: &str,
	user_2_verify_key: Option<&Vk>,
	user_2_user_info: Option<&str>,
) -> Vec<u8>
{
	let mut hasher = Sha256::new();

	user_1_verify_key.create_hash(&mut hasher);

	hasher.update(user_1_user_info.as_bytes());

	if let (Some(u_2), Some(u_2_i)) = (user_2_verify_key, user_2_user_info) {
		u_2.create_hash(&mut hasher);

		hasher.update(u_2_i.as_bytes());
	}

	let number_bytes = hasher.finalize();

	number_bytes.to_vec()
}