use crate::{Error, Id, PublicKey, Signature};
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
use bech32::{FromBase32, ToBase32};
use hmac::Hmac;
use k256::schnorr::SigningKey;
use pbkdf2::pbkdf2;
use rand_core::{OsRng, RngCore};
use sha2::Sha256;
use std::convert::TryFrom;
use zeroize::Zeroize;
const CHECK_VALUE: [u8; 11] = [15, 91, 241, 148, 90, 143, 101, 12, 172, 255, 103];
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum KeySecurity {
Weak = 0,
Medium = 1,
}
impl TryFrom<u8> for KeySecurity {
type Error = Error;
fn try_from(i: u8) -> Result<KeySecurity, Error> {
if i == 0 {
Ok(KeySecurity::Weak)
} else if i == 1 {
Ok(KeySecurity::Medium)
} else {
Err(Error::UnknownKeySecurity(i))
}
}
}
#[allow(missing_debug_implementations)]
pub struct PrivateKey(SigningKey, KeySecurity);
impl PrivateKey {
pub fn generate() -> PrivateKey {
let signing_key = SigningKey::random(&mut OsRng);
PrivateKey(signing_key, KeySecurity::Medium)
}
pub fn public_key(&self) -> PublicKey {
PublicKey(self.0.verifying_key().to_owned())
}
pub fn key_security(&self) -> KeySecurity {
self.1
}
pub fn as_hex_string(&mut self) -> String {
self.1 = KeySecurity::Weak;
hex::encode(self.0.to_bytes())
}
pub fn try_from_hex_string(v: &str) -> Result<PrivateKey, Error> {
let vec: Vec<u8> = hex::decode(v)?;
Ok(PrivateKey(SigningKey::from_bytes(&vec)?, KeySecurity::Weak))
}
pub fn try_as_bech32_string(&mut self) -> Result<String, Error> {
self.1 = KeySecurity::Weak;
Ok(bech32::encode(
"nsec",
self.0.to_bytes().to_vec().to_base32(),
bech32::Variant::Bech32,
)?)
}
pub fn try_from_bech32_string(s: &str) -> Result<PrivateKey, Error> {
let data = bech32::decode(s)?;
if data.0 != "nsec" {
Err(Error::WrongBech32("nsec".to_string(), data.0))
} else {
let decoded = Vec::<u8>::from_base32(&data.1)?;
Ok(PrivateKey(
SigningKey::from_bytes(&decoded)?,
KeySecurity::Weak,
))
}
}
pub fn sign_id(&self, id: Id) -> Result<Signature, Error> {
let mut rand: [u8; 32] = [0; 32];
OsRng.fill_bytes(&mut rand);
let signature = self.0.try_sign_prehashed(&id.0, &rand)?;
Ok(Signature(signature))
}
pub fn export_encrypted(&self, password: &str) -> Result<String, Error> {
let key = Self::password_to_key(password)?;
let mut iv: [u8; 16] = [0; 16];
OsRng.fill_bytes(&mut iv);
let mut inner_secret: Vec<u8> = self.0.to_bytes().to_vec();
inner_secret.extend(CHECK_VALUE); inner_secret.push(self.1 as u8);
let ciphertext = cbc::Encryptor::<aes::Aes256>::new(&key.into(), &iv.into())
.encrypt_padded_vec_mut::<Pkcs7>(&inner_secret);
inner_secret.zeroize();
let mut iv_plus_ciphertext: Vec<u8> = Vec::new();
iv_plus_ciphertext.extend(iv);
iv_plus_ciphertext.extend(ciphertext);
Ok(base64::encode(iv_plus_ciphertext))
}
pub fn import_encrypted(encrypted: &str, password: &str) -> Result<PrivateKey, Error> {
let key = Self::password_to_key(password)?;
let iv_plus_ciphertext = base64::decode(encrypted)?;
if iv_plus_ciphertext.len() < 48 {
return Err(Error::InvalidEncryptedPrivateKey);
}
let iv: [u8; 16] = iv_plus_ciphertext[..16].try_into()?;
let ciphertext = &iv_plus_ciphertext[16..];
let mut pt = cbc::Decryptor::<aes::Aes256>::new(&key.into(), &iv.into())
.decrypt_padded_vec_mut::<Pkcs7>(ciphertext)?;
if pt[pt.len() - 12..pt.len() - 1] != CHECK_VALUE {
return Err(Error::WrongDecryptionPassword);
}
let ks = KeySecurity::try_from(pt[pt.len() - 1])?;
let output = PrivateKey(SigningKey::from_bytes(&pt[..pt.len() - 12])?, ks);
pt.zeroize();
Ok(output)
}
fn password_to_key(password: &str) -> Result<[u8; 32], Error> {
let salt = b"nostr";
let mut key: [u8; 32] = [0; 32];
pbkdf2::<Hmac<Sha256>>(password.as_bytes(), salt, 4096, &mut key);
Ok(key)
}
#[allow(dead_code)]
pub(crate) fn mock() -> PrivateKey {
PrivateKey::generate()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_export_import() {
let pk = PrivateKey::generate();
let exported = pk.export_encrypted("secret").unwrap();
println!("{}", exported);
let imported_pk = PrivateKey::import_encrypted(&exported, "secret").unwrap();
assert_eq!(pk.public_key(), imported_pk.public_key());
assert_eq!(pk.key_security(), KeySecurity::Medium)
}
#[test]
fn test_bad_password() {
let pk = PrivateKey::generate();
let exported = pk.export_encrypted("rightsecret").unwrap();
assert!(PrivateKey::import_encrypted(&exported, "wrongsecret").is_err());
}
#[test]
fn test_privkey_bech32() {
let mut pk = PrivateKey::mock();
let encoded = pk.try_as_bech32_string().unwrap();
println!("bech32: {}", encoded);
let decoded = PrivateKey::try_from_bech32_string(&encoded).unwrap();
assert_eq!(pk.0.to_bytes(), decoded.0.to_bytes());
assert_eq!(decoded.1, KeySecurity::Weak);
}
}