diff --git a/Cargo.toml b/Cargo.toml index 8eb93cde..c16adf6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ resolver = "2" [workspace.dependencies] assert_matches = "1.5.0" assign = "1.1.1" -base64 = "0.13.1" +base64 = "0.20.0" criterion = "0.4.0" http = "0.2.8" js_int = "0.2.2" diff --git a/crates/ruma-common/src/serde/base64.rs b/crates/ruma-common/src/serde/base64.rs index 5fe052ec..f0bcd95c 100644 --- a/crates/ruma-common/src/serde/base64.rs +++ b/crates/ruma-common/src/serde/base64.rs @@ -2,6 +2,7 @@ use std::{fmt, marker::PhantomData}; +use base64::engine::fast_portable::{self, FastPortable, FastPortableConfig}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; /// A wrapper around `B` (usually `Vec`) that (de)serializes from / to a base64 string. @@ -25,7 +26,7 @@ pub trait Base64Config { } #[doc(hidden)] -pub struct Conf(base64::Config); +pub struct Conf(base64::alphabet::Alphabet); /// Standard base64 character set without padding. /// @@ -36,8 +37,7 @@ pub struct Conf(base64::Config); pub struct Standard; impl Base64Config for Standard { - // See https://github.com/matrix-org/matrix-spec/issues/838 - const CONF: Conf = Conf(base64::STANDARD_NO_PAD.decode_allow_trailing_bits(true)); + const CONF: Conf = Conf(base64::alphabet::STANDARD); } /// Url-safe base64 character set without padding. @@ -49,7 +49,13 @@ impl Base64Config for Standard { pub struct UrlSafe; impl Base64Config for UrlSafe { - const CONF: Conf = Conf(base64::URL_SAFE_NO_PAD.decode_allow_trailing_bits(true)); + const CONF: Conf = Conf(base64::alphabet::URL_SAFE); +} + +impl Base64 { + // See https://github.com/matrix-org/matrix-spec/issues/838 + const CONFIG: FastPortableConfig = fast_portable::NO_PAD.with_decode_allow_trailing_bits(true); + const ENGINE: FastPortable = FastPortable::from(&C::CONF.0, Self::CONFIG); } impl> Base64 { @@ -65,7 +71,7 @@ impl> Base64 { /// Encode the bytes contained in this `Base64` instance to unpadded base64. pub fn encode(&self) -> String { - base64::encode_config(&self.bytes, C::CONF.0) + base64::encode_engine(self.as_bytes(), &Self::ENGINE) } } @@ -84,7 +90,7 @@ impl Base64 { /// Parse some base64-encoded data to create a `Base64` instance. pub fn parse(encoded: impl AsRef<[u8]>) -> Result { - base64::decode_config(encoded, C::CONF.0).map(Self::new).map_err(Base64DecodeError) + base64::decode_engine(encoded, &Self::ENGINE).map(Self::new).map_err(Base64DecodeError) } } diff --git a/crates/ruma-signatures/src/functions.rs b/crates/ruma-signatures/src/functions.rs index 343a029f..3e0b89e3 100644 --- a/crates/ruma-signatures/src/functions.rs +++ b/crates/ruma-signatures/src/functions.rs @@ -6,7 +6,7 @@ use std::{ mem, }; -use base64::{encode_config, STANDARD_NO_PAD, URL_SAFE_NO_PAD}; +use base64::{alphabet, encode_engine}; use ruma_common::{ canonical_json::{redact, JsonType}, serde::{base64::Standard, Base64}, @@ -55,16 +55,18 @@ static REFERENCE_HASH_FIELDS_TO_REMOVE: &[&str] = &["age_ts", "signatures", "uns /// A homeserver signs JSON with a key pair: /// /// ```rust +/// # use ruma_common::serde::base64::Base64; +/// # /// const PKCS8: &str = "\ /// MFECAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/\ -/// tA0T+6tgSEA3TPraTczVkDPTRaX4K+AfUuyx7Mzq1UafTXypnl0t2k=\ +/// tA0T+6tgSEA3TPraTczVkDPTRaX4K+AfUuyx7Mzq1UafTXypnl0t2k\ /// "; /// -/// let document = base64::decode_config(&PKCS8, base64::STANDARD_NO_PAD).unwrap(); +/// let document: Base64 = Base64::parse(PKCS8).unwrap(); /// /// // Create an Ed25519 key pair. /// let key_pair = ruma_signatures::Ed25519KeyPair::from_der( -/// &document, +/// document.as_bytes(), /// "1".into(), // The "version" of the key. /// ) /// .unwrap(); @@ -331,14 +333,17 @@ pub fn reference_hash( let hash = Sha256::digest(json.as_bytes()); - Ok(encode_config( - hash, - match version { - RoomVersionId::V1 | RoomVersionId::V2 | RoomVersionId::V3 => STANDARD_NO_PAD, - // Room versions higher than version 3 are url safe base64 encoded - _ => URL_SAFE_NO_PAD, - }, - )) + let base64_alphabet = match version { + RoomVersionId::V1 | RoomVersionId::V2 | RoomVersionId::V3 => alphabet::STANDARD, + // Room versions higher than version 3 are url safe base64 encoded + _ => alphabet::URL_SAFE, + }; + let base64_engine = base64::engine::fast_portable::FastPortable::from( + &base64_alphabet, + base64::engine::fast_portable::NO_PAD, + ); + + Ok(encode_engine(hash, &base64_engine)) } /// Hashes and signs an event and adds the hash and signature to objects under the keys `hashes` and @@ -366,19 +371,19 @@ pub fn reference_hash( /// # Examples /// /// ```rust -/// # use ruma_common::RoomVersionId; +/// # use ruma_common::{RoomVersionId, serde::base64::Base64}; /// # use ruma_signatures::{hash_and_sign_event, Ed25519KeyPair}; /// # /// const PKCS8: &str = "\ /// MFECAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/\ -/// tA0T+6tgSEA3TPraTczVkDPTRaX4K+AfUuyx7Mzq1UafTXypnl0t2k=\ +/// tA0T+6tgSEA3TPraTczVkDPTRaX4K+AfUuyx7Mzq1UafTXypnl0t2k\ /// "; /// -/// let document = base64::decode_config(&PKCS8, base64::STANDARD_NO_PAD).unwrap(); +/// let document: Base64 = Base64::parse(PKCS8).unwrap(); /// /// // Create an Ed25519 key pair. /// let key_pair = Ed25519KeyPair::from_der( -/// &document, +/// document.as_bytes(), /// "1".into(), // The "version" of the key. /// ) /// .unwrap(); diff --git a/crates/ruma-signatures/src/lib.rs b/crates/ruma-signatures/src/lib.rs index 3bd6a2a6..518ea88c 100644 --- a/crates/ruma-signatures/src/lib.rs +++ b/crates/ruma-signatures/src/lib.rs @@ -103,29 +103,29 @@ fn split_id(id: &str) -> Result<(Algorithm, String), Error> { mod tests { use std::collections::BTreeMap; - use base64::{decode_config, STANDARD_NO_PAD}; use pkcs8::{der::Decode, PrivateKeyInfo}; - use ruma_common::{serde::Base64, RoomVersionId}; + use ruma_common::{ + serde::{base64::Standard, Base64}, + RoomVersionId, + }; use serde_json::{from_str as from_json_str, to_string as to_json_string}; use super::{ canonical_json, hash_and_sign_event, sign_json, verify_event, verify_json, Ed25519KeyPair, }; - const PKCS8: &str = "\ - MFECAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/\ - tA0T+6tgSEA3TPraTczVkDPTRaX4K+AfUuyx7Mzq1UafTXypnl0t2k=\ - "; + fn pkcs8() -> Vec { + const ENCODED: &str = "\ + MFECAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/\ + tA0T+6tgSEA3TPraTczVkDPTRaX4K+AfUuyx7Mzq1UafTXypnl0t2k\ + "; + + Base64::::parse(ENCODED).unwrap().into_inner() + } /// Convenience method for getting the public key as a string fn public_key_string() -> Base64 { - Base64::new( - PrivateKeyInfo::from_der(&decode_config(PKCS8, STANDARD_NO_PAD).unwrap()) - .unwrap() - .public_key - .unwrap() - .to_owned(), - ) + Base64::new(PrivateKeyInfo::from_der(&pkcs8()).unwrap().public_key.unwrap().to_owned()) } /// Convenience for converting a string of JSON into its canonical form. @@ -225,11 +225,7 @@ mod tests { #[test] fn sign_empty_json() { - let key_pair = Ed25519KeyPair::from_der( - decode_config(PKCS8, STANDARD_NO_PAD).unwrap().as_slice(), - "1".into(), - ) - .unwrap(); + let key_pair = Ed25519KeyPair::from_der(&pkcs8(), "1".into()).unwrap(); let mut value = from_json_str("{}").unwrap(); @@ -256,11 +252,7 @@ mod tests { #[test] fn sign_minimal_json() { - let key_pair = Ed25519KeyPair::from_der( - decode_config(PKCS8, STANDARD_NO_PAD).unwrap().as_slice(), - "1".into(), - ) - .unwrap(); + let key_pair = Ed25519KeyPair::from_der(&pkcs8(), "1".into()).unwrap(); let mut alpha_object = from_json_str(r#"{ "one": 1, "two": "Two" }"#).unwrap(); sign_json("domain", &key_pair, &mut alpha_object).unwrap(); @@ -316,11 +308,7 @@ mod tests { #[test] fn sign_minimal_event() { - let key_pair = Ed25519KeyPair::from_der( - decode_config(PKCS8, STANDARD_NO_PAD).unwrap().as_slice(), - "1".into(), - ) - .unwrap(); + let key_pair = Ed25519KeyPair::from_der(&pkcs8(), "1".into()).unwrap(); let json = r#"{ "room_id": "!x:domain", @@ -350,11 +338,7 @@ mod tests { #[test] fn sign_redacted_event() { - let key_pair = Ed25519KeyPair::from_der( - decode_config(PKCS8, STANDARD_NO_PAD).unwrap().as_slice(), - "1".into(), - ) - .unwrap(); + let key_pair = Ed25519KeyPair::from_der(&pkcs8(), "1".into()).unwrap(); let json = r#"{ "content": { diff --git a/crates/ruma-signatures/src/signatures.rs b/crates/ruma-signatures/src/signatures.rs index 93794f48..244557b9 100644 --- a/crates/ruma-signatures/src/signatures.rs +++ b/crates/ruma-signatures/src/signatures.rs @@ -1,6 +1,6 @@ //! Digital signatures and collections of signatures. -use base64::{encode_config, STANDARD_NO_PAD}; +use ruma_common::serde::{base64::Standard, Base64}; use crate::{split_id, Algorithm, Error}; @@ -61,7 +61,7 @@ impl Signature { /// /// Uses the standard character set with no padding. pub fn base64(&self) -> String { - encode_config(self.signature.as_slice(), STANDARD_NO_PAD) + Base64::::new(self.signature.as_slice()).encode() } /// The key identifier, a string containing the signature algorithm and the key "version"