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}