qrcode: Document the qrcode crate.

master
Damir Jelić 2021-05-20 15:02:14 +02:00
parent f49f5f1636
commit 8018b43443
4 changed files with 361 additions and 28 deletions

View File

@ -4,10 +4,16 @@ version = "0.1.0"
authors = ["Damir Jelić <poljar@termina.org.uk>"] authors = ["Damir Jelić <poljar@termina.org.uk>"]
edition = "2018" edition = "2018"
[package.metadata.docs.rs]
features = ["docs"]
rustdoc-args = ["--cfg", "feature=\"docs\""]
[features] [features]
default = ["decode_image"] default = ["decode_image"]
decode_image = ["image", "rqrr", "qrcode/image", "qrcode/svg"] decode_image = ["image", "rqrr", "qrcode/image", "qrcode/svg"]
docs = ["decode_image"]
[dependencies] [dependencies]
base64 = "0.13.0" base64 = "0.13.0"
byteorder = "1.4.3" byteorder = "1.4.3"

View File

@ -14,34 +14,48 @@
use thiserror::Error; use thiserror::Error;
/// Error type describing errors that happen while QR data is being decoded.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum DecodingError { pub enum DecodingError {
/// Error decoding the QR code.
#[cfg(feature = "decode_image")] #[cfg(feature = "decode_image")]
#[cfg_attr(feature = "docs", doc(cfg(decode_image)))] #[cfg_attr(feature = "docs", doc(cfg(decode_image)))]
#[error(transparent)] #[error(transparent)]
Qr(#[from] rqrr::DeQRError), Qr(#[from] rqrr::DeQRError),
/// The QR code data is missing the mandatory Matrix header.
#[error("the decoded QR code is missing the Matrix header")] #[error("the decoded QR code is missing the Matrix header")]
Header, Header,
/// The QR code data is containing an invalid, non UTF-8, flow id.
#[error(transparent)] #[error(transparent)]
Utf8(#[from] std::string::FromUtf8Error), 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}")] #[error("the QR code contains an invalid verification mode: {0}")]
Mode(u8), Mode(u8),
/// The flow id is not a valid event ID.
#[error(transparent)] #[error(transparent)]
Identifier(#[from] ruma_identifiers::Error), Identifier(#[from] ruma_identifiers::Error),
#[error(transparent)] #[error(transparent)]
/// The QR code data does not contain all the necessary fields.
Read(#[from] std::io::Error), 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}")] #[error("the QR code contains a too short shared secret, length: {0}")]
SharedSecret(usize), SharedSecret(usize),
/// The QR code data uses an invalid or unsupported version.
#[error("the QR code contains an invalid or unsupported version: {0}")] #[error("the QR code contains an invalid or unsupported version: {0}")]
Version(u8), Version(u8),
} }
/// Error type describing errors that happen while QR data is being encoded.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum EncodingError { pub enum EncodingError {
/// Error generating a QR code from the data, likely because the data
/// doesn't fit into a QR code.
#[error(transparent)] #[error(transparent)]
Qr(#[from] qrcode::types::QrError), Qr(#[from] qrcode::types::QrError),
/// Error decoding the identity keys as base64.
#[error(transparent)] #[error(transparent)]
Base64(#[from] base64::DecodeError), 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}")] #[error("The verification flow id length can't be converted into a u16: {0}")]
FlowId(#[from] std::num::TryFromIntError), FlowId(#[from] std::num::TryFromIntError),
} }

View File

@ -30,10 +30,12 @@
//! # } //! # }
//! ``` //! ```
#![cfg_attr(feature = "docs", feature(doc_cfg))]
#![deny( #![deny(
missing_debug_implementations, missing_debug_implementations,
dead_code, dead_code,
trivial_casts, trivial_casts,
missing_docs,
trivial_numeric_casts, trivial_numeric_casts,
unused_extern_crates, unused_extern_crates,
unused_import_braces, unused_import_braces,

View File

@ -31,10 +31,16 @@ use crate::{
utils::{base_64_encode, to_bytes, to_qr_code, HEADER, MAX_MODE, MIN_SECRET_LEN, VERSION}, 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)] #[derive(Clone, Debug, PartialEq)]
pub enum QrVerification { pub enum QrVerification {
/// The QR verification is verifying another user
Verification(VerificationData), Verification(VerificationData),
/// The QR verification is self-verifying and the current device trusts or
/// owns the master key
SelfVerification(SelfVerificationData), SelfVerification(SelfVerificationData),
/// The QR verification is self-verifying in which the current device does
/// not yet trust the master key
SelfVerificationNoMasterKey(SelfVerificationNoMasterKey), SelfVerificationNoMasterKey(SelfVerificationNoMasterKey),
} }
@ -75,6 +81,26 @@ impl TryFrom<Vec<u8>> for QrVerification {
} }
impl 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(feature = "decode_image")]
#[cfg_attr(feature = "docs", doc(cfg(decode_image)))] #[cfg_attr(feature = "docs", doc(cfg(decode_image)))]
pub fn from_image(image: DynamicImage) -> Result<Self, DecodingError> { pub fn from_image(image: DynamicImage) -> Result<Self, DecodingError> {
@ -82,16 +108,85 @@ impl QrVerification {
Self::decode(image) 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(feature = "decode_image")]
#[cfg_attr(feature = "docs", doc(cfg(decode_image)))] #[cfg_attr(feature = "docs", doc(cfg(decode_image)))]
pub fn from_luma(image: ImageBuffer<Luma<u8>, Vec<u8>>) -> Result<Self, DecodingError> { pub fn from_luma(image: ImageBuffer<Luma<u8>, Vec<u8>>) -> Result<Self, DecodingError> {
Self::decode(image) 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, DecodingError> { pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, DecodingError> {
Self::decode_bytes(bytes) 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<QrCode, EncodingError> { pub fn to_qr_code(&self) -> Result<QrCode, EncodingError> {
match self { match self {
QrVerification::Verification(v) => v.to_qr_code(), 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<Vec<u8>, EncodingError> { pub fn to_bytes(&self) -> Result<Vec<u8>, EncodingError> {
match self { match self {
QrVerification::Verification(v) => v.to_bytes(), 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<Self, DecodingError> { fn decode_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, DecodingError> {
let mut decoded = Cursor::new(bytes); let mut decoded = Cursor::new(bytes);
@ -145,8 +292,9 @@ impl QrVerification {
QrVerification::new(mode, flow_id, first_key, second_key, shared_secret) 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(feature = "decode_image")]
#[cfg_attr(feature = "docs", doc(cfg(decode_image)))]
fn decode(image: ImageBuffer<Luma<u8>, Vec<u8>>) -> Result<QrVerification, DecodingError> { fn decode(image: ImageBuffer<Luma<u8>, Vec<u8>>) -> Result<QrVerification, DecodingError> {
let decoded = decode_qr(image)?; let decoded = decode_qr(image)?;
Self::decode_bytes(decoded) 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)] #[derive(Clone, Debug, PartialEq)]
pub struct VerificationData { pub struct VerificationData {
event_id: EventId, event_id: EventId,
@ -192,6 +344,55 @@ pub struct VerificationData {
impl VerificationData { impl VerificationData {
const QR_MODE: u8 = 0x00; 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<Vec<u8>, EncodingError> { pub fn to_bytes(&self) -> Result<Vec<u8>, EncodingError> {
to_bytes( to_bytes(
Self::QR_MODE, 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<QrCode, EncodingError> { pub fn to_qr_code(&self) -> Result<QrCode, EncodingError> {
to_qr_code( to_qr_code(
Self::QR_MODE, Self::QR_MODE,
@ -211,15 +419,6 @@ impl VerificationData {
&self.shared_secret, &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<VerificationData> for QrVerification { impl From<VerificationData> for QrVerification {
@ -228,6 +427,11 @@ impl From<VerificationData> 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)] #[derive(Clone, Debug, PartialEq)]
pub struct SelfVerificationData { pub struct SelfVerificationData {
transaction_id: String, transaction_id: String,
@ -239,6 +443,59 @@ pub struct SelfVerificationData {
impl SelfVerificationData { impl SelfVerificationData {
const QR_MODE: u8 = 0x01; 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<Vec<u8>, EncodingError> { pub fn to_bytes(&self) -> Result<Vec<u8>, EncodingError> {
to_bytes( to_bytes(
Self::QR_MODE, 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<QrCode, EncodingError> { pub fn to_qr_code(&self) -> Result<QrCode, EncodingError> {
to_qr_code( to_qr_code(
Self::QR_MODE, Self::QR_MODE,
@ -258,15 +522,6 @@ impl SelfVerificationData {
&self.shared_secret, &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<SelfVerificationData> for QrVerification { impl From<SelfVerificationData> for QrVerification {
@ -275,6 +530,11 @@ impl From<SelfVerificationData> 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)] #[derive(Clone, Debug, PartialEq)]
pub struct SelfVerificationNoMasterKey { pub struct SelfVerificationNoMasterKey {
transaction_id: String, transaction_id: String,
@ -286,6 +546,59 @@ pub struct SelfVerificationNoMasterKey {
impl SelfVerificationNoMasterKey { impl SelfVerificationNoMasterKey {
const QR_MODE: u8 = 0x02; 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<Vec<u8>, EncodingError> { pub fn to_bytes(&self) -> Result<Vec<u8>, EncodingError> {
to_bytes( to_bytes(
Self::QR_MODE, 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<QrCode, EncodingError> { pub fn to_qr_code(&self) -> Result<QrCode, EncodingError> {
to_qr_code( to_qr_code(
Self::QR_MODE, Self::QR_MODE,
@ -305,15 +625,6 @@ impl SelfVerificationNoMasterKey {
&self.shared_secret, &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<SelfVerificationNoMasterKey> for QrVerification { impl From<SelfVerificationNoMasterKey> for QrVerification {