brycx / pasetors

PASETOrs: PASETO tokens in pure Rust
MIT License
91 stars 10 forks source link

Make keys generic over version #25

Closed not-my-profile closed 2 years ago

not-my-profile commented 2 years ago

Turns version mismatch errors from runtime errors into compile-time errors, giving us compile-time algorithm lucidity.

brycx commented 2 years ago

I've been playing around with this for a bit. I really like this approach, thanks so much for submitting it. I do have some suggested additions to this (playground link):

Suggested additions to keys ```rust use crate::errors::Errors; use crate::keys2::try_again::prive::{KeyVersion}; use std::marker::PhantomData; /// The private trait approach. /// /// All functional logic for how keys are handled for each version belong to the structs. mod prive { use crate::errors::Errors; /// A given key pub trait KeyVersion { /// Create key from `bytes`. fn new_local(bytes: &[u8]) -> Result, Errors>; /// Create key from `bytes`. fn new_public(bytes: &[u8]) -> Result, Errors>; /// Create key from `bytes`. fn new_secret(bytes: &[u8]) -> Result, Errors>; } } /// It's not guaranteed that future keys may be randomly generated, nor can all keys be gen'ed. /// So we use a separate trait for this. /// /// This need not be private since it requires a KeyVersion which can't be implemented by a user. /// But a user COULD implement Generate for a V* that shouldn't be generatable. pub trait Generate { // Randomly generate key. fn gen() -> Result where Self: Sized; } #[derive(Debug)] // TODO: Delete pub struct V2 {} impl KeyVersion for V2 { fn new_local(bytes: &[u8]) -> Result, Errors> { if bytes.len() != 32 { return Err(Errors::KeyError); } Ok(bytes.to_vec()) } fn new_public(bytes: &[u8]) -> Result, Errors> { Self::new_local(bytes) } fn new_secret(bytes: &[u8]) -> Result, Errors> { Self::new_local(bytes) } } #[derive(Debug)] // TODO: Delete pub struct V4 {} impl KeyVersion for V4 { fn new_local(bytes: &[u8]) -> Result, Errors> { if bytes.len() != 32 { return Err(Errors::KeyError); } Ok(bytes.to_vec()) } fn new_public(bytes: &[u8]) -> Result, Errors> { Self::new_local(bytes) } fn new_secret(bytes: &[u8]) -> Result, Errors> { Self::new_local(bytes) } } #[derive(Debug)] // TODO: Delete /// A symmetric key used for `.local` tokens. pub struct SymmetricKey { bytes: Vec, phantom: PhantomData, } impl SymmetricKey { pub fn new(input: impl AsRef<[u8]>) -> Result { Ok(Self { bytes: V::new_local(input.as_ref())?, phantom: PhantomData, }) } } impl Generate> for SymmetricKey{ fn gen() -> Result { let mut rng_bytes = vec![0u8; 32]; getrandom::getrandom(&mut rng_bytes)?; Self::new(rng_bytes) } } impl Generate> for SymmetricKey{ fn gen() -> Result { let mut rng_bytes = vec![0u8; 32]; getrandom::getrandom(&mut rng_bytes)?; Self::new(rng_bytes) } } #[derive(Debug)] // TODO: Delete /// An asymmetric secret key used for `.public` tokens. pub struct AsymmetricSecretKey { bytes: Vec, phantom: PhantomData, } impl AsymmetricSecretKey { pub fn new(input: impl AsRef<[u8]>) -> Result { Ok(Self { bytes: V::new_secret(input.as_ref())?, phantom: PhantomData, }) } } impl Generate> for AsymmetricSecretKey{ fn gen() -> Result { let mut rng_bytes = vec![0u8; 32]; getrandom::getrandom(&mut rng_bytes)?; Self::new(rng_bytes) } } impl Generate> for AsymmetricSecretKey{ fn gen() -> Result { let mut rng_bytes = vec![0u8; 32]; getrandom::getrandom(&mut rng_bytes)?; Self::new(rng_bytes) } } #[derive(Debug)] // TODO: Delete /// An asymmetric public key used for `.public` tokens. pub struct AsymmetricPublicKey { bytes: Vec, phantom: PhantomData, } impl AsymmetricPublicKey { pub fn new(input: impl AsRef<[u8]>) -> Result { Ok(Self { bytes: V::new_public(input.as_ref())?, phantom: PhantomData, }) } } } ```

This does add more boilerplate, but doesn't restrict the logic for the key-types as much. Do you have any thoughts/concerns on this @not-my-profile?

Note: I don't expect you to change your PR. I can get a new one set up if needed, that's no problem.

not-my-profile commented 2 years ago

I really like this approach, thanks so much for submitting it.

You're welcome :)

Before, I couldn't implement eg. SymmetricKey for a V6, with the same API, because it was already defined for V2orV4.

I think you are mistaken. The following compiles just fine. (playground) (That's because it is impossible for a crate to implement a foreign trait for a foreign type).

pub struct V2;
pub struct V4;
pub struct V6;

pub trait V2orV4 {}
impl V2orV4 for V2 {}
impl V2orV4 for V4 {}

pub struct SymmetricKey<V> {
    bytes: Vec<u8>,
    phantom: V,
}

impl<V: V2orV4> SymmetricKey<V> {
    pub fn gen() -> Self {
        todo!();
    }
}

impl SymmetricKey<V6> {
    pub fn gen() -> Self {
        todo!();
    }
}

It's not guaranteed that future keys may be randomly generated, nor can all keys be gen'ed. So we use a separate trait for this.

I'm a bit confused by this, what are examples of keys that cannot be generated?

Sidenote: Did you mean to link a different playground? Yours doesn't compile.

brycx commented 2 years ago

I think you are mistaken. The following compiles just fine. (playground) (That's because it is impossible for a crate to implement a foreign trait for a foreign type).

Strange. I thought it was weird, I must've made a mistake then. Well, in that case, your initial approach is much simpler.

brycx commented 2 years ago

I'm a bit confused by this, what are examples of keys that cannot be generated?

This was before you pointed out me missing something. But, it was to avoid defining gen() in KeyVersion which would mean an AsymmetricPublicKey would have a gen() function as well.

Sidenote: Did you mean to link a different playground? Yours doesn't compile.

No, but I must've forgotten to remove some invalid imports for playground.

brycx commented 2 years ago

Added in #31