diff --git a/src/accounts.rs b/src/accounts.rs index 425139d71b..637684b206 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -372,7 +372,7 @@ impl Accounts { /// Sets notification token for Apple Push Notification service. pub async fn set_push_device_token(&self, token: &str) -> Result<()> { - self.push_subscriber.set_device_token(token).await; + self.push_subscriber.set_device_token(token).await?; Ok(()) } } diff --git a/src/push.rs b/src/push.rs index fbdf7ff5ad..5d5d3a3a6c 100644 --- a/src/push.rs +++ b/src/push.rs @@ -1,10 +1,13 @@ use std::sync::atomic::Ordering; use std::sync::Arc; -use anyhow::Result; +use anyhow::{Context as _, Result}; +use pgp::crypto::sym::SymmetricKeyAlgorithm; +use rand::thread_rng; use tokio::sync::RwLock; use crate::context::Context; +use crate::key::DcKey; /// Manages subscription to Apple Push Notification services. /// @@ -24,20 +27,56 @@ pub struct PushSubscriber { inner: Arc>, } +/// The key was generated with +/// `rsop generate-key --profile rfc9580 | rsop extract-cert`. +const NOTIFIERS_PUBLIC_KEY: &str = "-----BEGIN PGP PUBLIC KEY BLOCK----- + +xioGZz5XrhsAAAAgArETOonYhhUGVk0fw0t8b4MbvoFJFHVKwD352OiEizPCsAYf +GwoAAABBBQJnPleuAhsDAh4JCAsJCAcKDQwLBRUKCQgLAhYCIiEGCeNFWH7kM/8A +XS8A2HouJVoSe2JbPKDxomei7cnPVg4AAAAAVtIgQI/MVJd3abu2ITUxpFMsTro8 +3sc5WC8dXB5Et5GNROsCQzfdmpmCb5RcVmjSirVnOIlxBvGg00ajCs76PKK0FUIk +xIQfFcNpKVnsqEcXja//Hq37Q5YQIHBswDTy4tUIzioGZz5XrhkAAAAgwgebLt0s +ZFniutxUyR+0wtgUjdPyqUiI5Tu9Qp4R/1fClQYYGwgAAAAsBQJnPleuAhsMIiEG +CeNFWH7kM/8AXS8A2HouJVoSe2JbPKDxomei7cnPVg4AAAAKCRAJ40VYfuQz/+nV +EJGsfYwfQw1ErVtGRNLHsrX/dmwpFlKS1fbCWXjxL8yPSfwvPPSKh0sMTbZ9X7NC +KvGiT8+VtKuphR9lga2BoUWnrQCXViPkJyPztVQSg0kO +=9WnP +-----END PGP PUBLIC KEY BLOCK-----"; + impl PushSubscriber { /// Creates new push notification subscriber. pub(crate) fn new() -> Self { Default::default() } - /// Sets device token for Apple Push Notification service. - pub(crate) async fn set_device_token(&self, token: &str) { - self.inner.write().await.device_token = Some(token.to_string()); + /// Sets device token for Apple Push Notification service + /// or Firebase Cloud Messaging. + /// + /// The token is encrypted with OpenPGP. + pub(crate) async fn set_device_token(&self, token: &str) -> Result<()> { + let public_key = pgp::composed::SignedPublicKey::from_asc(&NOTIFIERS_PUBLIC_KEY)?.0; + let encryption_subkey = public_key + .public_subkeys + .first() + .context("No encryption subkey found")?; + let literal_message = pgp::composed::Message::new_literal("", token); + let mut rng = thread_rng(); + let encrypted_message = literal_message.encrypt_to_keys_seipdv1( + &mut rng, + SymmetricKeyAlgorithm::AES128, + &[&encryption_subkey], + )?; + let encoded_message = encrypted_message.to_armored_string(Default::default())?; + self.inner.write().await.device_token = Some(encoded_message); + Ok(()) } /// Retrieves device token. /// + /// The token is encrypted with OpenPGP. + /// /// Token may be not available if application is not running on Apple platform, + /// does not have Google Play services, /// failed to register for remote notifications or is in the process of registering. /// /// IMAP loop should periodically check if device token is available @@ -121,3 +160,22 @@ impl Context { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_set_device_key() { + let push_subscriber = PushSubscriber::new(); + assert_eq!(push_subscriber.device_token().await, None); + + push_subscriber + .set_device_token("some-token") + .await + .expect("Failed to set device token"); + let device_token = push_subscriber.device_token().await.unwrap(); + assert!(device_token.starts_with("-----BEGIN PGP MESSAGE-----")); + assert!(device_token.ends_with("-----END PGP MESSAGE-----\n")); + } +}