This repository has been archived on 2025-08-14. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
subscribe-list/src/keys.rs
2024-04-08 23:39:40 -07:00

139 lines
4 KiB
Rust

use crate::models::{Keys, OnlyZero};
use core::fmt;
use diesel::{
expression::TypedExpressionType, sql_types::SqlType, Connection, RunQueryDsl, SqliteConnection,
};
use ed25519_dalek::{
ed25519::{signature::rand_core::OsRng, SignatureEncoding},
SignatureError, Signer, Verifier,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_with::{
base64::{Base64, UrlSafe},
formats::Unpadded,
serde_as,
};
use std::marker::PhantomData;
#[derive(Copy, Clone)]
pub struct Key<T>(T);
impl<T> fmt::Debug for Key<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Key([redacted])")
}
}
impl<T> Key<T> {
pub fn new(v: T) -> Self {
Self(v)
}
pub fn secret(&self) -> &T {
&self.0
}
}
impl<ST, DB: diesel::backend::Backend> diesel::Queryable<ST, DB> for Key<ed25519_dalek::SigningKey>
where
Vec<u8>: diesel::Queryable<ST, DB>,
{
type Row = <Vec<u8> as diesel::Queryable<ST, DB>>::Row;
fn build(row: Self::Row) -> diesel::deserialize::Result<Self> {
let v = <Vec<u8> as diesel::Queryable<ST, DB>>::build(row)?;
Ok(Key(ed25519_dalek::SigningKey::try_from(&*v)?))
}
}
impl<T: SqlType + TypedExpressionType> diesel::expression::AsExpression<T>
for Key<ed25519_dalek::SigningKey>
where
Vec<u8>: diesel::expression::AsExpression<T>,
{
type Expression = <Vec<u8> as diesel::expression::AsExpression<T>>::Expression;
fn as_expression(self) -> Self::Expression {
Vec::<u8>::from(self.0.to_bytes()).as_expression()
}
}
impl<'a, T: SqlType + TypedExpressionType> diesel::expression::AsExpression<T>
for &'a Key<ed25519_dalek::SigningKey>
where
&'a [u8]: diesel::expression::AsExpression<T>,
{
type Expression = <&'a [u8] as diesel::expression::AsExpression<T>>::Expression;
fn as_expression(self) -> Self::Expression {
self.0.as_bytes()[..].as_expression()
}
}
impl<'a, T: SqlType + TypedExpressionType> diesel::expression::AsExpression<T>
for &'a &'_ Key<ed25519_dalek::SigningKey>
where
&'a [u8]: diesel::expression::AsExpression<T>,
{
type Expression = <&'a [u8] as diesel::expression::AsExpression<T>>::Expression;
fn as_expression(self) -> Self::Expression {
self.0.as_bytes()[..].as_expression()
}
}
pub fn get_or_make_keys(db: &mut SqliteConnection) -> eyre::Result<Keys> {
db.transaction(|db| -> eyre::Result<Keys> {
let keys: Option<Keys> = crate::schema::keys::dsl::keys.load(db)?.get(0).cloned();
if let Some(keys) = keys {
return Ok(keys);
}
let keys = Keys {
id: OnlyZero::Zero,
email_unsubscribe_url: Key::new(ed25519_dalek::SigningKey::generate(&mut OsRng)),
};
diesel::insert_into(crate::schema::keys::dsl::keys)
.values(keys.clone())
.execute(db)?;
Ok(keys)
})
}
#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct Signed<T, S: SignatureEncoding> {
#[serde(rename = "v")]
value: String,
#[serde(rename = "s", bound(deserialize = "S::Repr: TryFrom<Vec<u8>>"))]
#[serde_as(as = "Base64<UrlSafe, Unpadded>")]
signature: S::Repr,
#[serde(skip)]
_phantom: PhantomData<(fn() -> T, S)>,
}
impl<T: Serialize + DeserializeOwned, S: SignatureEncoding> Signed<T, S> {
pub fn sign<K>(value: &T, key: &K) -> Result<Self, serde_json::Error>
where
K: Signer<S>,
{
let value = serde_json::to_string(value)?;
let signature = key.sign(value.as_bytes()).to_bytes();
Ok(Self {
value,
signature,
_phantom: PhantomData,
})
}
pub fn verify<K>(&self, key: &K) -> eyre::Result<Result<T, SignatureError>>
where
K: Verifier<S>,
for<'a> eyre::Report: From<<S as TryFrom<&'a [u8]>>::Error>,
{
if let Err(e) = key.verify(
&self.value.as_bytes(),
&S::try_from(self.signature.as_ref())?,
) {
return Ok(Err(e));
}
Ok(Ok(serde_json::from_str(&self.value)?))
}
}