use libsodium_rs::{crypto_sign, random};
use paseto_core::PasetoError;
use paseto_core::key::HasKey;
use paseto_core::pae::pre_auth_encode;
use paseto_core::version::{Public, Secret};
use super::{PublicKey, SecretKey, V4};
impl HasKey<Public> for V4 {
type Key = PublicKey;
fn decode(bytes: &[u8]) -> Result<PublicKey, PasetoError> {
crypto_sign::PublicKey::from_bytes(bytes)
.map(PublicKey)
.map_err(|_| PasetoError::InvalidKey)
}
fn encode(key: &PublicKey) -> Box<[u8]> {
key.0.as_bytes().to_vec().into_boxed_slice()
}
}
impl HasKey<Secret> for V4 {
type Key = SecretKey;
fn decode(bytes: &[u8]) -> Result<SecretKey, PasetoError> {
crypto_sign::SecretKey::from_bytes(bytes)
.map(SecretKey)
.map_err(|_| PasetoError::InvalidKey)
}
fn encode(key: &SecretKey) -> Box<[u8]> {
key.0.as_bytes().to_vec().into_boxed_slice()
}
}
impl SecretKey {
pub(crate) fn unsealing_key(&self) -> PublicKey {
let public_key = self
.0
.as_bytes()
.last_chunk()
.expect("secret key ends with the public key");
PublicKey(crypto_sign::PublicKey::from_bytes_exact(*public_key))
}
}
impl paseto_core::version::SealingVersion<Public> for V4 {
fn unsealing_key(key: &SecretKey) -> PublicKey {
key.unsealing_key()
}
fn random() -> Result<SecretKey, PasetoError> {
let mut secret_key = [0; 32];
loop {
random::fill_bytes(&mut secret_key);
if let Ok(key) = crypto_sign::keypair_from_seed(&secret_key) {
break Ok(SecretKey(key.secret_key));
}
}
}
fn nonce() -> Result<[u8; 0], PasetoError> {
Ok([])
}
fn dangerous_seal_with_nonce(
key: &SecretKey,
encoding: &'static str,
mut payload: Vec<u8>,
footer: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, PasetoError> {
let bytes = preauth_public(encoding, &payload, footer, aad);
let sig =
crypto_sign::sign_detached(&bytes, &key.0).map_err(|_| PasetoError::CryptoError)?;
payload.extend_from_slice(&sig);
Ok(payload)
}
}
impl paseto_core::version::UnsealingVersion<Public> for V4 {
type Nonce = [u8; 0];
type Tag = [u8; 64];
fn unseal<'a>(
key: &PublicKey,
encoding: &'static str,
payload: &'a mut [u8],
footer: &[u8],
aad: &[u8],
) -> Result<&'a [u8], PasetoError> {
let (cleartext, tag) = payload
.split_last_chunk::<64>()
.ok_or(PasetoError::InvalidToken)?;
let bytes = preauth_public(encoding, cleartext, footer, aad);
if !crypto_sign::verify_detached(tag, &bytes, &key.0) {
return Err(PasetoError::CryptoError);
}
Ok(cleartext)
}
}
fn preauth_public(encoding: &'static str, cleartext: &[u8], footer: &[u8], aad: &[u8]) -> Vec<u8> {
use paseto_core::key::KeyType;
let mut out = Vec::new();
pre_auth_encode(
[
&[
"v4".as_bytes(),
encoding.as_bytes(),
Public::HEADER.as_bytes(),
],
&[cleartext],
&[footer],
&[aad],
],
&mut out,
);
out
}