Skip to main content

matrix_sdk/authentication/oauth/qrcode/secure_channel/
crypto_channel.rs

1// Copyright 2025 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Module implementing the cryptographic part of a [`SecureChannel`].
16//!
17//! This implements an abstraction over the secure channel provided by
18//! vodozemac. As [MSC4108] evolved, the underlying cryptographic primitives
19//! have changed from ECIES to [HPKE]. Since QR code login has shipped in some
20//! clients before the MSC got approved and merged into the spec, we're in the
21//! unlucky position of having to support both cryptographic channels for a
22//! while.
23//!
24//! This module allows this backwards compatibility and adds a bit of
25//! cryptographic agility for the time when we will have to support post-quanum
26//! safe HPKE variants.
27//!
28//! [HPKE]: https://www.rfc-editor.org/rfc/rfc9180.html
29//! [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
30
31use vodozemac::{
32    Curve25519PublicKey,
33    ecies::{CheckCode, Ecies, EstablishedEcies, InboundCreationResult, InitialMessage, Message},
34};
35
36use crate::authentication::oauth::qrcode::SecureChannelError as Error;
37
38/// A cryptographic communication channel.
39pub(super) enum CryptoChannel {
40    Ecies(Ecies),
41}
42
43impl CryptoChannel {
44    /// Create a new ECIES-based [`CryptoChannel`].
45    pub(super) fn new_ecies() -> Self {
46        CryptoChannel::Ecies(Ecies::new())
47    }
48
49    /// Get the [`Curve25519PublicKey`] of this cryptographic channel.
50    pub(super) fn public_key(&self) -> Curve25519PublicKey {
51        match self {
52            CryptoChannel::Ecies(ecies) => ecies.public_key(),
53        }
54    }
55
56    /// Establish a cryptographic channel by unsealing an initial message.
57    pub(super) fn establish_inbound_channel(
58        self,
59        message: &str,
60    ) -> Result<CryptoChannelCreationResult, Error> {
61        match self {
62            CryptoChannel::Ecies(ecies) => {
63                let message = InitialMessage::decode(message)?;
64                Ok(CryptoChannelCreationResult::Ecies(ecies.establish_inbound_channel(&message)?))
65            }
66        }
67    }
68}
69
70pub(super) enum CryptoChannelCreationResult {
71    Ecies(InboundCreationResult),
72}
73
74impl CryptoChannelCreationResult {
75    /// Get the unsealed plaintext of the initial message.
76    pub(super) fn plaintext(&self) -> &[u8] {
77        match self {
78            CryptoChannelCreationResult::Ecies(inbound_creation_result) => {
79                &inbound_creation_result.message
80            }
81        }
82    }
83}
84
85/// A fully established cryptographic communication channel.
86///
87/// This channel allows you to seal/encrypt as well as open/decrypt
88/// cryptographic messages.
89pub(super) enum EstablishedCryptoChannel {
90    Ecies(EstablishedEcies),
91}
92
93impl EstablishedCryptoChannel {
94    /// Get the [`CheckCode`] of this [`EstablishedCryptoChannel`].
95    pub(super) fn check_code(&self) -> &CheckCode {
96        match self {
97            EstablishedCryptoChannel::Ecies(established_ecies) => established_ecies.check_code(),
98        }
99    }
100
101    /// Seal the given plaintext using this [`EstablishedCryptoChannel`].
102    pub(super) fn seal(&mut self, plaintext: &str) -> String {
103        match self {
104            EstablishedCryptoChannel::Ecies(channel) => {
105                let message = channel.encrypt(plaintext.as_bytes());
106                message.encode()
107            }
108        }
109    }
110
111    /// Open the given sealed message using this [`EstablishedCryptoChannel`].
112    pub(super) fn open(&mut self, message: &str) -> Result<String, Error> {
113        let plaintext = match self {
114            EstablishedCryptoChannel::Ecies(channel) => {
115                let message = Message::decode(message)?;
116                channel.decrypt(&message)?
117            }
118        };
119
120        Ok(String::from_utf8(plaintext).map_err(|e| e.utf8_error())?)
121    }
122}