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