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::{Ecies, EstablishedEcies, InboundCreationResult, InitialMessage, Message},
34    hpke::DigitMode,
35};
36
37use crate::authentication::oauth::qrcode::SecureChannelError as Error;
38
39/// A cryptographic communication channel.
40pub(super) enum CryptoChannel {
41    Ecies(Ecies),
42}
43
44impl CryptoChannel {
45    /// Create a new ECIES-based [`CryptoChannel`].
46    pub(super) fn new_ecies() -> Self {
47        CryptoChannel::Ecies(Ecies::new())
48    }
49
50    /// Get the [`Curve25519PublicKey`] of this cryptographic channel.
51    pub(super) fn public_key(&self) -> Curve25519PublicKey {
52        match self {
53            CryptoChannel::Ecies(ecies) => ecies.public_key(),
54        }
55    }
56
57    /// Establish a cryptographic channel by unsealing an initial message.
58    pub(super) fn establish_inbound_channel(
59        self,
60        message: &str,
61    ) -> Result<CryptoChannelCreationResult, Error> {
62        match self {
63            CryptoChannel::Ecies(ecies) => {
64                let message = InitialMessage::decode(message)?;
65                Ok(CryptoChannelCreationResult::Ecies(ecies.establish_inbound_channel(&message)?))
66            }
67        }
68    }
69}
70
71pub(super) enum CryptoChannelCreationResult {
72    Ecies(InboundCreationResult),
73}
74
75impl CryptoChannelCreationResult {
76    /// Get the unsealed plaintext of the initial message.
77    pub(super) fn plaintext(&self) -> &[u8] {
78        match self {
79            CryptoChannelCreationResult::Ecies(inbound_creation_result) => {
80                &inbound_creation_result.message
81            }
82        }
83    }
84}
85
86/// A fully established cryptographic communication channel.
87///
88/// This channel allows you to seal/encrypt as well as open/decrypt
89/// cryptographic messages.
90pub(super) enum EstablishedCryptoChannel {
91    Ecies(EstablishedEcies),
92}
93
94impl EstablishedCryptoChannel {
95    /// Get the [`CheckCode`] of this [`EstablishedCryptoChannel`].
96    pub(super) fn check_code(&self) -> u8 {
97        match self {
98            EstablishedCryptoChannel::Ecies(established_ecies) => {
99                established_ecies.check_code().to_digit(DigitMode::AllowLeadingZero)
100            }
101        }
102    }
103
104    /// Seal the given plaintext using this [`EstablishedCryptoChannel`].
105    pub(super) fn seal(&mut self, plaintext: &str) -> String {
106        match self {
107            EstablishedCryptoChannel::Ecies(channel) => {
108                let message = channel.encrypt(plaintext.as_bytes());
109                message.encode()
110            }
111        }
112    }
113
114    /// Open the given sealed message using this [`EstablishedCryptoChannel`].
115    pub(super) fn open(&mut self, message: &str) -> Result<String, Error> {
116        let plaintext = match self {
117            EstablishedCryptoChannel::Ecies(channel) => {
118                let message = Message::decode(message)?;
119                channel.decrypt(&message)?
120            }
121        };
122
123        Ok(String::from_utf8(plaintext).map_err(|e| e.utf8_error())?)
124    }
125}