Skip to content

Commit b364407

Browse files
committed
Expose the QR code parsing structs used by the QR login mechanism
1 parent 68e2eef commit b364407

File tree

6 files changed

+183
-0
lines changed

6 files changed

+183
-0
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# UNRELEASED
22

3+
- Add `QrCodeData` and `QrCodeMode` classes which can be used to parse or
4+
generate QR codes intended for the QR code login mechanism described in
5+
[MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108).
6+
37
- Update matrix-rust-sdk to `35173347f`, which includes:
48

59
- Add data types to parse the QR code data for the QR code login defined in

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ serde_json = "1.0.91"
6767
serde-wasm-bindgen = "0.5.0"
6868
tracing = { version = "0.1.36", default-features = false, features = ["std"] }
6969
tracing-subscriber = { version = "0.3.14", default-features = false, features = ["registry", "std", "ansi"] }
70+
url = "2.5.0"
7071
wasm-bindgen = "0.2.89"
7172
wasm-bindgen-futures = "0.4.33"
7273
zeroize = "1.6.0"

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod libolm_migration;
3232
pub mod machine;
3333
mod macros;
3434
pub mod olm;
35+
pub mod qr_login;
3536
pub mod requests;
3637
pub mod responses;
3738
pub mod store;

src/qr_login.rs

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
//! Types for QR code login
2+
3+
use matrix_sdk_crypto::types::qr_login;
4+
use url::Url;
5+
use wasm_bindgen::prelude::*;
6+
7+
use crate::vodozemac::Curve25519PublicKey;
8+
9+
/// The mode of the QR code login.
10+
///
11+
/// The QR code login mechanism supports both, the new device, as well as the
12+
/// existing device to display the QR code.
13+
///
14+
/// The different modes have an explicit one-byte identifier which gets added to
15+
/// the QR code data.
16+
#[wasm_bindgen]
17+
#[derive(Debug)]
18+
pub enum QrCodeMode {
19+
/// Enum variant for the case where the new device is displaying the QR
20+
/// code.
21+
Login,
22+
/// Enum variant for the case where the existing device is displaying the QR
23+
/// code.
24+
Reciprocate,
25+
}
26+
27+
impl From<qr_login::QrCodeMode> for QrCodeMode {
28+
fn from(value: qr_login::QrCodeMode) -> Self {
29+
match value {
30+
qr_login::QrCodeMode::Login => Self::Login,
31+
qr_login::QrCodeMode::Reciprocate => Self::Reciprocate,
32+
}
33+
}
34+
}
35+
36+
/// Data for the QR code login mechanism.
37+
///
38+
/// The [`QrCodeData`] can be serialized and encoded as a QR code or it can be
39+
/// decoded from a QR code.
40+
#[wasm_bindgen]
41+
#[derive(Debug)]
42+
pub struct QrCodeData {
43+
inner: qr_login::QrCodeData,
44+
}
45+
46+
#[wasm_bindgen]
47+
impl QrCodeData {
48+
/// Create new [`QrCodeData`] from a given public key, a rendezvous URL and,
49+
/// optionally, a homeserver ULR.
50+
///
51+
/// If a homeserver URL is given, then the [`QrCodeData`] mode will be
52+
/// [`QrCodeMode::Reciprocate`], i.e. the QR code will contain data for the
53+
/// existing device to display the QR code.
54+
///
55+
/// If no homeserver is given, the [`QrCodeData`] mode will be
56+
/// [`QrCodeMode::Login`], i.e. the QR code will contain data for the
57+
/// new device to display the QR code.
58+
#[wasm_bindgen(constructor)]
59+
pub fn new(
60+
public_key: Curve25519PublicKey,
61+
rendezvous_url: &str,
62+
homeserver_url: Option<String>,
63+
) -> Result<QrCodeData, JsError> {
64+
let public_key = public_key.inner;
65+
let rendezvous_url = Url::parse(rendezvous_url)?;
66+
67+
let mode_data = if let Some(homeserver_url) = homeserver_url {
68+
qr_login::QrCodeModeData::Reciprocate { homeserver_url: Url::parse(&homeserver_url)? }
69+
} else {
70+
qr_login::QrCodeModeData::Login
71+
};
72+
73+
let inner = qr_login::QrCodeData { public_key, rendezvous_url, mode_data };
74+
75+
Ok(QrCodeData { inner })
76+
}
77+
78+
/// Attempt to decode a slice of bytes into a [`QrCodeData`] object.
79+
///
80+
/// The slice of bytes would generally be returned by a QR code decoder.
81+
pub fn from_bytes(bytes: &[u8]) -> Result<QrCodeData, JsError> {
82+
Ok(Self { inner: qr_login::QrCodeData::from_bytes(bytes)? })
83+
}
84+
85+
/// Encode the [`QrCodeData`] into a list of bytes.
86+
///
87+
/// The list of bytes can be used by a QR code generator to create an image
88+
/// containing a QR code.
89+
pub fn to_bytes(&self) -> Vec<u8> {
90+
self.inner.to_bytes()
91+
}
92+
93+
/// Attempt to decode a base64 encoded string into a [`QrCodeData`] object.
94+
pub fn from_base64(data: &str) -> Result<QrCodeData, JsError> {
95+
Ok(Self { inner: qr_login::QrCodeData::from_base64(data)? })
96+
}
97+
98+
/// Encode the [`QrCodeData`] into a list of bytes.
99+
///
100+
/// The list of bytes can be used by a QR code generator to create an image
101+
/// containing a QR code.
102+
pub fn to_base64(&self) -> String {
103+
self.inner.to_base64()
104+
}
105+
106+
/// Get the Curve25519 public key embedded in the [`QrCodeData`].
107+
///
108+
/// This Curve25519 public key should be used to establish an ECIES channel
109+
/// with the other device.
110+
#[wasm_bindgen(getter)]
111+
pub fn public_key(&self) -> Curve25519PublicKey {
112+
self.inner.public_key.into()
113+
}
114+
115+
/// Get the URL of the rendezvous server which will be used to exchange
116+
/// devices between the two devices.
117+
#[wasm_bindgen(getter)]
118+
pub fn rendezvous_url(&self) -> String {
119+
self.inner.rendezvous_url.as_str().to_owned()
120+
}
121+
122+
/// Get the homeserver URL which the new device will be logged in to.
123+
///
124+
/// This will be only available if the existing device has generated the QR
125+
/// code and the new device is the one scanning the QR code.
126+
#[wasm_bindgen(getter)]
127+
pub fn homeserver_url(&self) -> Option<String> {
128+
if let qr_login::QrCodeModeData::Reciprocate { homeserver_url } = &self.inner.mode_data {
129+
Some(homeserver_url.as_str().to_owned())
130+
} else {
131+
None
132+
}
133+
}
134+
135+
/// Get the mode of this [`QrCodeData`] instance.
136+
#[wasm_bindgen(getter)]
137+
pub fn mode(&self) -> QrCodeMode {
138+
self.inner.mode().into()
139+
}
140+
}

