use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD};
use secrecy::{ExposeSecret, SecretString};
use strong_box::{SharedStrongBox, SharedStrongBoxKey, StrongBox};
mod error;
pub use error::Error;
const PUBLIC_KEY_PREFIX: &str = "sspuk:";
const PRIVATE_KEY_PREFIX: &str = "ssprk:";
pub fn make_key() -> Result<(PrvKey, PubKey), Error> {
let key = SharedStrongBox::generate_key();
Ok((
PrvKey(format!(
"{}{}",
PRIVATE_KEY_PREFIX,
BASE64_URL_SAFE_NO_PAD.encode(
key.private()
.ok_or_else(|| Error::invalid_key(
"SHOULDN'T HAPPEN: new key does not have privates"
))?
.expose_secret()
)
)),
PubKey(format!(
"{}{}",
PUBLIC_KEY_PREFIX,
BASE64_URL_SAFE_NO_PAD.encode(key.public())
)),
))
}
pub fn encrypt(plaintext: &str, ctx: &str, key: &str) -> Result<String, Error> {
let keydata = key
.strip_prefix(PUBLIC_KEY_PREFIX)
.ok_or_else(|| Error::invalid_key("incorrect prefix"))?;
#[allow(clippy::shadow_unrelated)] let key = SharedStrongBoxKey::try_from(&BASE64_URL_SAFE_NO_PAD.decode(keydata)?)?;
let strong_box = SharedStrongBox::new(key);
Ok(BASE64_URL_SAFE_NO_PAD.encode(strong_box.encrypt(plaintext.as_bytes(), ctx.as_bytes())?))
}
pub fn decrypt(ciphertext: &str, ctx: &str, key: &SecretString) -> Result<String, Error> {
let ciphertext = BASE64_URL_SAFE_NO_PAD.decode(ciphertext)?;
let keydata = key
.expose_secret()
.strip_prefix(PRIVATE_KEY_PREFIX)
.ok_or_else(|| Error::invalid_key("incorrect prefix"))?;
#[allow(clippy::shadow_unrelated)] let key = SharedStrongBoxKey::try_from(&BASE64_URL_SAFE_NO_PAD.decode(keydata)?)?;
let strong_box = SharedStrongBox::new(key);
Ok(String::from_utf8(
strong_box.decrypt(ciphertext, ctx.as_bytes())?,
)?)
}
#[allow(missing_debug_implementations)]
pub struct PrvKey(String);
#[derive(Debug)]
pub struct PubKey(String);
impl std::ops::Deref for PrvKey {
type Target = String;
fn deref(&self) -> &String {
&self.0
}
}
impl std::ops::Deref for PubKey {
type Target = String;
fn deref(&self) -> &String {
&self.0
}
}