diff --git a/matrix_qrcode/Cargo.toml b/matrix_qrcode/Cargo.toml index f9ab6f34..f3d26a12 100644 --- a/matrix_qrcode/Cargo.toml +++ b/matrix_qrcode/Cargo.toml @@ -4,10 +4,16 @@ version = "0.1.0" authors = ["Damir Jelić "] edition = "2018" +[package.metadata.docs.rs] +features = ["docs"] +rustdoc-args = ["--cfg", "feature=\"docs\""] + [features] default = ["decode_image"] decode_image = ["image", "rqrr", "qrcode/image", "qrcode/svg"] +docs = ["decode_image"] + [dependencies] base64 = "0.13.0" byteorder = "1.4.3" diff --git a/matrix_qrcode/src/error.rs b/matrix_qrcode/src/error.rs index 0dc3303c..cb628c09 100644 --- a/matrix_qrcode/src/error.rs +++ b/matrix_qrcode/src/error.rs @@ -14,34 +14,48 @@ use thiserror::Error; +/// Error type describing errors that happen while QR data is being decoded. #[derive(Error, Debug)] pub enum DecodingError { + /// Error decoding the QR code. #[cfg(feature = "decode_image")] #[cfg_attr(feature = "docs", doc(cfg(decode_image)))] #[error(transparent)] Qr(#[from] rqrr::DeQRError), + /// The QR code data is missing the mandatory Matrix header. #[error("the decoded QR code is missing the Matrix header")] Header, + /// The QR code data is containing an invalid, non UTF-8, flow id. #[error(transparent)] Utf8(#[from] std::string::FromUtf8Error), + /// The QR code data is using an unsupported or invalid verification mode. #[error("the QR code contains an invalid verification mode: {0}")] Mode(u8), + /// The flow id is not a valid event ID. #[error(transparent)] Identifier(#[from] ruma_identifiers::Error), #[error(transparent)] + /// The QR code data does not contain all the necessary fields. Read(#[from] std::io::Error), + /// The QR code data uses an invalid shared secret. #[error("the QR code contains a too short shared secret, length: {0}")] SharedSecret(usize), + /// The QR code data uses an invalid or unsupported version. #[error("the QR code contains an invalid or unsupported version: {0}")] Version(u8), } +/// Error type describing errors that happen while QR data is being encoded. #[derive(Error, Debug)] pub enum EncodingError { + /// Error generating a QR code from the data, likely because the data + /// doesn't fit into a QR code. #[error(transparent)] Qr(#[from] qrcode::types::QrError), + /// Error decoding the identity keys as base64. #[error(transparent)] Base64(#[from] base64::DecodeError), + /// Error encoding the given flow id, the flow id is too large. #[error("The verification flow id length can't be converted into a u16: {0}")] FlowId(#[from] std::num::TryFromIntError), } diff --git a/matrix_qrcode/src/lib.rs b/matrix_qrcode/src/lib.rs index a297e908..91e60735 100644 --- a/matrix_qrcode/src/lib.rs +++ b/matrix_qrcode/src/lib.rs @@ -30,10 +30,12 @@ //! # } //! ``` +#![cfg_attr(feature = "docs", feature(doc_cfg))] #![deny( missing_debug_implementations, dead_code, trivial_casts, + missing_docs, trivial_numeric_casts, unused_extern_crates, unused_import_braces, diff --git a/matrix_qrcode/src/types.rs b/matrix_qrcode/src/types.rs index 38022a5b..d31161a4 100644 --- a/matrix_qrcode/src/types.rs +++ b/matrix_qrcode/src/types.rs @@ -31,10 +31,16 @@ use crate::{ utils::{base_64_encode, to_bytes, to_qr_code, HEADER, MAX_MODE, MIN_SECRET_LEN, VERSION}, }; +/// An enum representing the different modes a QR verification can be in. #[derive(Clone, Debug, PartialEq)] pub enum QrVerification { + /// The QR verification is verifying another user Verification(VerificationData), + /// The QR verification is self-verifying and the current device trusts or + /// owns the master key SelfVerification(SelfVerificationData), + /// The QR verification is self-verifying in which the current device does + /// not yet trust the master key SelfVerificationNoMasterKey(SelfVerificationNoMasterKey), } @@ -75,6 +81,26 @@ impl TryFrom> for QrVerification { } impl QrVerification { + /// Decode and parse an image of a QR code into a `QrVerification` + /// + /// The image will be converted into a grey scale image before decoding is + /// attempted + /// + /// # Arguments + /// + /// * `image` - The image containing the QR code. + /// + /// # Example + /// ```no_run + /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # fn main() -> Result<(), DecodingError> { + /// use image; + /// + /// let image = image::open("/path/to/my/image.png").unwrap(); + /// let result = QrVerification::from_image(image)?; + /// # Ok(()) + /// # } + /// ``` #[cfg(feature = "decode_image")] #[cfg_attr(feature = "docs", doc(cfg(decode_image)))] pub fn from_image(image: DynamicImage) -> Result { @@ -82,16 +108,85 @@ impl QrVerification { Self::decode(image) } + /// Decode and parse an grey scale image of a QR code into a + /// `QrVerification` + /// + /// # Arguments + /// + /// * `image` - The grey scale image containing the QR code. + /// + /// # Example + /// ```no_run + /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # fn main() -> Result<(), DecodingError> { + /// use image; + /// + /// let image = image::open("/path/to/my/image.png").unwrap(); + /// let image = image.to_luma8(); + /// let result = QrVerification::from_luma(image)?; + /// # Ok(()) + /// # } + /// ``` #[cfg(feature = "decode_image")] #[cfg_attr(feature = "docs", doc(cfg(decode_image)))] pub fn from_luma(image: ImageBuffer, Vec>) -> Result { Self::decode(image) } + /// Parse the decoded payload of a QR code in byte slice form as a + /// `QrVerification` + /// + /// This method is useful if you would like to do your own custom QR code + /// decoding. + /// + /// # Arguments + /// + /// * `bytes` - The raw bytes of a decoded QR code. + /// + /// # Example + /// ``` + /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # fn main() -> Result<(), DecodingError> { + /// let data = b"MATRIX\ + /// \x02\x02\x00\x07\ + /// FLOW_ID\ + /// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ + /// SHARED_SECRET"; + /// + /// let result = QrVerification::from_bytes(data)?; + /// # Ok(()) + /// # } + /// ``` pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { Self::decode_bytes(bytes) } + /// Encode the `QrVerification` into a `QrCode`. + /// + /// This method turns the `QrVerification` into a QR code that can be + /// rendered and presented to be scanned. + /// + /// The encoding can fail if the data doesn't fit into a QR code or if the + /// identity keys that should be encoded into the QR code are not valid + /// base64. + /// + /// # Example + /// ``` + /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # fn main() -> Result<(), DecodingError> { + /// let data = b"MATRIX\ + /// \x02\x02\x00\x07\ + /// FLOW_ID\ + /// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ + /// SHARED_SECRET"; + /// + /// let result = QrVerification::from_bytes(data)?; + /// let encoded = result.to_qr_code().unwrap(); + /// # Ok(()) + /// # } + /// ``` pub fn to_qr_code(&self) -> Result { match self { QrVerification::Verification(v) => v.to_qr_code(), @@ -100,6 +195,30 @@ impl QrVerification { } } + /// Encode the `QrVerification` into a vector of bytes that can be encoded + /// as a QR code. + /// + /// The encoding can fail if the identity keys that should be encoded are + /// not valid base64. + /// + /// # Example + /// ``` + /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # fn main() -> Result<(), DecodingError> { + /// let data = b"MATRIX\ + /// \x02\x02\x00\x07\ + /// FLOW_ID\ + /// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ + /// SHARED_SECRET"; + /// + /// let result = QrVerification::from_bytes(data)?; + /// let encoded = result.to_bytes().unwrap(); + /// + /// assert_eq!(data.as_ref(), encoded.as_slice()); + /// # Ok(()) + /// # } + /// ``` pub fn to_bytes(&self) -> Result, EncodingError> { match self { QrVerification::Verification(v) => v.to_bytes(), @@ -108,6 +227,34 @@ impl QrVerification { } } + /// Decode the byte slice containing the decoded QR code data. + /// + /// The format is defined in the [spec]. + /// + /// The byte slice consists of the following parts: + /// + /// * the ASCII string MATRIX + /// * one byte indicating the QR code version (must be 0x02) + /// * one byte indicating the QR code verification mode. one of the + /// following + /// values: + /// * 0x00 verifying another user with cross-signing + /// * 0x01 self-verifying in which the current device does trust the + /// master key + /// * 0x02 self-verifying in which the current device does not yet trust + /// the master key + /// * the event ID or transaction_id of the associated verification request + /// event, encoded as: + /// * two bytes in network byte order (big-endian) indicating the length + /// in bytes of the ID as a UTF-8 string + /// * the ID as a UTF-8 string + /// * the first key, as 32 bytes + /// * the second key, as 32 bytes + /// * a random shared secret, as a byte string. as we do not share the + /// length of the secret, and it is not a fixed size, clients will just + /// use the remainder of binary string as the shared secret. + /// + /// [spec]: https://spec.matrix.org/unstable/client-server-api/#qr-code-format fn decode_bytes(bytes: impl AsRef<[u8]>) -> Result { let mut decoded = Cursor::new(bytes); @@ -145,8 +292,9 @@ impl QrVerification { QrVerification::new(mode, flow_id, first_key, second_key, shared_secret) } + /// Decode the given image of an QR code and if we find a valid code, try to + /// decode it as a `QrVerification`. #[cfg(feature = "decode_image")] - #[cfg_attr(feature = "docs", doc(cfg(decode_image)))] fn decode(image: ImageBuffer, Vec>) -> Result { let decoded = decode_qr(image)?; Self::decode_bytes(decoded) @@ -181,6 +329,10 @@ impl QrVerification { } } +/// The non-encoded data for the first mode of QR code verification. +/// +/// This mode is used for verification between two users using their master +/// cross signing keys. #[derive(Clone, Debug, PartialEq)] pub struct VerificationData { event_id: EventId, @@ -192,6 +344,55 @@ pub struct VerificationData { impl VerificationData { const QR_MODE: u8 = 0x00; + /// Create a new `VerificationData` struct that can be encoded as a QR code. + /// + /// # Arguments + /// * `event_id` - The event id of the `m.key.verification.request` event + /// that initiated the verification flow this QR code should be part of. + /// + /// * `first_key` - Our own cross signing master key. Needs to be encoded as + /// unpadded base64 + /// + /// * `second_key` - The cross signing master key of the other user. + /// + /// * ` shared_secret` - A random bytestring encoded as unpadded base64, + /// needs to be at least 8 bytes long. + pub fn new( + event_id: EventId, + first_key: String, + second_key: String, + shared_secret: String, + ) -> Self { + Self { event_id, first_master_key: first_key, second_master_key: second_key, shared_secret } + } + + /// Encode the `VerificationData` into a vector of bytes that can be + /// encoded as a QR code. + /// + /// The encoding can fail if the master keys that should be encoded are not + /// valid base64. + /// + /// # Example + /// ``` + /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # fn main() -> Result<(), DecodingError> { + /// let data = b"MATRIX\ + /// \x02\x00\x00\x0f\ + /// $test:localhost\ + /// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ + /// SHARED_SECRET"; + /// + /// let result = QrVerification::from_bytes(data)?; + /// if let QrVerification::Verification(decoded) = result { + /// let encoded = decoded.to_bytes().unwrap(); + /// assert_eq!(data.as_ref(), encoded.as_slice()); + /// } else { + /// panic!("Data was encoded as an incorrect mode"); + /// } + /// # Ok(()) + /// # } + /// ``` pub fn to_bytes(&self) -> Result, EncodingError> { to_bytes( Self::QR_MODE, @@ -202,6 +403,13 @@ impl VerificationData { ) } + /// Encode the `VerificationData` into a `QrCode`. + /// + /// This method turns the `VerificationData` into a QR code that can be + /// rendered and presented to be scanned. + /// + /// The encoding can fail if the data doesn't fit into a QR code or if the + /// keys that should be encoded into the QR code are not valid base64. pub fn to_qr_code(&self) -> Result { to_qr_code( Self::QR_MODE, @@ -211,15 +419,6 @@ impl VerificationData { &self.shared_secret, ) } - - pub fn new( - event_id: EventId, - first_key: String, - second_key: String, - shared_secret: String, - ) -> Self { - Self { event_id, first_master_key: first_key, second_master_key: second_key, shared_secret } - } } impl From for QrVerification { @@ -228,6 +427,11 @@ impl From for QrVerification { } } +/// The non-encoded data for the second mode of QR code verification. +/// +/// This mode is used for verification between two devices of the same user +/// where this device, that is creating this QR code, is trusting or owning +/// the cross signing master key. #[derive(Clone, Debug, PartialEq)] pub struct SelfVerificationData { transaction_id: String, @@ -239,6 +443,59 @@ pub struct SelfVerificationData { impl SelfVerificationData { const QR_MODE: u8 = 0x01; + /// Create a new `SelfVerificationData` struct that can be encoded as a QR + /// code. + /// + /// # Arguments + /// * `transaction_id` - The transaction id of this verification flow, the + /// transaction id was sent by the `m.key.verification.request` event + /// that initiated the verification flow this QR code should be part of. + /// + /// * `master_key` - Our own cross signing master key. Needs to be encoded + /// as + /// unpadded base64 + /// + /// * `device_key` - The ed25519 key of the other device, encoded as + /// unpadded base64. + /// + /// * ` shared_secret` - A random bytestring encoded as unpadded base64, + /// needs to be at least 8 bytes long. + pub fn new( + transaction_id: String, + master_key: String, + device_key: String, + shared_secret: String, + ) -> Self { + Self { transaction_id, master_key, device_key, shared_secret } + } + + /// Encode the `SelfVerificationData` into a vector of bytes that can be + /// encoded as a QR code. + /// + /// The encoding can fail if the keys that should be encoded are not valid + /// base64. + /// + /// # Example + /// ``` + /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # fn main() -> Result<(), DecodingError> { + /// let data = b"MATRIX\ + /// \x02\x01\x00\x06\ + /// FLOWID\ + /// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ + /// SHARED_SECRET"; + /// + /// let result = QrVerification::from_bytes(data)?; + /// if let QrVerification::SelfVerification(decoded) = result { + /// let encoded = decoded.to_bytes().unwrap(); + /// assert_eq!(data.as_ref(), encoded.as_slice()); + /// } else { + /// panic!("Data was encoded as an incorrect mode"); + /// } + /// # Ok(()) + /// # } + /// ``` pub fn to_bytes(&self) -> Result, EncodingError> { to_bytes( Self::QR_MODE, @@ -249,6 +506,13 @@ impl SelfVerificationData { ) } + /// Encode the `SelfVerificationData` into a `QrCode`. + /// + /// This method turns the `SelfVerificationData` into a QR code that can be + /// rendered and presented to be scanned. + /// + /// The encoding can fail if the data doesn't fit into a QR code or if the + /// keys that should be encoded into the QR code are not valid base64. pub fn to_qr_code(&self) -> Result { to_qr_code( Self::QR_MODE, @@ -258,15 +522,6 @@ impl SelfVerificationData { &self.shared_secret, ) } - - pub fn new( - transaction_id: String, - master_key: String, - device_key: String, - shared_secret: String, - ) -> Self { - Self { transaction_id, master_key, device_key, shared_secret } - } } impl From for QrVerification { @@ -275,6 +530,11 @@ impl From for QrVerification { } } +/// The non-encoded data for the third mode of QR code verification. +/// +/// This mode is used for verification between two devices of the same user +/// where this device, that is creating this QR code, is not trusting the +/// cross signing master key. #[derive(Clone, Debug, PartialEq)] pub struct SelfVerificationNoMasterKey { transaction_id: String, @@ -286,6 +546,59 @@ pub struct SelfVerificationNoMasterKey { impl SelfVerificationNoMasterKey { const QR_MODE: u8 = 0x02; + /// Create a new `SelfVerificationData` struct that can be encoded as a QR + /// code. + /// + /// # Arguments + /// * `transaction_id` - The transaction id of this verification flow, the + /// transaction id was sent by the `m.key.verification.request` event + /// that initiated the verification flow this QR code should be part of. + /// + /// * `device_key` - The ed25519 key of our own device, encoded as unpadded + /// base64. + /// + /// * `master_key` - Our own cross signing master key. Needs to be encoded + /// as + /// unpadded base64 + /// + /// * ` shared_secret` - A random bytestring encoded as unpadded base64, + /// needs to be at least 8 bytes long. + pub fn new( + transaction_id: String, + device_key: String, + master_key: String, + shared_secret: String, + ) -> Self { + Self { transaction_id, device_key, master_key, shared_secret } + } + + /// Encode the `SelfVerificationNoMasterKey` into a vector of bytes that can + /// be encoded as a QR code. + /// + /// The encoding can fail if the keys that should be encoded are not valid + /// base64. + /// + /// # Example + /// ``` + /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # fn main() -> Result<(), DecodingError> { + /// let data = b"MATRIX\ + /// \x02\x02\x00\x06\ + /// FLOWID\ + /// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ + /// SHARED_SECRET"; + /// + /// let result = QrVerification::from_bytes(data)?; + /// if let QrVerification::SelfVerificationNoMasterKey(decoded) = result { + /// let encoded = decoded.to_bytes().unwrap(); + /// assert_eq!(data.as_ref(), encoded.as_slice()); + /// } else { + /// panic!("Data was encoded as an incorrect mode"); + /// } + /// # Ok(()) + /// # } + /// ``` pub fn to_bytes(&self) -> Result, EncodingError> { to_bytes( Self::QR_MODE, @@ -296,6 +609,13 @@ impl SelfVerificationNoMasterKey { ) } + /// Encode the `SelfVerificationNoMasterKey` into a `QrCode`. + /// + /// This method turns the `SelfVerificationNoMasterKey` into a QR code that + /// can be rendered and presented to be scanned. + /// + /// The encoding can fail if the data doesn't fit into a QR code or if the + /// keys that should be encoded into the QR code are not valid base64. pub fn to_qr_code(&self) -> Result { to_qr_code( Self::QR_MODE, @@ -305,15 +625,6 @@ impl SelfVerificationNoMasterKey { &self.shared_secret, ) } - - pub fn new( - transaction_id: String, - device_key: String, - master_key: String, - shared_secret: String, - ) -> Self { - Self { transaction_id, device_key, master_key, shared_secret } - } } impl From for QrVerification {