tests/qr_code.test.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const { QrCodeData, QrCodeMode, Curve25519PublicKey } = require("../pkg/matrix_sdk_crypto_wasm");
2+
3+
describe(QrCodeData.name, () => {
4+
test("can parse the QR code bytes from the MSC", () => {
5+
const base64Data =
6+
"TUFUUklYAgPYhmhqshl7eA4wCp1KIUdIBwDXkp85qzG55RQ3AkjtawBHaHR0cHM6Ly9yZW5kZXp2b3VzLmxhYi5lbGVtZW50LmRldi9lOGRhNjM1NS01NTBiLTRhMzItYTE5My0xNjE5ZDk4MzA2Njg";
7+
8+
const data = QrCodeData.from_base64(base64Data);
9+
10+
expect(data.public_key.toBase64()).toStrictEqual("2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws");
11+
expect(data.rendezvous_url).toStrictEqual(
12+
"https://rendezvous.lab.element.dev/e8da6355-550b-4a32-a193-1619d9830668",
13+
);
14+
expect(data.mode).toStrictEqual(QrCodeMode.Login);
15+
16+
const encoded = data.to_base64();
17+
18+
expect(base64Data).toStrictEqual(encoded);
19+
});
20+
21+
test("can construct a new QrCodeData class", () => {
22+
const base64Data =
23+
"TUFUUklYAgPYhmhqshl7eA4wCp1KIUdIBwDXkp85qzG55RQ3AkjtawBHaHR0cHM6Ly9yZW5kZXp2b3VzLmxhYi5lbGVtZW50LmRldi9lOGRhNjM1NS01NTBiLTRhMzItYTE5My0xNjE5ZDk4MzA2Njg";
24+
const publicKey = new Curve25519PublicKey("2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws");
25+
const rendezvousUrl = "https://rendezvous.lab.element.dev/e8da6355-550b-4a32-a193-1619d9830668";
26+
27+
const data = new QrCodeData(publicKey, rendezvousUrl);
28+
29+
expect(data.public_key.toBase64()).toStrictEqual("2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws");
30+
expect(data.rendezvous_url).toStrictEqual(rendezvousUrl);
31+
expect(data.mode).toStrictEqual(QrCodeMode.Login);
32+
33+
const encoded = data.to_base64();
34+
expect(base64Data).toStrictEqual(encoded);
35+
});
36+
});

0 commit comments

Comments
 (0)