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}