willow25 0.7.0

A ready-to-use implementation of the Willow specifications.
Documentation
use core::fmt;

use alloc::vec::Vec;

#[cfg(feature = "dev")]
use arbitrary::Arbitrary;

use derive_more::{Display, Error};
use signature::{Keypair, Signer, Verifier};

use ufotofu::codec_prelude::*;

use crate::prelude::*;

/// An [authorisation token](https://willowprotocol.org/specs/data-model/index.html#AuthorisationToken), cryptographically certifying that an entry was created by somebody who had the permission to do so.
///
/// #### Examples
///
/// Authorising an entry of your own:
///
/// ```
/// use rand::rngs::OsRng;
/// use willow25::prelude::*;
/// use willow25::authorisation::raw::*;
///
/// # #[cfg(feature = "dev")] {
/// let mut csprng = OsRng;
/// let (subspace_id, secret) = randomly_generate_subspace(&mut csprng);
/// let namespace_id = NamespaceId::from_bytes(&[17; 32]);
///
/// let my_entry = Entry::builder()
///     .namespace_id(namespace_id.clone())
///     .subspace_id(subspace_id.clone())
///     .path(path!("/ideas"))
///     .timestamp(12345)
///     .payload(b"chocolate with mustard")
///     .build();
///
/// let mut cap = WriteCapability::new_communal(
///     namespace_id.clone(),
///     subspace_id.clone(),
/// );
///
/// let auth_token = AuthorisationToken::new_for_entry(&my_entry, &cap, &secret).unwrap();
///
/// assert!(auth_token.does_authorise(&my_entry));
/// # }
/// ```
///
/// Verifying an untrusted entry, creating the authorisation token from raw parts, which fails if those parts are invalid:
///
/// ```
/// use rand::rngs::OsRng;
/// use willow25::prelude::*;
/// use willow25::authorisation::raw::*;
///
/// # #[cfg(feature = "dev")] {
/// let mut csprng = OsRng;
/// let (subspace_id, secret) = randomly_generate_subspace(&mut csprng);
/// let namespace_id = NamespaceId::from_bytes(&[17; 32]);
///
/// let my_entry = Entry::builder()
///     .namespace_id(namespace_id.clone())
///     .subspace_id(subspace_id.clone())
///     .path(path!("/ideas"))
///     .timestamp(12345)
///     .payload(b"chocolate with mustard")
///     .build();
///
/// let mut cap = WriteCapability::new_communal(
///     namespace_id.clone(),
///     subspace_id.clone(),
/// );
///
/// let auth_token = AuthorisationToken::new(
///     cap,
///     SubspaceSignature::from([18; 64]), // Invalid signature, verification will fail.
/// );
///
/// assert!(!auth_token.does_authorise(&my_entry));
/// # }
/// ```
#[derive(Clone, PartialEq, Eq)]
pub struct AuthorisationToken {
    capability: WriteCapability,
    signature: SubspaceSignature,
}

impl fmt::Debug for AuthorisationToken {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("McAuthorisationToken")
            .field("capability", &self.capability)
            .field("signature", &self.signature)
            .finish()
    }
}

#[cfg(feature = "dev")]
impl<'a> Arbitrary<'a> for AuthorisationToken {
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
        Ok(Self::new(
            Arbitrary::arbitrary(u)?,
            Arbitrary::arbitrary(u)?,
        ))
    }
}

impl AuthorisationToken {
    /// Manually creates a new [`AuthorisationToken`] from a write capability and a signature (as opposed to using [`AuthorisationToken::new_for_entry`](AuthorisationToken::new_for_entry) to create the correct signature yourself).
    ///
    /// ```
    /// use rand::rngs::OsRng;
    /// use willow25::prelude::*;
    /// use willow25::authorisation::raw::*;
    ///
    /// # #[cfg(feature = "dev")] {
    /// let mut csprng = OsRng;
    /// let (subspace_id, secret) = randomly_generate_subspace(&mut csprng);
    /// let namespace_id = NamespaceId::from_bytes(&[17; 32]);
    ///
    /// let my_entry = Entry::builder()
    ///     .namespace_id(namespace_id.clone())
    ///     .subspace_id(subspace_id.clone())
    ///     .path(path!("/ideas"))
    ///     .timestamp(12345)
    ///     .payload(b"chocolate with mustard")
    ///     .build();
    ///
    /// let mut cap = WriteCapability::new_communal(
    ///     namespace_id.clone(),
    ///     subspace_id.clone(),
    /// );
    ///
    /// let auth_token = AuthorisationToken::new(
    ///     cap,
    ///     SubspaceSignature::from([18; 64]), // Invalid signature, verification will fail.
    /// );
    ///
    /// assert!(!auth_token.does_authorise(&my_entry));
    /// # }
    /// ```
    pub fn new(capability: WriteCapability, signature: SubspaceSignature) -> Self {
        Self {
            capability,
            signature,
        }
    }

