diff --git a/crates/ruma-client-api/src/r0/backup.rs b/crates/ruma-client-api/src/r0/backup.rs index c2e9f0cd..ec266a42 100644 --- a/crates/ruma-client-api/src/r0/backup.rs +++ b/crates/ruma-client-api/src/r0/backup.rs @@ -19,7 +19,7 @@ use std::collections::BTreeMap; use js_int::UInt; use ruma_identifiers::{DeviceKeyId, UserId}; -use ruma_serde::Raw; +use ruma_serde::{Base64, Raw}; use serde::{Deserialize, Serialize}; /// A wrapper around a mapping of session IDs to key data. @@ -46,7 +46,7 @@ pub enum BackupAlgorithm { #[serde(rename = "m.megolm_backup.v1.curve25519-aes-sha2")] MegolmBackupV1Curve25519AesSha2 { /// The curve25519 public key used to encrypt the backups, encoded in unpadded base64. - public_key: String, + public_key: Base64, /// Signatures of the auth_data as Signed JSON. signatures: BTreeMap, BTreeMap, String>>, @@ -109,13 +109,13 @@ impl From for KeyBackupData { #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct SessionData { /// Unpadded base64-encoded public half of the ephemeral key. - pub ephemeral: String, + pub ephemeral: Base64, /// Ciphertext, encrypted using AES-CBC-256 with PKCS#7 padding, encoded in base64. - pub ciphertext: String, + pub ciphertext: Base64, /// First 8 bytes of MAC key, encoded in base64. - pub mac: String, + pub mac: Base64, } /// The algorithm used for storing backups. @@ -126,13 +126,13 @@ pub struct SessionData { #[allow(clippy::exhaustive_structs)] pub struct SessionDataInit { /// Unpadded base64-encoded public half of the ephemeral key. - pub ephemeral: String, + pub ephemeral: Base64, /// Ciphertext, encrypted using AES-CBC-256 with PKCS#7 padding, encoded in base64. - pub ciphertext: String, + pub ciphertext: Base64, /// First 8 bytes of MAC key, encoded in base64. - pub mac: String, + pub mac: Base64, } impl From for SessionData { diff --git a/crates/ruma-common/src/encryption.rs b/crates/ruma-common/src/encryption.rs index 1ce1ee2b..0ceb3cfc 100644 --- a/crates/ruma-common/src/encryption.rs +++ b/crates/ruma-common/src/encryption.rs @@ -5,6 +5,7 @@ use std::collections::BTreeMap; use ruma_identifiers::{DeviceId, DeviceKeyId, EventEncryptionAlgorithm, UserId}; +use ruma_serde::Base64; use serde::{Deserialize, Serialize}; /// Identity keys for a device. @@ -79,7 +80,7 @@ pub type SignedKeySignatures = BTreeMap, BTreeMap, #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct SignedKey { /// Base64-encoded 32-byte Curve25519 public key. - pub key: String, + pub key: Base64, /// Signatures for the key object. pub signatures: SignedKeySignatures, @@ -92,7 +93,7 @@ pub struct SignedKey { impl SignedKey { /// Creates a new `SignedKey` with the given key and signatures. - pub fn new(key: String, signatures: SignedKeySignatures) -> Self { + pub fn new(key: Base64, signatures: SignedKeySignatures) -> Self { Self { key, signatures, @@ -103,7 +104,7 @@ impl SignedKey { /// Creates a new fallback `SignedKey` with the given key and signatures. #[cfg(feature = "unstable-pre-spec")] - pub fn new_fallback(key: String, signatures: SignedKeySignatures) -> Self { + pub fn new_fallback(key: Base64, signatures: SignedKeySignatures) -> Self { Self { key, signatures, fallback: true } } } diff --git a/crates/ruma-events/src/key/verification/accept.rs b/crates/ruma-events/src/key/verification/accept.rs index 7b388a21..7082f7f9 100644 --- a/crates/ruma-events/src/key/verification/accept.rs +++ b/crates/ruma-events/src/key/verification/accept.rs @@ -5,6 +5,7 @@ use std::collections::BTreeMap; use ruma_events_macros::EventContent; +use ruma_serde::Base64; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; @@ -118,7 +119,7 @@ pub struct SasV1Content { /// The hash (encoded as unpadded base64) of the concatenation of the /// device's ephemeral public key (encoded as unpadded base64) and the /// canonical JSON representation of the `m.key.verification.start` message. - pub commitment: String, + pub commitment: Base64, } /// Mandatory initial set of fields for creating an accept `SasV1Content`. @@ -146,7 +147,7 @@ pub struct SasV1ContentInit { /// The hash (encoded as unpadded base64) of the concatenation of the /// device's ephemeral public key (encoded as unpadded base64) and the /// canonical JSON representation of the `m.key.verification.start` message. - pub commitment: String, + pub commitment: Base64, } impl From for SasV1Content { @@ -170,6 +171,7 @@ mod tests { #[cfg(feature = "unstable-pre-spec")] use ruma_identifiers::event_id; use ruma_identifiers::user_id; + use ruma_serde::Base64; use serde_json::{ from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue, }; @@ -193,7 +195,7 @@ mod tests { key_agreement_protocol: KeyAgreementProtocol::Curve25519, message_authentication_code: MessageAuthenticationCode::HkdfHmacSha256, short_authentication_string: vec![ShortAuthenticationString::Decimal], - commitment: "test_commitment".into(), + commitment: Base64::new(b"hello".to_vec()), }), }; @@ -203,7 +205,7 @@ mod tests { "content": { "transaction_id": "456", "method": "m.sas.v1", - "commitment": "test_commitment", + "commitment": "aGVsbG8", "key_agreement_protocol": "curve25519", "hash": "sha256", "message_authentication_code": "hkdf-hmac-sha256", @@ -258,13 +260,13 @@ mod tests { key_agreement_protocol: KeyAgreementProtocol::Curve25519, message_authentication_code: MessageAuthenticationCode::HkdfHmacSha256, short_authentication_string: vec![ShortAuthenticationString::Decimal], - commitment: "test_commitment".into(), + commitment: Base64::new(b"hello".to_vec()), }), }; let json_data = json!({ "method": "m.sas.v1", - "commitment": "test_commitment", + "commitment": "aGVsbG8", "key_agreement_protocol": "curve25519", "hash": "sha256", "message_authentication_code": "hkdf-hmac-sha256", @@ -282,7 +284,7 @@ mod tests { fn deserialization() { let json = json!({ "transaction_id": "456", - "commitment": "test_commitment", + "commitment": "aGVsbG8", "method": "m.sas.v1", "hash": "sha256", "key_agreement_protocol": "curve25519", @@ -302,7 +304,7 @@ mod tests { message_authentication_code, short_authentication_string, }) - } if commitment == "test_commitment" + } if commitment.encode() == "aGVsbG8" && transaction_id == "456" && hash == HashAlgorithm::Sha256 && key_agreement_protocol == KeyAgreementProtocol::Curve25519 @@ -314,7 +316,7 @@ mod tests { let json = json!({ "content": { - "commitment": "test_commitment", + "commitment": "aGVsbG8", "transaction_id": "456", "method": "m.sas.v1", "key_agreement_protocol": "curve25519", @@ -340,7 +342,7 @@ mod tests { short_authentication_string, }) } - } if commitment == "test_commitment" + } if commitment.encode() == "aGVsbG8" && sender == user_id!("@example:localhost") && transaction_id == "456" && hash == HashAlgorithm::Sha256 @@ -386,7 +388,7 @@ mod tests { let id = event_id!("$1598361704261elfgc:localhost"); let json = json!({ - "commitment": "test_commitment", + "commitment": "aGVsbG8", "method": "m.sas.v1", "hash": "sha256", "key_agreement_protocol": "curve25519", @@ -412,7 +414,7 @@ mod tests { message_authentication_code, short_authentication_string, }) - } if commitment == "test_commitment" + } if commitment.encode() == "aGVsbG8" && *event_id == *id && hash == HashAlgorithm::Sha256 && key_agreement_protocol == KeyAgreementProtocol::Curve25519 diff --git a/crates/ruma-events/src/key/verification/key.rs b/crates/ruma-events/src/key/verification/key.rs index e6e208e0..8d31bd69 100644 --- a/crates/ruma-events/src/key/verification/key.rs +++ b/crates/ruma-events/src/key/verification/key.rs @@ -3,6 +3,7 @@ //! [`m.key.verification.key`]: https://spec.matrix.org/v1.1/client-server-api/#mkeyverificationkey use ruma_events_macros::EventContent; +use ruma_serde::Base64; use serde::{Deserialize, Serialize}; #[cfg(feature = "unstable-pre-spec")] @@ -20,14 +21,14 @@ pub struct ToDeviceKeyVerificationKeyEventContent { /// Must be the same as the one used for the `m.key.verification.start` message. pub transaction_id: String, - /// The device's ephemeral public key, encoded as unpadded Base64. - pub key: String, + /// The device's ephemeral public key, encoded as unpadded base64. + pub key: Base64, } impl ToDeviceKeyVerificationKeyEventContent { /// Creates a new `ToDeviceKeyVerificationKeyEventContent` with the given transaction ID and /// key. - pub fn new(transaction_id: String, key: String) -> Self { + pub fn new(transaction_id: String, key: Base64) -> Self { Self { transaction_id, key } } } @@ -40,8 +41,8 @@ impl ToDeviceKeyVerificationKeyEventContent { #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[ruma_event(type = "m.key.verification.key", kind = Message)] pub struct KeyVerificationKeyEventContent { - /// The device's ephemeral public key, encoded as unpadded Base64. - pub key: String, + /// The device's ephemeral public key, encoded as unpadded base64. + pub key: Base64, /// Information about the related event. #[serde(rename = "m.relates_to")] @@ -51,7 +52,7 @@ pub struct KeyVerificationKeyEventContent { #[cfg(feature = "unstable-pre-spec")] impl KeyVerificationKeyEventContent { /// Creates a new `KeyVerificationKeyEventContent` with the given key and relation. - pub fn new(key: String, relates_to: Relation) -> Self { + pub fn new(key: Base64, relates_to: Relation) -> Self { Self { key, relates_to } } } diff --git a/crates/ruma-events/src/key/verification/mac.rs b/crates/ruma-events/src/key/verification/mac.rs index 1016e3d0..4f57892b 100644 --- a/crates/ruma-events/src/key/verification/mac.rs +++ b/crates/ruma-events/src/key/verification/mac.rs @@ -5,6 +5,7 @@ use std::collections::BTreeMap; use ruma_events_macros::EventContent; +use ruma_serde::Base64; use serde::{Deserialize, Serialize}; #[cfg(feature = "unstable-pre-spec")] @@ -24,18 +25,18 @@ pub struct ToDeviceKeyVerificationMacEventContent { /// A map of the key ID to the MAC of the key, using the algorithm in the verification process. /// - /// The MAC is encoded as unpadded Base64. - pub mac: BTreeMap, + /// The MAC is encoded as unpadded base64. + pub mac: BTreeMap, /// The MAC of the comma-separated, sorted, list of key IDs given in the `mac` property, - /// encoded as unpadded Base64. - pub keys: String, + /// encoded as unpadded base64. + pub keys: Base64, } impl ToDeviceKeyVerificationMacEventContent { /// Creates a new `ToDeviceKeyVerificationMacEventContent` with the given transaction ID, key ID /// to MAC map and key MAC. - pub fn new(transaction_id: String, mac: BTreeMap, keys: String) -> Self { + pub fn new(transaction_id: String, mac: BTreeMap, keys: Base64) -> Self { Self { transaction_id, mac, keys } } } @@ -50,12 +51,12 @@ impl ToDeviceKeyVerificationMacEventContent { pub struct KeyVerificationMacEventContent { /// A map of the key ID to the MAC of the key, using the algorithm in the verification process. /// - /// The MAC is encoded as unpadded Base64. - pub mac: BTreeMap, + /// The MAC is encoded as unpadded base64. + pub mac: BTreeMap, /// The MAC of the comma-separated, sorted, list of key IDs given in the `mac` property, - /// encoded as unpadded Base64. - pub keys: String, + /// encoded as unpadded base64. + pub keys: Base64, /// Information about the related event. #[serde(rename = "m.relates_to")] @@ -66,7 +67,7 @@ pub struct KeyVerificationMacEventContent { impl KeyVerificationMacEventContent { /// Creates a new `KeyVerificationMacEventContent` with the given key ID to MAC map, key MAC and /// relation. - pub fn new(mac: BTreeMap, keys: String, relates_to: Relation) -> Self { + pub fn new(mac: BTreeMap, keys: Base64, relates_to: Relation) -> Self { Self { mac, keys, relates_to } } } diff --git a/crates/ruma-events/src/key/verification/start.rs b/crates/ruma-events/src/key/verification/start.rs index 4c80c28e..07c784a4 100644 --- a/crates/ruma-events/src/key/verification/start.rs +++ b/crates/ruma-events/src/key/verification/start.rs @@ -6,6 +6,8 @@ use std::collections::BTreeMap; use ruma_events_macros::EventContent; use ruma_identifiers::DeviceId; +#[cfg(feature = "unstable-pre-spec")] +use ruma_serde::Base64; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; @@ -115,7 +117,7 @@ pub struct _CustomContent { #[serde(rename = "m.reciprocate.v1", tag = "method")] pub struct ReciprocateV1Content { /// The shared secret from the QR code, encoded using unpadded base64. - pub secret: String, + pub secret: Base64, } #[cfg(feature = "unstable-pre-spec")] @@ -123,7 +125,7 @@ impl ReciprocateV1Content { /// Create a new `ReciprocateV1Content` with the given shared secret. /// /// The shared secret needs to come from the scanned QR code, encoded using unpadded base64. - pub fn new(secret: String) -> Self { + pub fn new(secret: Base64) -> Self { Self { secret } } } @@ -205,6 +207,8 @@ mod tests { #[cfg(feature = "unstable-pre-spec")] use ruma_identifiers::event_id; use ruma_identifiers::user_id; + #[cfg(feature = "unstable-pre-spec")] + use ruma_serde::Base64; use serde_json::{ from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue, }; @@ -288,7 +292,7 @@ mod tests { #[cfg(feature = "unstable-pre-spec")] { - let secret = "This is a secret to everybody".to_owned(); + let secret = Base64::new(b"This is a secret to everybody".to_vec()); let key_verification_start_content = ToDeviceKeyVerificationStartEventContent { from_device: "123".into(), @@ -341,7 +345,7 @@ mod tests { assert_eq!(to_json_value(&key_verification_start_content).unwrap(), json_data); - let secret = "This is a secret to everybody".to_owned(); + let secret = Base64::new(b"This is a secret to everybody".to_vec()); let key_verification_start_content = KeyVerificationStartEventContent { from_device: "123".into(), @@ -468,7 +472,7 @@ mod tests { "content": { "from_device": "123", "method": "m.reciprocate.v1", - "secret": "It's a secret to everybody", + "secret": "c2VjcmV0Cg", "transaction_id": "456", }, "type": "m.key.verification.start", @@ -487,7 +491,7 @@ mod tests { } if from_device == "123" && sender == user_id!("@example:localhost") && transaction_id == "456" - && secret == "It's a secret to everybody" + && secret.encode() == "c2VjcmV0Cg" ); } } @@ -533,7 +537,7 @@ mod tests { let json = json!({ "from_device": "123", "method": "m.reciprocate.v1", - "secret": "It's a secret to everybody", + "secret": "c2VjcmV0Cg", "m.relates_to": { "rel_type": "m.reference", "event_id": id, @@ -548,7 +552,7 @@ mod tests { method: StartMethod::ReciprocateV1(ReciprocateV1Content { secret }), } if from_device == "123" && event_id == id - && secret == "It's a secret to everybody" + && secret.encode() == "c2VjcmV0Cg" ); } } diff --git a/crates/ruma-events/src/room.rs b/crates/ruma-events/src/room.rs index acbc4a6b..41466973 100644 --- a/crates/ruma-events/src/room.rs +++ b/crates/ruma-events/src/room.rs @@ -6,6 +6,7 @@ use std::collections::BTreeMap; use js_int::UInt; use ruma_identifiers::MxcUri; +use ruma_serde::Base64; use serde::{Deserialize, Serialize}; pub mod aliases; @@ -122,12 +123,12 @@ pub struct EncryptedFile { pub key: JsonWebKey, /// The 128-bit unique counter block used by AES-CTR, encoded as unpadded base64. - pub iv: String, + pub iv: Base64, /// A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64. /// /// Clients should support the SHA-256 hash, which uses the key sha256. - pub hashes: BTreeMap, + pub hashes: BTreeMap, /// Version of the encrypted attachments protocol. /// @@ -149,12 +150,12 @@ pub struct EncryptedFileInit { pub key: JsonWebKey, /// The 128-bit unique counter block used by AES-CTR, encoded as unpadded base64. - pub iv: String, + pub iv: Base64, /// A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64. /// /// Clients should support the SHA-256 hash, which uses the key sha256. - pub hashes: BTreeMap, + pub hashes: BTreeMap, /// Version of the encrypted attachments protocol. /// @@ -192,7 +193,7 @@ pub struct JsonWebKey { pub alg: String, /// The key, encoded as url-safe unpadded base64. - pub k: String, + pub k: Base64, /// Extractable. /// @@ -224,7 +225,7 @@ pub struct JsonWebKeyInit { pub alg: String, /// The key, encoded as url-safe unpadded base64. - pub k: String, + pub k: Base64, /// Extractable. /// diff --git a/crates/ruma-events/src/room/third_party_invite.rs b/crates/ruma-events/src/room/third_party_invite.rs index 715e0486..75265253 100644 --- a/crates/ruma-events/src/room/third_party_invite.rs +++ b/crates/ruma-events/src/room/third_party_invite.rs @@ -3,6 +3,7 @@ //! [`m.room.third_party_invite`]: https://spec.matrix.org/v1.1/client-server-api/#mroomthird_party_invite use ruma_events_macros::EventContent; +use ruma_serde::Base64; use serde::{Deserialize, Serialize}; /// The content of an `m.room.third_party_invite` event. @@ -30,12 +31,12 @@ pub struct RoomThirdPartyInviteEventContent { #[cfg_attr(feature = "compat", serde(default))] pub key_validity_url: String, - /// A Base64-encoded Ed25519 key with which the token must be signed. + /// A base64-encoded Ed25519 key with which the token must be signed. /// /// If you activate the `compat` feature, this field being absent in JSON will result in an /// empty string here during deserialization. - #[cfg_attr(feature = "compat", serde(default))] - pub public_key: String, + #[cfg_attr(feature = "compat", serde(default = "Base64::empty"))] + pub public_key: Base64, /// Keys with which the token may be signed. #[serde(skip_serializing_if = "Option::is_none")] @@ -45,7 +46,7 @@ pub struct RoomThirdPartyInviteEventContent { impl RoomThirdPartyInviteEventContent { /// Creates a new `RoomThirdPartyInviteEventContent` with the given display name, key validity /// url and public key. - pub fn new(display_name: String, key_validity_url: String, public_key: String) -> Self { + pub fn new(display_name: String, key_validity_url: String, public_key: Base64) -> Self { Self { display_name, key_validity_url, public_key, public_keys: None } } } @@ -61,13 +62,13 @@ pub struct PublicKey { #[serde(skip_serializing_if = "Option::is_none")] pub key_validity_url: Option, - /// A Base64-encoded Ed25519 key with which the token must be signed. - pub public_key: String, + /// A base64-encoded Ed25519 key with which the token must be signed. + pub public_key: Base64, } impl PublicKey { /// Creates a new `PublicKey` with the given base64-encoded ed25519 key. - pub fn new(public_key: String) -> Self { + pub fn new(public_key: Base64) -> Self { Self { key_validity_url: None, public_key } } } diff --git a/crates/ruma-federation-api/src/discovery.rs b/crates/ruma-federation-api/src/discovery.rs index d0e1cdd3..8ccddad4 100644 --- a/crates/ruma-federation-api/src/discovery.rs +++ b/crates/ruma-federation-api/src/discovery.rs @@ -4,6 +4,7 @@ use std::collections::BTreeMap; use ruma_common::MilliSecondsSinceUnixEpoch; use ruma_identifiers::{ServerName, ServerSigningKeyId}; +use ruma_serde::Base64; use serde::{Deserialize, Serialize}; pub mod discover_homeserver; @@ -16,13 +17,13 @@ pub mod get_server_version; #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct VerifyKey { - /// The Unpadded Base64 encoded key. - pub key: String, + /// The unpadded base64-encoded key. + pub key: Base64, } impl VerifyKey { /// Creates a new `VerifyKey` from the given key. - pub fn new(key: String) -> Self { + pub fn new(key: Base64) -> Self { Self { key } } } @@ -34,13 +35,13 @@ pub struct OldVerifyKey { /// Timestamp when this key expired. pub expired_ts: MilliSecondsSinceUnixEpoch, - /// The Unpadded Base64 encoded key. - pub key: String, + /// The unpadded base64-encoded key. + pub key: Base64, } impl OldVerifyKey { /// Creates a new `OldVerifyKey` with the given expiry time and key. - pub fn new(expired_ts: MilliSecondsSinceUnixEpoch, key: String) -> Self { + pub fn new(expired_ts: MilliSecondsSinceUnixEpoch, key: Base64) -> Self { Self { expired_ts, key } } } diff --git a/crates/ruma-federation-api/src/keys/claim_keys/v1.rs b/crates/ruma-federation-api/src/keys/claim_keys/v1.rs index 7b853108..d887dcc8 100644 --- a/crates/ruma-federation-api/src/keys/claim_keys/v1.rs +++ b/crates/ruma-federation-api/src/keys/claim_keys/v1.rs @@ -6,7 +6,7 @@ use std::collections::BTreeMap; use ruma_api::ruma_api; use ruma_common::encryption::OneTimeKey; use ruma_identifiers::{DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UserId}; -use ruma_serde::Raw; +use ruma_serde::{Base64, Raw}; use serde::{Deserialize, Serialize}; ruma_api! { @@ -56,7 +56,7 @@ pub type OneTimeKeys = #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct KeyObject { /// The key, encoded using unpadded base64. - pub key: String, + pub key: Base64, /// Signature of the key object. pub signatures: BTreeMap, BTreeMap, String>>, @@ -65,7 +65,7 @@ pub struct KeyObject { impl KeyObject { /// Creates a new `KeyObject` with the given key and signatures. pub fn new( - key: String, + key: Base64, signatures: BTreeMap, BTreeMap, String>>, ) -> Self { Self { key, signatures } diff --git a/crates/ruma-identity-service-api/src/invitation/sign_invitation_ed25519/v2.rs b/crates/ruma-identity-service-api/src/invitation/sign_invitation_ed25519/v2.rs index 04791618..2bc0e4fd 100644 --- a/crates/ruma-identity-service-api/src/invitation/sign_invitation_ed25519/v2.rs +++ b/crates/ruma-identity-service-api/src/invitation/sign_invitation_ed25519/v2.rs @@ -2,6 +2,7 @@ use ruma_api::ruma_api; use ruma_identifiers::{ServerSignatures, UserId}; +use ruma_serde::Base64; ruma_api! { metadata: { @@ -21,7 +22,7 @@ ruma_api! { pub token: &'a str, /// The private key, encoded as unpadded base64. - pub private_key: &'a str, + pub private_key: &'a Base64, } response: { @@ -41,7 +42,7 @@ ruma_api! { impl<'a> Request<'a> { /// Creates a `Request` with the given Matrix user ID, token and private_key. - pub fn new(mxid: &'a UserId, token: &'a str, private_key: &'a str) -> Self { + pub fn new(mxid: &'a UserId, token: &'a str, private_key: &'a Base64) -> Self { Self { mxid, token, private_key } } } diff --git a/crates/ruma-identity-service-api/src/keys/check_public_key_validity/v2.rs b/crates/ruma-identity-service-api/src/keys/check_public_key_validity/v2.rs index f5c21796..2d955783 100644 --- a/crates/ruma-identity-service-api/src/keys/check_public_key_validity/v2.rs +++ b/crates/ruma-identity-service-api/src/keys/check_public_key_validity/v2.rs @@ -1,6 +1,7 @@ //! [GET /_matrix/identity/v2/pubkey/isvalid](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-pubkey-isvalid) use ruma_api::ruma_api; +use ruma_serde::Base64; ruma_api! { metadata: { @@ -15,7 +16,7 @@ ruma_api! { request: { /// Base64-encoded (no padding) public key to check for validity. #[ruma_api(query)] - pub public_key: &'a str, + pub public_key: &'a Base64, } response: { @@ -26,7 +27,7 @@ ruma_api! { impl<'a> Request<'a> { /// Create a `Request` with the given base64-encoded (unpadded) public key. - pub fn new(public_key: &'a str) -> Self { + pub fn new(public_key: &'a Base64) -> Self { Self { public_key } } } diff --git a/crates/ruma-identity-service-api/src/keys/get_public_key/v2.rs b/crates/ruma-identity-service-api/src/keys/get_public_key/v2.rs index 51f0d05c..9bbe37d7 100644 --- a/crates/ruma-identity-service-api/src/keys/get_public_key/v2.rs +++ b/crates/ruma-identity-service-api/src/keys/get_public_key/v2.rs @@ -2,6 +2,7 @@ use ruma_api::ruma_api; use ruma_identifiers::ServerSigningKeyId; +use ruma_serde::Base64; ruma_api! { metadata: { @@ -20,8 +21,8 @@ ruma_api! { } response: { - /// Unpadded Base64 encoded public key. - pub public_key: String, + /// Unpadded base64-encoded public key. + pub public_key: Base64, } } @@ -34,7 +35,7 @@ impl<'a> Request<'a> { impl Response { /// Create a `Response` with the given base64-encoded (unpadded) public key. - pub fn new(public_key: String) -> Self { + pub fn new(public_key: Base64) -> Self { Self { public_key } } } diff --git a/crates/ruma-identity-service-api/src/keys/validate_ephemeral_key/v2.rs b/crates/ruma-identity-service-api/src/keys/validate_ephemeral_key/v2.rs index 3243cde8..8c1470cd 100644 --- a/crates/ruma-identity-service-api/src/keys/validate_ephemeral_key/v2.rs +++ b/crates/ruma-identity-service-api/src/keys/validate_ephemeral_key/v2.rs @@ -1,6 +1,7 @@ //! [GET /_matrix/identity/v2/pubkey/ephemeral/isvalid](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-pubkey-ephemeral-isvalid) use ruma_api::ruma_api; +use ruma_serde::Base64; ruma_api! { metadata: { @@ -15,7 +16,7 @@ ruma_api! { request: { /// The unpadded base64-encoded short-term public key to check. #[ruma_api(query)] - pub public_key: &'a str, + pub public_key: &'a Base64, } response: { @@ -26,7 +27,7 @@ ruma_api! { impl<'a> Request<'a> { /// Create a `Request` with the given base64-encoded (unpadded) short-term public key. - pub fn new(public_key: &'a str) -> Self { + pub fn new(public_key: &'a Base64) -> Self { Self { public_key } } } diff --git a/crates/ruma-serde/Cargo.toml b/crates/ruma-serde/Cargo.toml index dc1c7424..6c486530 100644 --- a/crates/ruma-serde/Cargo.toml +++ b/crates/ruma-serde/Cargo.toml @@ -13,6 +13,7 @@ version = "0.5.0" edition = "2018" [dependencies] +base64 = "0.13.0" bytes = "1.0.1" form_urlencoded = "1.0.0" itoa = "0.4.6" diff --git a/crates/ruma-serde/src/base64.rs b/crates/ruma-serde/src/base64.rs new file mode 100644 index 00000000..516b0100 --- /dev/null +++ b/crates/ruma-serde/src/base64.rs @@ -0,0 +1,72 @@ +use std::fmt; + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +/// DOCS +// generic? +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Base64> { + bytes: B, +} + +const BASE64_CONFIG: base64::Config = base64::STANDARD_NO_PAD.decode_allow_trailing_bits(true); + +impl> Base64 { + /// Create a `Base64` instance from raw bytes, to be base64-encoded in serialialization. + pub fn new(bytes: B) -> Self { + Self { bytes } + } + + /// Get the raw bytes held by this `Base64` instance. + pub fn as_bytes(&self) -> &[u8] { + self.bytes.as_ref() + } + + /// Encode the bytes contained in this `Base64` instance to unpadded base64. + pub fn encode(&self) -> String { + base64::encode_config(&self.bytes, BASE64_CONFIG) + } +} + +impl Base64 { + /// Create a `Base64` instance containing an empty `Vec`. + pub fn empty() -> Self { + Self { bytes: Vec::new() } + } + + /// Parse some base64-encoded data to create a `Base64` instance. + pub fn parse(encoded: impl AsRef<[u8]>) -> Result { + base64::decode_config(encoded, BASE64_CONFIG).map(Self::new) + } +} + +impl> fmt::Debug for Base64 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.encode().fmt(f) + } +} + +impl> fmt::Display for Base64 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.encode().fmt(f) + } +} + +impl<'de> Deserialize<'de> for Base64 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let encoded = crate::deserialize_cow_str(deserializer)?; + Self::parse(&*encoded).map_err(de::Error::custom) + } +} + +impl> Serialize for Base64 { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.encode()) + } +} diff --git a/crates/ruma-serde/src/lib.rs b/crates/ruma-serde/src/lib.rs index 4b2e5ea6..759f7f40 100644 --- a/crates/ruma-serde/src/lib.rs +++ b/crates/ruma-serde/src/lib.rs @@ -6,6 +6,7 @@ use serde_json::Value as JsonValue; +mod base64; mod buf; pub mod can_be_empty; mod canonical_json; @@ -19,19 +20,22 @@ mod strings; pub mod test; pub mod urlencoded; -pub use buf::{json_to_buf, slice_to_buf}; -pub use can_be_empty::{is_empty, CanBeEmpty}; -pub use canonical_json::{ - to_canonical_value, try_from_json_map, - value::{CanonicalJsonValue, Object as CanonicalJsonObject}, - Error as CanonicalJsonError, -}; -pub use cow::deserialize_cow_str; -pub use empty::vec_as_map_of_empty; -pub use raw::Raw; -pub use strings::{ - btreemap_int_or_string_to_int_values, empty_string_as_none, int_or_string_to_int, - none_as_empty_string, +pub use self::{ + base64::Base64, + buf::{json_to_buf, slice_to_buf}, + can_be_empty::{is_empty, CanBeEmpty}, + canonical_json::{ + to_canonical_value, try_from_json_map, + value::{CanonicalJsonValue, Object as CanonicalJsonObject}, + Error as CanonicalJsonError, + }, + cow::deserialize_cow_str, + empty::vec_as_map_of_empty, + raw::Raw, + strings::{ + btreemap_int_or_string_to_int_values, empty_string_as_none, int_or_string_to_int, + none_as_empty_string, + }, }; /// The inner type of [`JsonValue::Object`]. diff --git a/crates/ruma-signatures/src/functions.rs b/crates/ruma-signatures/src/functions.rs index 9392b3e7..c43bc058 100644 --- a/crates/ruma-signatures/src/functions.rs +++ b/crates/ruma-signatures/src/functions.rs @@ -7,9 +7,9 @@ use std::{ mem, }; -use base64::{decode_config, encode_config, Config, STANDARD_NO_PAD, URL_SAFE_NO_PAD}; +use base64::{encode_config, STANDARD_NO_PAD, URL_SAFE_NO_PAD}; use ruma_identifiers::{EventId, RoomVersionId, ServerName, UserId}; -use ruma_serde::{CanonicalJsonObject, CanonicalJsonValue}; +use ruma_serde::{Base64, CanonicalJsonObject, CanonicalJsonValue}; use serde_json::{from_str as from_json_str, to_string as to_json_string}; use sha2::{digest::Digest, Sha256}; @@ -230,7 +230,9 @@ pub fn canonical_json(object: &CanonicalJsonObject) -> Result { /// ```rust /// use std::collections::BTreeMap; /// -/// const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI"; +/// use ruma_serde::Base64; +/// +/// const PUBLIC_KEY: &[u8] = b"XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI"; /// /// // Deserialize the signed JSON. /// let object = serde_json::from_str( @@ -245,7 +247,7 @@ pub fn canonical_json(object: &CanonicalJsonObject) -> Result { /// /// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify. /// let mut public_key_set = BTreeMap::new(); -/// public_key_set.insert("ed25519:1".into(), PUBLIC_KEY.to_owned()); +/// public_key_set.insert("ed25519:1".into(), Base64::parse(PUBLIC_KEY.to_owned()).unwrap()); /// let mut public_key_map = BTreeMap::new(); /// public_key_map.insert("domain".into(), public_key_set); /// @@ -289,20 +291,15 @@ pub fn verify_json( ) })?; - let verify = |config: Config| { - let signature_bytes = decode_config(signature, config) - .map_err(|e| ParseError::base64("signature", signature, e))?; + let signature = Base64::parse(signature) + .map_err(|e| ParseError::base64("signature", signature, e))?; - let public_key_bytes = decode_config(&public_key, config) - .map_err(|e| ParseError::base64("public key", public_key, e))?; - - verify_json_with(&Ed25519Verifier, &public_key_bytes, &signature_bytes, object) - }; - - #[cfg(feature = "compat")] - also_try_forgiving_base64(STANDARD_NO_PAD, verify)?; - #[cfg(not(feature = "compat"))] - verify(STANDARD_NO_PAD)?; + verify_json_with( + &Ed25519Verifier, + public_key.as_bytes(), + signature.as_bytes(), + object, + )?; } } @@ -335,7 +332,7 @@ where /// Creates a *content hash* for an event. /// -/// Returns the hash as a Base64-encoded string, using the standard character set, without padding. +/// Returns the hash as a base64-encoded string, using the standard character set, without padding. /// /// The content hash of an event covers the complete event including the unredacted contents. It is /// used during federation and is described in the Matrix server-server specification. @@ -347,7 +344,7 @@ where /// # Errors /// /// Returns an error if the event is too large. -pub fn content_hash(object: &CanonicalJsonObject) -> Result { +pub fn content_hash(object: &CanonicalJsonObject) -> Result, Error> { let json = canonical_json_with_fields_to_remove(object, CONTENT_HASH_FIELDS_TO_REMOVE)?; if json.len() > MAX_PDU_BYTES { return Err(Error::PduSize); @@ -355,12 +352,12 @@ pub fn content_hash(object: &CanonicalJsonObject) -> Result { let hash = Sha256::digest(json.as_bytes()); - Ok(encode_config(&hash, STANDARD_NO_PAD)) + Ok(Base64::new(hash.into())) } /// Creates a *reference hash* for an event. /// -/// Returns the hash as a Base64-encoded string, using the standard character set, without padding. +/// Returns the hash as a base64-encoded string, using the standard character set, without padding. /// /// The reference hash of an event covers the essential fields of an event, including content /// hashes. It is used to generate event identifiers and is described in the Matrix server-server @@ -507,7 +504,7 @@ where match hashes_value { CanonicalJsonValue::Object(hashes) => { - hashes.insert("sha256".into(), CanonicalJsonValue::String(hash)) + hashes.insert("sha256".into(), CanonicalJsonValue::String(hash.encode())) } _ => return Err(JsonError::not_of_type("hashes", JsonType::Object)), }; @@ -546,10 +543,10 @@ where /// ```rust /// # use std::collections::BTreeMap; /// # use ruma_identifiers::RoomVersionId; -/// # use ruma_signatures::verify_event; -/// # use ruma_signatures::Verified; +/// # use ruma_serde::Base64; +/// # use ruma_signatures::{verify_event, Verified}; /// # -/// const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI"; +/// const PUBLIC_KEY: &[u8] = b"XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI"; /// /// // Deserialize an event from JSON. /// let object = serde_json::from_str( @@ -579,14 +576,14 @@ where /// /// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify. /// let mut public_key_set = BTreeMap::new(); -/// public_key_set.insert("ed25519:1".into(), PUBLIC_KEY.to_owned()); +/// public_key_set.insert("ed25519:1".into(), Base64::parse(PUBLIC_KEY.to_owned()).unwrap()); /// let mut public_key_map = BTreeMap::new(); /// public_key_map.insert("domain".into(), public_key_set); /// /// // Verify at least one signature for each entity in `public_key_map`. /// let verification_result = verify_event(&public_key_map, &object, &RoomVersionId::V6); /// assert!(verification_result.is_ok()); -/// assert!(matches!(verification_result.unwrap(), Verified::All)); +/// assert_eq!(verification_result.unwrap(), Verified::All); /// ``` pub fn verify_event( public_key_map: &PublicKeyMap, @@ -659,34 +656,31 @@ pub fn verify_event( let public_key = signature_and_pubkey.public_key; - let verify = |config: Config| { - let signature_bytes = decode_config(signature, config) - .map_err(|e| ParseError::base64("signature", signature, e))?; + let signature = + Base64::parse(signature).map_err(|e| ParseError::base64("signature", signature, e))?; - let public_key_bytes = decode_config(&public_key, config) - .map_err(|e| ParseError::base64("public key", public_key, e))?; - - verify_json_with(&Ed25519Verifier, &public_key_bytes, &signature_bytes, &canonical_json) - }; - - #[cfg(feature = "compat")] - also_try_forgiving_base64(STANDARD_NO_PAD, verify)?; - #[cfg(not(feature = "compat"))] - verify(STANDARD_NO_PAD)?; + verify_json_with( + &Ed25519Verifier, + public_key.as_bytes(), + signature.as_bytes(), + &canonical_json, + )?; } let calculated_hash = content_hash(object)?; - if *hash == calculated_hash { - Ok(Verified::All) - } else { - Ok(Verified::Signatures) + if let Ok(hash) = Base64::parse(hash) { + if hash.as_bytes() == calculated_hash.as_bytes() { + return Ok(Verified::All); + } } + + Ok(Verified::Signatures) } struct SignatureAndPubkey<'a> { signature: &'a CanonicalJsonValue, - public_key: &'a String, + public_key: &'a Base64, } /// Internal implementation detail of the canonical JSON algorithm. @@ -821,41 +815,6 @@ fn is_third_party_invite(object: &CanonicalJsonObject) -> Result { } } -#[cfg(feature = "compat")] -// see https://github.com/ruma/ruma/issues/591 -// synapse allows this, so we must allow it too. -// shouldn't lose data, but when it does, it'll make verification fail instead. -pub(crate) fn also_try_forgiving_base64( - config: Config, - twice: impl Fn(Config) -> Result, -) -> Result -where - E: std::fmt::Display, -{ - use tracing::{debug, warn}; - - let first_try = match twice(config) { - Ok(t) => return Ok(t), - Err(e) => e, - }; - - let adjusted = config.decode_allow_trailing_bits(true); - - match twice(adjusted) { - Ok(t) => { - warn!( - "Usage of base64 config only worked after allowing trailing bits, first error: {}", - first_try - ); - Ok(t) - } - Err(e) => { - debug!("Second error when trying to allow trailing bits: {}", e); - Err(first_try) - } - } -} - #[cfg(test)] mod tests { use std::{ @@ -863,9 +822,8 @@ mod tests { convert::{TryFrom, TryInto}, }; - use base64::{encode_config, STANDARD_NO_PAD}; use ruma_identifiers::{RoomVersionId, ServerSigningKeyId, SigningKeyAlgorithm}; - use ruma_serde::CanonicalJsonValue; + use ruma_serde::{Base64, CanonicalJsonValue}; use serde_json::json; use super::canonical_json; @@ -941,20 +899,6 @@ mod tests { assert!(matches!(verification, Verified::Signatures)); } - #[cfg(feature = "compat")] - #[test] - fn fallback_invalid_base64() { - use base64::{decode_config, Config}; - - const SLIGHTLY_MALFORMED_BASE64: &str = "3UmJnEIzUr2xWyaUnJg5fXwRybwG5FVC6GqMHverEUn0ztuIsvVxX89JXX2pvdTsOBbLQx+4TVL02l4Cp5wPCm"; - - let verify = |config: Config| decode_config(SLIGHTLY_MALFORMED_BASE64, config); - - assert!(verify(STANDARD_NO_PAD).is_err()); - - assert!(super::also_try_forgiving_base64(STANDARD_NO_PAD, verify).is_ok()); - } - #[test] fn verify_event_check_signatures_for_both_sender_and_event_id() { let key_pair_sender = generate_key_pair(); @@ -1062,8 +1006,7 @@ mod tests { let mut public_key_map = PublicKeyMap::new(); let mut sender_key_map = PublicKeySet::new(); let newly_generated_key_pair = generate_key_pair(); - let encoded_public_key = - encode_config(newly_generated_key_pair.public_key(), STANDARD_NO_PAD); + let encoded_public_key = Base64::new(newly_generated_key_pair.public_key().to_owned()); let version = ServerSigningKeyId::from_parts( SigningKeyAlgorithm::Ed25519, key_pair_sender.version().try_into().unwrap(), @@ -1092,7 +1035,7 @@ mod tests { fn add_key_to_map(public_key_map: &mut PublicKeyMap, name: &str, pair: &Ed25519KeyPair) { let mut sender_key_map = PublicKeySet::new(); - let encoded_public_key = encode_config(pair.public_key(), STANDARD_NO_PAD); + let encoded_public_key = Base64::new(pair.public_key().to_owned()); let version = ServerSigningKeyId::from_parts( SigningKeyAlgorithm::Ed25519, pair.version().try_into().unwrap(), diff --git a/crates/ruma-signatures/src/keys.rs b/crates/ruma-signatures/src/keys.rs index 8a6ce692..87084771 100644 --- a/crates/ruma-signatures/src/keys.rs +++ b/crates/ruma-signatures/src/keys.rs @@ -11,6 +11,7 @@ use pkcs8::{ der::{Decodable, Encodable}, AlgorithmIdentifier, ObjectIdentifier, PrivateKeyInfo, }; +use ruma_serde::Base64; use crate::{signatures::Signature, Algorithm, Error, ParseError}; @@ -184,8 +185,8 @@ pub type PublicKeyMap = BTreeMap; /// A set of public keys for a single homeserver. /// -/// This is represented as a map from key ID to Base64-encoded signature. -pub type PublicKeySet = BTreeMap; +/// This is represented as a map from key ID to base64-encoded signature. +pub type PublicKeySet = BTreeMap; #[cfg(test)] mod tests { diff --git a/crates/ruma-signatures/src/lib.rs b/crates/ruma-signatures/src/lib.rs index 174be8e5..d725a6b4 100644 --- a/crates/ruma-signatures/src/lib.rs +++ b/crates/ruma-signatures/src/lib.rs @@ -17,7 +17,7 @@ //! are also required to contain hashes of their content, which are similarly stored within the //! hashed JSON object under a `hashes` key. //! -//! In JSON representations, both signatures and hashes appear as Base64-encoded strings, using the +//! In JSON representations, both signatures and hashes appear as base64-encoded strings, using the //! standard character set, without padding. //! //! # Signing and hashing @@ -104,9 +104,10 @@ fn split_id(id: &str) -> Result<(Algorithm, String), SplitError> { mod tests { use std::collections::BTreeMap; - use base64::{decode_config, encode_config, STANDARD_NO_PAD}; + use base64::{decode_config, STANDARD_NO_PAD}; use pkcs8::{der::Decodable, PrivateKeyInfo}; use ruma_identifiers::RoomVersionId; + use ruma_serde::Base64; use serde_json::{from_str as from_json_str, to_string as to_json_string}; use super::{ @@ -119,13 +120,13 @@ mod tests { "; /// Convenience method for getting the public key as a string - fn public_key_string() -> String { - encode_config( - &PrivateKeyInfo::from_der(&decode_config(PKCS8, STANDARD_NO_PAD).unwrap()) + fn public_key_string() -> Base64 { + Base64::new( + PrivateKeyInfo::from_der(&decode_config(PKCS8, STANDARD_NO_PAD).unwrap()) .unwrap() .public_key - .unwrap(), - STANDARD_NO_PAD, + .unwrap() + .to_owned(), ) } diff --git a/crates/ruma-signatures/src/signatures.rs b/crates/ruma-signatures/src/signatures.rs index de8cd1ea..32a1ed72 100644 --- a/crates/ruma-signatures/src/signatures.rs +++ b/crates/ruma-signatures/src/signatures.rs @@ -57,7 +57,7 @@ impl Signature { self.signature.as_slice() } - /// A Base64 encoding of the signature. + /// A base64 encoding of the signature. /// /// Uses the standard character set with no padding. pub fn base64(&self) -> String { diff --git a/crates/ruma-state-res/src/event_auth.rs b/crates/ruma-state-res/src/event_auth.rs index 8af3160c..9511d53d 100644 --- a/crates/ruma-state-res/src/event_auth.rs +++ b/crates/ruma-state-res/src/event_auth.rs @@ -12,7 +12,7 @@ use ruma_events::{ EventType, }; use ruma_identifiers::{RoomVersionId, UserId}; -use ruma_serde::Raw; +use ruma_serde::{Base64, Raw}; use serde::{de::IgnoredAny, Deserialize}; use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue}; use tracing::{debug, error, info, warn}; @@ -925,15 +925,21 @@ fn verify_third_party_invite( Err(_) => return false, }; + let decoded_invite_token = match Base64::parse(&tp_id.signed.token) { + Ok(tok) => tok, + // FIXME: Log a warning? + Err(_) => return false, + }; + // A list of public keys in the public_keys field for key in tpid_ev.public_keys.unwrap_or_default() { - if key.public_key == tp_id.signed.token { + if key.public_key == decoded_invite_token { return true; } } // A single public key in the public_key field - tpid_ev.public_key == tp_id.signed.token + tpid_ev.public_key == decoded_invite_token } #[cfg(test)]