Introduce a Base64 type and use it where applicable

This commit is contained in:
Jonas Platte 2021-10-01 18:30:07 +02:00
parent 1bdeebbd00
commit 4c859c5aeb
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
22 changed files with 247 additions and 203 deletions

View File

@ -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<Box<UserId>, BTreeMap<Box<DeviceKeyId>, String>>,
@ -109,13 +109,13 @@ impl From<KeyBackupDataInit> 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<SessionDataInit> for SessionData {

View File

@ -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<Box<UserId>, BTreeMap<Box<DeviceKeyId>,
#[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 }
}
}

View File

@ -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<SasV1ContentInit> 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

View File

@ -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 }
}
}

View File

@ -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<String, String>,
/// The MAC is encoded as unpadded base64.
pub mac: BTreeMap<String, Base64>,
/// 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<String, String>, keys: String) -> Self {
pub fn new(transaction_id: String, mac: BTreeMap<String, Base64>, 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<String, String>,
/// The MAC is encoded as unpadded base64.
pub mac: BTreeMap<String, Base64>,
/// 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<String, String>, keys: String, relates_to: Relation) -> Self {
pub fn new(mac: BTreeMap<String, Base64>, keys: Base64, relates_to: Relation) -> Self {
Self { mac, keys, relates_to }
}
}

View File

@ -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"
);
}
}

View File

@ -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<String, String>,
pub hashes: BTreeMap<String, Base64>,
/// 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<String, String>,
pub hashes: BTreeMap<String, Base64>,
/// 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.
///

View File

@ -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<String>,
/// 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 }
}
}

View File

@ -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 }
}
}

View File

@ -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<Box<UserId>, BTreeMap<Box<DeviceKeyId>, 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<Box<UserId>, BTreeMap<Box<DeviceKeyId>, String>>,
) -> Self {
Self { key, signatures }

View File

@ -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 }
}
}

View File

@ -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 }
}
}

View File

@ -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 }
}
}

View File

@ -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 }
}
}

View File

@ -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"

View File

@ -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<B = Vec<u8>> {
bytes: B,
}
const BASE64_CONFIG: base64::Config = base64::STANDARD_NO_PAD.decode_allow_trailing_bits(true);
impl<B: AsRef<[u8]>> Base64<B> {
/// 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<u8>`.
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<Self, base64::DecodeError> {
base64::decode_config(encoded, BASE64_CONFIG).map(Self::new)
}
}
impl<B: AsRef<[u8]>> fmt::Debug for Base64<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.encode().fmt(f)
}
}
impl<B: AsRef<[u8]>> fmt::Display for Base64<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.encode().fmt(f)
}
}
impl<'de> Deserialize<'de> for Base64 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let encoded = crate::deserialize_cow_str(deserializer)?;
Self::parse(&*encoded).map_err(de::Error::custom)
}
}
impl<B: AsRef<[u8]>> Serialize for Base64<B> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.encode())
}
}

View File

@ -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::{
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,
};
pub use cow::deserialize_cow_str;
pub use empty::vec_as_map_of_empty;
pub use raw::Raw;
pub use strings::{
},
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`].

View File

@ -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<String, Error> {
/// ```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<String, Error> {
///
/// // 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)
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<String, Error> {
pub fn content_hash(object: &CanonicalJsonObject) -> Result<Base64<[u8; 32]>, 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<String, Error> {
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<bool, Error> {
}
}
#[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<T, E>(
config: Config,
twice: impl Fn(Config) -> Result<T, E>,
) -> Result<T, E>
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(),

View File

@ -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<String, PublicKeySet>;
/// 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<String, String>;
/// This is represented as a map from key ID to base64-encoded signature.
pub type PublicKeySet = BTreeMap<String, Base64>;
#[cfg(test)]
mod tests {

View File

@ -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(),
)
}

View File

@ -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 {

View File

@ -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)]