openmls/ciphersuite/
signable.rs

1//! This module defines traits used for signing and verifying
2//! structs from the MLS protocol spec.
3//!
4//! # Type-Enforced Verification
5//!
6//! This module contains four traits, each describing the property they enable
7//! upon implementation: [`Signable`], [`SignedStruct`], [`Verifiable`] and
8//! [`VerifiedStruct`].
9//!
10//! Each trait represents the state of a struct in a sender-receiver flow with
11//! the following transitions.
12//!
13//! * the signer creates an instance of a struct that implements [`Signable`]
14//! * the signer signs it, consuming the [`Signable`] struct and producing a [`SignedStruct`]
15//! * the signer serializes the struct and sends it to the verifier
16//! * the verifier deserializes the byte-string into a struct implementing [`Verifiable`]
17//! * the verifier verifies the struct, consuming the [`Verifiable`] struct and producing a [`VerifiedStruct`]
18//!
19//! Using this process, we can ensure that only structs implementing
20//! [`SignedStruct`] are sent over the wire and only structs implementing
21//! [`VerifiedStruct`] are used on the verifier side as input for further
22//! processing functions.
23//!
24//! For the type-safety to work, it is important that [`Signable`] and
25//! [`SignedStruct`] are implemented by distinct structs. The same goes for
26//! [`Verifiable`] and [`VerifiedStruct`]. In addition, only the
27//! [`SignedStruct`] should implement the [`tls_codec::Serialize`] trait.
28//! Similarly, only the [`Verifiable`] struct should implement the
29//! [`tls_codec::Deserialize`] trait.
30
31use openmls_traits::{crypto::OpenMlsCrypto, signatures::Signer};
32use thiserror::Error;
33use tls_codec::Serialize;
34
35use crate::ciphersuite::{OpenMlsSignaturePublicKey, SignContent, Signature};
36
37/// Signature generation and verification errors.
38/// The only information relayed with this error is whether the signature
39/// verification or generation failed.
40#[derive(Error, Debug, PartialEq, Eq, Clone)]
41pub enum SignatureError {
42    /// Signature verification failed
43    #[error("Signature verification failed.")]
44    VerificationError,
45    /// Signature generation failed
46    #[error("Signature generation failed.")]
47    SigningError,
48}
49
50/// This trait must be implemented by all structs that contain a self-signature.
51pub trait SignedStruct<T> {
52    /// Build a signed struct version from the payload struct.
53    fn from_payload(payload: T, signature: Signature, serialized_payload: Vec<u8>) -> Self;
54}
55
56/// The `Signable` trait is implemented by all struct that are being signed.
57/// The implementation has to provide the `unsigned_payload` function.
58pub trait Signable: Sized {
59    /// The type of the object once it's signed.
60    type SignedOutput;
61
62    /// Return the unsigned, serialized payload that should be signed.
63    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error>;
64
65    /// Return the string label used for labeled signing.
66    fn label(&self) -> &str;
67
68    /// Sign the payload with the given `private_key`.
69    ///
70    /// Returns a `Signature`.
71    fn sign(self, signer: &impl Signer) -> Result<Self::SignedOutput, SignatureError>
72    where
73        Self::SignedOutput: SignedStruct<Self>,
74    {
75        let payload = self
76            .unsigned_payload()
77            .map_err(|_| SignatureError::SigningError)?;
78        let payload_sign_content =
79            match SignContent::new(self.label(), payload.clone().into()).tls_serialize_detached() {
80                Ok(p) => p,
81                Err(e) => {
82                    log::error!("Serializing SignContent failed, {e:?}");
83                    return Err(SignatureError::SigningError);
84                }
85            };
86        let signature = signer
87            .sign(&payload_sign_content)
88            .map_err(|_| SignatureError::SigningError)?;
89
90        Ok(Self::SignedOutput::from_payload(
91            self,
92            signature.into(),
93            payload,
94        ))
95    }
96}
97
98/// This marker trait must be implemented by all structs that contain a verified
99/// self-signature.
100pub trait VerifiedStruct {}
101
102/// The verifiable trait must be implemented by any struct that is signed with
103/// a credential. The actual `verify` method is provided.
104/// The `unsigned_payload` and `signature` functions have to be implemented for
105/// each struct, returning the serialized payload and the signature respectively.
106///
107/// Note that `Verifiable` should not be implemented on the same struct as
108/// `Signable`. If this appears to be necessary, it is probably a sign that the
109/// struct implementing them aren't well defined. Not that both traits define an
110/// `unsigned_payload` function.
111pub trait Verifiable: Sized {
112    /// The type used for representing the verified data. Must implement the marker trait
113    /// [`VerifiedStruct`].
114    type VerifiedStruct: VerifiedStruct;
115
116    /// Return the unsigned, serialized payload that should be verified.
117    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error>;
118
119    /// A reference to the signature to be verified.
120    fn signature(&self) -> &Signature;
121
122    /// Return the string label used for labeled verification.
123    fn label(&self) -> &str;
124
125    /// Verifies the payload against the given `credential`.
126    /// Usually this is implemented by first checking that `self.verify_no_out()`
127    /// does not return an error, and then converting the value into
128    /// `Self::VerifiedStruct`.
129    ///
130    /// Returns `Ok(Self::VerifiedOutput)` if the signature is valid and
131    /// `CredentialError::InvalidSignature` otherwise.
132    fn verify(
133        self,
134        crypto: &impl OpenMlsCrypto,
135        pk: &OpenMlsSignaturePublicKey,
136    ) -> Result<Self::VerifiedStruct, SignatureError>;
137
138    /// Verifies the payload against the given public key.
139    /// The signature is fetched via the [`Verifiable::signature()`] function and
140    /// the payload via [`Verifiable::unsigned_payload()`].
141    ///
142    /// Returns `Ok(())` if the signature is valid and
143    /// [`SignatureError::VerificationError`] otherwise.
144    fn verify_no_out(
145        &self,
146        crypto: &impl OpenMlsCrypto,
147        pk: &OpenMlsSignaturePublicKey,
148    ) -> Result<(), SignatureError> {
149        let payload = self
150            .unsigned_payload()
151            .map_err(|_| SignatureError::VerificationError)?;
152        let sign_content = SignContent::new(self.label(), payload.into());
153        let payload = match sign_content.tls_serialize_detached() {
154            Ok(p) => p,
155            Err(e) => {
156                log::error!("Serializing SignContent failed, {e:?}");
157                return Err(SignatureError::VerificationError);
158            }
159        };
160        // https://validation.openmls.tech/#valn1301
161        crypto
162            .verify_signature(
163                pk.signature_scheme(),
164                &payload,
165                pk.as_slice(),
166                self.signature().value(),
167            )
168            .map_err(|_| SignatureError::VerificationError)
169    }
170}