    /// Returns a reference to the write capability.
    pub fn capability(&self) -> &WriteCapability {
        &self.capability
    }

    /// Returns a reference to the signature.
    pub fn signature(&self) -> &SubspaceSignature {
        &self.signature
    }

    /// Takes ownership of the authorisation token and returns its capability and signature by value.
    pub fn into_parts(self) -> (WriteCapability, SubspaceSignature) {
        (self.capability, self.signature)
    }

    /// Creates an authorisation token for a given entry and capability by computing the correct signature from the given secret.
    ///
    /// Returns an error if the capability does not grant write access to the entry being authorised, or if the secret key does not correspond to the receiver of the write capability.
    ///
    /// ```
    /// use rand::rngs::OsRng;
    /// use willow25::prelude::*;
    /// use willow25::authorisation::raw::*;
    ///
    /// # #[cfg(feature = "dev")] {
    /// let mut csprng = OsRng;
    /// let (subspace_id, secret) = randomly_generate_subspace(&mut csprng);
    /// let namespace_id = NamespaceId::from_bytes(&[17; 32]);
    ///
    /// let my_entry = Entry::builder()
    ///     .namespace_id(namespace_id.clone())
    ///     .subspace_id(subspace_id.clone())
    ///     .path(path!("/ideas"))
    ///     .timestamp(12345)
    ///     .payload(b"chocolate with mustard")
    ///     .build();
    ///
    /// let mut cap = WriteCapability::new_communal(
    ///     namespace_id.clone(),
    ///     subspace_id.clone(),
    /// );
    ///
    /// let auth_token = AuthorisationToken::new_for_entry(&my_entry, &cap, &secret).unwrap();
    ///
    /// assert!(auth_token.does_authorise(&my_entry));
    /// # }
    /// ```
    pub fn new_for_entry<E>(
        entry: &E,
        write_capability: &WriteCapability,
        secret: &SubspaceSecret,
    ) -> Result<Self, DoesNotAuthorise>
    where
        E: EntrylikeExt + ?Sized,
    {
        if &secret.verifying_key() == write_capability.receiver()
            && write_capability.includes(entry)
        {
            let mut encoded_entry = Vec::with_capacity(entry.length_of_entry_encoding());
            pollster::block_on(async {
                entry
                    .encode_entry(&mut (&mut encoded_entry).into_consumer())
                    .await
                    .unwrap();
            });

            let signature = secret.sign(&encoded_entry);

            Ok(Self::new(write_capability.clone(), signature))
        } else {
            Err(DoesNotAuthorise)
        }
    }

    /// Return `true` iff `self` is a valid authorisation token for the given entry.
    ///
    /// ```
    /// use rand::rngs::OsRng;
    /// use willow25::prelude::*;
    /// use willow25::authorisation::raw::*;
    ///
    /// # #[cfg(feature = "dev")] {
    /// let mut csprng = OsRng;
    /// let (subspace_id, secret) = randomly_generate_subspace(&mut csprng);
    /// let namespace_id = NamespaceId::from_bytes(&[17; 32]);
    ///
    /// let my_entry = Entry::builder()
    ///     .namespace_id(namespace_id.clone())
    ///     .subspace_id(subspace_id.clone())
    ///     .path(path!("/ideas"))
    ///     .timestamp(12345)
    ///     .payload(b"chocolate with mustard")
    ///     .build();
    ///
    /// let mut cap = WriteCapability::new_communal(
    ///     namespace_id.clone(),
    ///     subspace_id.clone(),
    /// );
    ///
    /// let auth_token = AuthorisationToken::new_for_entry(&my_entry, &cap, &secret).unwrap();
    ///
    /// assert!(auth_token.does_authorise(&my_entry));
    /// # }
    /// ```
    pub fn does_authorise<E>(&self, entry: &E) -> bool
    where
        E: EntrylikeExt + ?Sized,
    {
        if self.capability.includes(entry) {
            let mut encoded_entry = Vec::with_capacity(entry.length_of_entry_encoding());
            pollster::block_on(async {
                entry
                    .encode_entry(&mut (&mut encoded_entry).into_consumer())
                    .await
                    .unwrap();
            });

            let verifier: &SubspaceId = self.capability.receiver();

            matches!(verifier.verify(&encoded_entry, &self.signature), Ok(()))
        } else {
            false
        }
    }
}

