Skip to main content

core_crypto/mls/
external_sender.rs

1use openmls::{
2    extensions::ExternalSender as MlsExternalSender,
3    prelude::{Credential as MlsCredential, OpenMlsCrypto as _, OpenMlsSignaturePublicKey, SignatureScheme},
4};
5use rusty_jwt_tools::{
6    jsonwebtoken::{
7        DecodingKey,
8        errors::Error as JsonWebTokenError,
9        jwk::{AlgorithmParameters, EllipticCurve, Jwk},
10    },
11    prelude::RustyJwtError,
12};
13
14use crate::{Error, OpenMlsError, Result, mls_provider::CRYPTO};
15
16const WIRE_SERVER_IDENTITY: &str = "wire-server";
17
18/// A RFC 9420 External Sender
19///
20/// This can be used to initialize a subconversation.
21#[derive(
22    Debug, Clone, PartialEq, Eq, derive_more::From, derive_more::Into, derive_more::Deref, derive_more::DerefMut,
23)]
24pub struct ExternalSender(pub(crate) MlsExternalSender);
25
26impl ExternalSender {
27    /// Serialize this external sender into a byte vector.
28    ///
29    /// This produces the public key and matches [`Self::parse_public_key`].
30    pub fn serialize(&self) -> Vec<u8> {
31        self.0.signature_key().as_slice().to_vec()
32    }
33
34    /// Parse an external sender given a JWK.
35    ///
36    /// This expects a raw json serialized JWK of an Ed25519 key, or of an ECDSA key on P-256, P-384 or P-521.
37    pub fn parse_jwk(jwk: &[u8]) -> Result<ExternalSender> {
38        let jwk = serde_json::from_slice::<Jwk>(jwk)
39            .map_err(JsonWebTokenError::from)
40            .map_err(RustyJwtError::from)?;
41        let signature_scheme = match &jwk.algorithm {
42            AlgorithmParameters::EllipticCurve(params) => match params.curve {
43                EllipticCurve::P256 => SignatureScheme::ECDSA_SECP256R1_SHA256,
44                EllipticCurve::P384 => SignatureScheme::ECDSA_SECP384R1_SHA384,
45                EllipticCurve::P521 => SignatureScheme::ECDSA_SECP521R1_SHA512,
46                _ => return Err(RustyJwtError::UnsupportedAlgorithm.into()),
47            },
48            AlgorithmParameters::OctetKeyPair(params) if matches!(params.curve, EllipticCurve::Ed25519) => {
49                SignatureScheme::ED25519
50            }
51            _ => return Err(RustyJwtError::UnsupportedAlgorithm.into()),
52        };
53        // ECDSA keys are uncompressed points, as RFC 9420 requires
54        let key = DecodingKey::from_jwk(&jwk).map_err(RustyJwtError::from)?;
55        let key = key.try_get_as_bytes().map_err(RustyJwtError::from)?;
56        Self::parse_public_key(key, signature_scheme)
57    }
58
59    /// Parse an external sender given a raw public key.
60    ///
61    /// This supports the legacy behaviour where the server was providing the external sender public key
62    /// raw.
63    // TODO: remove at some point when the backend API is not used anymore. Tracking issue: WPB-9614
64    pub fn parse_public_key(key: &[u8], signature_scheme: SignatureScheme) -> Result<ExternalSender> {
65        CRYPTO
66            .validate_signature_key(signature_scheme, key)
67            .map_err(OpenMlsError::wrap("validating signature key"))?;
68        let key = OpenMlsSignaturePublicKey::new(key.into(), signature_scheme)
69            .map_err(OpenMlsError::wrap("creating new signature public key"))?;
70        Ok(MlsExternalSender::new(key.into(), MlsCredential::new_basic(WIRE_SERVER_IDENTITY.into())).into())
71    }
72
73    /// Parse an external sender.
74    ///
75    /// This first attempts to parse this as a JWK per `parse_jwk`,
76    /// and falls back to `parse_public_key` if the former method fails.
77    pub fn parse(key: &[u8], signature_scheme: SignatureScheme) -> Result<ExternalSender> {
78        Self::parse_jwk(key).or_else(|_| Self::parse_public_key(key, signature_scheme))
79    }
80}
81
82impl TryFrom<Vec<u8>> for ExternalSender {
83    type Error = Error;
84
85    fn try_from(value: Vec<u8>) -> Result<Self> {
86        Self::parse_jwk(&value)
87    }
88}