/// An error indicating that an [`AuthorisationToken`] does not grant write permission for some given entry.
#[derive(Clone, Display, Error, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[display("entry paired with invalid `AuthorisationToken`")]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
pub struct DoesNotAuthorise;

/// A pair of a prior [`AuthorisedEntry`] and [`Entry`], used to encoded and decode [`McAuthorisationToken`]s privately.
///
/// [`McAuthorisationToken`]: meadowcap::McAuthorisationToken
#[derive(PartialEq, Clone, Debug)]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
pub struct PriorAuthedEntryEntryPair {
    /// The previous [`AuthorisedEntry`] which was decoded.
    prior_authed_entry: AuthorisedEntry,
    /// An entry included by the [`WriteCapability`] being privately encoded against.
    entry: Entry,
}

impl PriorAuthedEntryEntryPair {
    /// Returns a new [`PriorAuthedEntryEntryPair`] with the given `prior_authed_entry` and `entry`.
    pub fn new(prior_authed_entry: AuthorisedEntry, entry: Entry) -> Self {
        Self {
            prior_authed_entry,
            entry,
        }
    }

    /// Returns the previous [`AuthorisedEntry`] which was decoded against.
    pub fn prior_authed_entry(&self) -> &AuthorisedEntry {
        &self.prior_authed_entry
    }

    /// Returns the entry included by the [`WriteCapability`] being privately encoded against.
    pub fn entry(&self) -> &Entry {
        &self.entry
    }

    /// Consumes `self` to return the underlying [`AuthorisedEntry`] and [`Entry`].
    pub fn into_parts(self) -> (AuthorisedEntry, Entry) {
        (self.prior_authed_entry, self.entry)
    }
}

/// Implements relative encoding according to the [EncodeMeadowcapAuthorisationTokenRelative](https://willowprotocol.org/specs/encodings/index.html#encsec_EncodeMeadowcapAuthorisationTokenRelative) encoding function.
impl RelativeEncodable<PriorAuthedEntryEntryPair> for AuthorisationToken {
    async fn relative_encode<C>(
        &self,
        rel: &PriorAuthedEntryEntryPair,
        consumer: &mut C,
    ) -> Result<(), C::Error>
    where
        C: ufotofu::BulkConsumer<Item = u8> + ?Sized,
    {
        let prev = &rel.prior_authed_entry;
        let entry = &rel.entry;

        let pair = crate::authorisation::PriorCapEntryPair::new(
            prev.authorisation_token().capability().clone(),
            entry.clone(),
        );

        self.capability().relative_encode(&pair, consumer).await?;
        self.signature().encode(consumer).await?;

        Ok(())
    }

    /// Returns false if the relative entry's namespace is not `self`'s [granted area](https://willowprotocol.org/specs/meadowcap/index.html#communal_cap_granted_namespace), or if the relative entry is not included by `self`'s [granted area](https://willowprotocol.org/specs/meadowcap/index.html#communal_cap_granted_area).
    fn can_be_encoded_relative_to(&self, rel: &PriorAuthedEntryEntryPair) -> bool {
        self.capability().granted_namespace() == rel.entry.namespace_id()
            && self.capability().granted_area().includes(&rel.entry)
    }
}

/// Implements relative decoding according to the [EncodeMeadowcapAuthorisationTokenRelative](https://willowprotocol.org/specs/encodings/index.html#encsec_EncodeMeadowcapAuthorisationTokenRelative) encoding function.
impl RelativeDecodable<PriorAuthedEntryEntryPair> for AuthorisationToken {
    type ErrorReason = Blame;

    async fn relative_decode<P>(
        rel: &PriorAuthedEntryEntryPair,
        producer: &mut P,
    ) -> Result<Self, ufotofu::codec::DecodeError<P::Final, P::Error, Self::ErrorReason>>
    where
        P: ufotofu::BulkProducer<Item = u8> + ?Sized,
        Self: Sized,
    {
        let prev = &rel.prior_authed_entry;
        let entry = &rel.entry;

        let pair = crate::authorisation::PriorCapEntryPair::new(
            prev.authorisation_token().capability().clone(),
            entry.clone(),
        );

        let write_cap = WriteCapability::relative_decode(&pair, producer).await?;
        let signature = SubspaceSignature::decode(producer)
            .await
            .map_err(|_| DecodeError::Other(Blame::TheirFault))?;

        Ok(Self::new(write_cap, signature))
    }
}