magic_wormhole/
core.rs

1#![allow(deprecated)]
2
3pub(super) mod key;
4pub mod rendezvous;
5mod server_messages;
6#[cfg(test)]
7mod test;
8
9/// Module for wormhole code generation and completion.
10pub mod wordlist;
11
12use serde_derive::{Deserialize, Serialize};
13use std::{borrow::Cow, str::FromStr};
14use thiserror::Error;
15
16use crate::Wordlist;
17
18use self::{rendezvous::*, server_messages::EncryptedMessage};
19
20use crypto_secretbox as secretbox;
21
22/// An error occurred in the wormhole connection
23#[derive(Debug, thiserror::Error)]
24#[non_exhaustive]
25pub enum WormholeError {
26    /// Corrupt message received from peer. Some deserialization went wrong, we probably got some garbage
27    #[error("Corrupt message received from peer")]
28    ProtocolJson(
29        #[from]
30        #[source]
31        serde_json::Error,
32    ),
33    /// Error with the rendezvous server connection. Some deserialization went wrong, we probably got some garbage
34    #[error("Error with the rendezvous server connection")]
35    ServerError(
36        #[from]
37        #[source]
38        rendezvous::RendezvousError,
39    ),
40    /// A generic string message for "something went wrong", i.e.
41    /// the server sent some bullshit message order
42    #[error("Protocol error: {}", _0)]
43    Protocol(Box<str>),
44    /// Key confirmation failed. If you didn't mistype the code,
45    /// this is a sign of an attacker guessing passwords. Please try
46    /// again some time later.
47    #[error(
48        "Key confirmation failed. If you didn't mistype the code, \
49        this is a sign of an attacker guessing passwords. Please try \
50        again some time later."
51    )]
52    PakeFailed,
53    /// Cannot decrypt a received message
54    #[error("Cannot decrypt a received message")]
55    Crypto,
56    /// Nameplate is unclaimed
57    #[error("Nameplate is unclaimed: {}", _0)]
58    UnclaimedNameplate(Nameplate),
59    /// The provided code is invalid
60    #[error("The provided code is invalid: {_0}")]
61    CodeInvalid(#[from] ParseCodeError),
62}
63
64impl WormholeError {
65    /** Should we tell the server that we are "errory" or "scared"? */
66    pub fn is_scared(&self) -> bool {
67        matches!(self, Self::PakeFailed)
68    }
69}
70
71impl From<std::convert::Infallible> for WormholeError {
72    fn from(_: std::convert::Infallible) -> Self {
73        unreachable!()
74    }
75}
76
77/**
78 * The result of the client-server handshake
79 */
80#[derive(Clone, Debug, PartialEq, Eq)]
81#[deprecated(
82    since = "0.7.0",
83    note = "part of the response of `Wormhole::connect_without_code(...)` and `Wormhole::connect_with_code(...) please use 'MailboxConnection::create(...)`/`MailboxConnection::connect(..)` and `Wormhole::connect(mailbox_connection)' instead"
84)]
85pub struct WormholeWelcome {
86    /** A welcome message from the server (think of "message of the day"). Should be displayed to the user if present. */
87    pub welcome: Option<String>,
88    /// The wormhole code used in the exchange
89    pub code: Code,
90}
91
92/**
93 * Establishing Wormhole connection
94 *
95 * You can send and receive arbitrary messages in form of byte slices over it, using [`Wormhole::send`] and [`Wormhole::receive`].
96 * Everything else (including encryption) will be handled for you.
97 *
98 * To create a wormhole, use the mailbox connection created via [`MailboxConnection::create`] or [`MailboxConnection::connect`] with the [`Wormhole::connect`] method.
99 * Typically, the sender side connects without a code (which will create one), and the receiver side has one (the user entered it, who got it from the sender).
100 *
101 * # Clean shutdown
102 *
103 * TODO
104 */
105/* TODO
106 * Maybe a better way to handle application level protocols is to create a trait for them and then
107 * to paramterize over them.
108 */
109/// A `MailboxConnection` contains a `RendezvousServer` which is connected to the mailbox
110pub struct MailboxConnection<V: serde::Serialize + Send + Sync + 'static> {
111    /// A copy of `AppConfig`,
112    config: AppConfig<V>,
113    /// The `RendezvousServer` with an open mailbox connection
114    server: RendezvousServer,
115    /// The welcome message received from the mailbox server
116    welcome: Option<String>,
117    /// The mailbox id of the created mailbox
118    mailbox: Mailbox,
119    /// The Code which is required to connect to the mailbox.
120    code: Code,
121}
122
123impl<V: serde::Serialize + Send + Sync + 'static> MailboxConnection<V> {
124    /// Create a connection to a mailbox which is configured with a `Code` starting with the nameplate and by a given number of wordlist based random words.
125    ///
126    /// # Arguments
127    ///
128    /// * `config`: Application configuration
129    /// * `code_length`: number of words used for the password. The words are taken from the default wordlist.
130    ///
131    /// # Examples
132    ///
133    /// ```no_run
134    /// # fn main() -> eyre::Result<()> { async_std::task::block_on(async {
135    /// use magic_wormhole::{transfer::APP_CONFIG, AppConfig, MailboxConnection};
136    /// let config = APP_CONFIG;
137    /// let mailbox_connection = MailboxConnection::create(config, 2).await?;
138    /// # Ok(()) })}
139    /// ```
140    pub async fn create(config: AppConfig<V>, code_length: usize) -> Result<Self, WormholeError> {
141        Self::create_with_validated_password(
142            config,
143            Wordlist::default_wordlist(code_length).choose_words(),
144        )
145        .await
146    }
147
148    /// Create a connection to a mailbox which is configured with a `Code` containing the nameplate and the given password.
149    ///
150    /// # Arguments
151    ///
152    /// * `config`: Application configuration
153    /// * `password`: Free text password which will be appended to the nameplate number to form the `Code`
154    ///
155    /// # Examples
156    ///
157    /// ```no_run
158    /// # #[cfg(feature = "entropy")]
159    /// # {
160    /// # fn main() -> eyre::Result<()> { async_std::task::block_on(async {
161    /// use magic_wormhole::{transfer::APP_CONFIG, MailboxConnection};
162    /// let config = APP_CONFIG;
163    /// let mailbox_connection =
164    ///     MailboxConnection::create_with_password(config, "secret".parse()?).await?;
165    /// # Ok(()) })}
166    /// # }
167    /// ```
168    ///
169    /// TODO: Replace this with create_with_validated_password
170    pub async fn create_with_password(
171        config: AppConfig<V>,
172        #[cfg(not(feature = "entropy"))] password: &str,
173        #[cfg(feature = "entropy")] password: Password,
174    ) -> Result<Self, WormholeError> {
175        #[cfg(not(feature = "entropy"))]
176        let password = password.parse().map_err(ParseCodeError::from)?;
177
178        Self::create_with_validated_password(config, password).await
179    }
180
181    /// Create a connection to a mailbox which is configured with a `Code` containing the nameplate and the given password.
182    ///
183    /// # Arguments
184    ///
185    /// * `config`: Application configuration
186    /// * `password`: Free text password which will be appended to the nameplate number to form the `Code`
187    async fn create_with_validated_password(
188        config: AppConfig<V>,
189        password: Password,
190    ) -> Result<Self, WormholeError> {
191        let (mut server, welcome) =
192            RendezvousServer::connect(&config.id, &config.rendezvous_url).await?;
193        let (nameplate, mailbox) = server.allocate_claim_open().await?;
194        let code = Code::from_components(nameplate, password);
195
196        Ok(MailboxConnection {
197            config,
198            server,
199            mailbox,
200            code,
201            welcome,
202        })
203    }
204
205    /// Create a connection to a mailbox defined by a `Code` which contains the `Nameplate` and the password to authorize the access.
206    ///
207    /// # Arguments
208    ///
209    /// * `config`: Application configuration
210    /// * `code`: The `Code` required to authorize to connect to an existing mailbox.
211    /// * `allocate`:
212    ///   - `true`: Allocates a `Nameplate` if it does not exist.
213    ///   - `false`: The call fails with a `WormholeError::UnclaimedNameplate` when the `Nameplate` does not exist.
214    ///
215    /// # Examples
216    ///
217    /// ```no_run
218    /// # fn main() -> eyre::Result<()> { async_std::task::block_on(async {
219    /// use magic_wormhole::{transfer::APP_CONFIG, Code, MailboxConnection, Nameplate};
220    /// let config = APP_CONFIG;
221    /// let code = "5-password".parse()?;
222    /// let mailbox_connection = MailboxConnection::connect(config, code, false).await?;
223    /// # Ok(()) })}
224    /// ```
225    pub async fn connect(
226        config: AppConfig<V>,
227        code: Code,
228        allocate: bool,
229    ) -> Result<Self, WormholeError> {
230        let (mut server, welcome) =
231            RendezvousServer::connect(&config.id, &config.rendezvous_url).await?;
232        let nameplate = code.nameplate();
233
234        // Ensure the code has enough entropy without the nameplate [#193](https://github.com/magic-wormhole/magic-wormhole.rs/issues/193)
235
236        if !allocate {
237            let nameplates = server.list_nameplates().await?;
238            if !nameplates.contains(&nameplate) {
239                server.shutdown(Mood::Errory).await?;
240                return Err(WormholeError::UnclaimedNameplate(nameplate));
241            }
242        }
243        let mailbox = server.claim_open(nameplate).await?;
244
245        Ok(MailboxConnection {
246            config,
247            server,
248            mailbox,
249            code,
250            welcome,
251        })
252    }
253
254    /// Shut down the connection to the mailbox
255    ///
256    /// # Arguments
257    ///
258    /// * `mood`: `Mood` should give a hint of the reason of the shutdown
259    ///
260    /// # Examples
261    ///
262    /// ```
263    /// # fn main() -> eyre::Result<()> { use magic_wormhole::WormholeError;
264    /// # #[cfg(feature = "entropy")]
265    /// return async_std::task::block_on(async {
266    /// use magic_wormhole::{transfer::APP_CONFIG, MailboxConnection, Mood};
267    /// let config = APP_CONFIG;
268    /// let mailbox_connection = MailboxConnection::create_with_password(config, "secret-code-password".parse()?)
269    ///     .await?;
270    /// mailbox_connection.shutdown(Mood::Happy).await?;
271    /// # Ok(())});
272    /// # #[cfg(not(feature = "entropy"))]
273    /// # return Ok(());
274    /// # }
275    /// ```
276    pub async fn shutdown(self, mood: Mood) -> Result<(), WormholeError> {
277        self.server
278            .shutdown(mood)
279            .await
280            .map_err(WormholeError::ServerError)
281    }
282
283    /// The welcome message received from the mailbox server
284    pub fn welcome(&self) -> Option<&str> {
285        self.welcome.as_deref()
286    }
287
288    /// The mailbox id of the created mailbox
289    pub fn mailbox(&self) -> &Mailbox {
290        &self.mailbox
291    }
292
293    /// The Code that was used to connect to the mailbox.
294    pub fn code(&self) -> &Code {
295        &self.code
296    }
297}
298
299/// A wormhole is an open connection to a peer via the rendezvous server.
300///
301/// This establishes the client-client part of the connection setup.
302#[derive(Debug)]
303pub struct Wormhole {
304    #[allow(deprecated)]
305    server: RendezvousServer,
306    phase: u64,
307    key: key::Key<key::WormholeKey>,
308    appid: AppID,
309    /// The cryptographic verifier code for the connection
310    #[deprecated(since = "0.7.0", note = "Use the verifier() method")]
311    pub verifier: Box<secretbox::Key>,
312    /// Our app version
313    #[deprecated(since = "0.7.0", note = "Use the our_version() method")]
314    pub our_version: Box<dyn std::any::Any + Send + Sync>,
315    /// The app version of the peer
316    #[deprecated(since = "0.7.0", note = "Use the peer_version() method")]
317    pub peer_version: serde_json::Value,
318}
319
320impl Wormhole {
321    /**
322     * Generate a code and connect to the rendezvous server.
323     *
324     * # Returns
325     *
326     * A tuple with a [`WormholeWelcome`] and a [`std::future::Future`] that will
327     * do the rest of the client-client handshake and yield the [`Wormhole`] object
328     * on success.
329     */
330    #[deprecated(
331        since = "0.7.0",
332        note = "please use 'MailboxConnection::create(..) and Wormhole::connect(mailbox_connection)' instead"
333    )]
334    #[allow(deprecated)]
335    pub async fn connect_without_code(
336        config: AppConfig<impl serde::Serialize + Send + Sync + 'static>,
337        code_length: usize,
338    ) -> Result<
339        (
340            WormholeWelcome,
341            impl std::future::Future<Output = Result<Self, WormholeError>>,
342        ),
343        WormholeError,
344    > {
345        let mailbox_connection = MailboxConnection::create(config, code_length).await?;
346        Ok((
347            WormholeWelcome {
348                welcome: mailbox_connection.welcome.clone(),
349                code: mailbox_connection.code.clone(),
350            },
351            Self::connect(mailbox_connection),
352        ))
353    }
354
355    /**
356     * Connect to a peer with a code.
357     */
358    #[deprecated(
359        since = "0.7.0",
360        note = "please use 'MailboxConnection::connect(..) and Wormhole::connect(mailbox_connection)' instead"
361    )]
362    #[allow(deprecated)]
363    pub async fn connect_with_code(
364        config: AppConfig<impl serde::Serialize + Send + Sync + 'static>,
365        code: Code,
366    ) -> Result<(WormholeWelcome, Self), WormholeError> {
367        let mailbox_connection = MailboxConnection::connect(config, code.clone(), true).await?;
368        Ok((
369            WormholeWelcome {
370                welcome: mailbox_connection.welcome.clone(),
371                code,
372            },
373            Self::connect(mailbox_connection).await?,
374        ))
375    }
376
377    /// Set up a Wormhole which is the client-client part of the connection setup
378    ///
379    /// The MailboxConnection already contains a rendezvous server with an opened mailbox.
380    pub async fn connect(
381        mailbox_connection: MailboxConnection<impl serde::Serialize + Send + Sync + 'static>,
382    ) -> Result<Self, WormholeError> {
383        let MailboxConnection {
384            config,
385            mut server,
386            mailbox: _mailbox,
387            code,
388            welcome: _welcome,
389        } = mailbox_connection;
390
391        /* Send PAKE */
392        let (pake_state, pake_msg_ser) = key::make_pake(code.as_ref(), &config.id);
393        server.send_peer_message(Phase::PAKE, pake_msg_ser).await?;
394
395        /* Receive PAKE */
396        let peer_pake = key::extract_pake_msg(&server.next_peer_message_some().await?.body)?;
397        let key = pake_state
398            .finish(&peer_pake)
399            .map_err(|_| WormholeError::PakeFailed)
400            .map(|key| *secretbox::Key::from_slice(&key))?;
401
402        /* Send versions message */
403        let mut versions = key::VersionsMessage::new();
404        versions.set_app_versions(serde_json::to_value(&config.app_version).unwrap());
405        let (version_phase, version_msg) = key::build_version_msg(server.side(), &key, &versions);
406        server.send_peer_message(version_phase, version_msg).await?;
407        let peer_version = server.next_peer_message_some().await?;
408
409        /* Handle received message */
410        let versions: key::VersionsMessage = peer_version
411            .decrypt(&key)
412            .ok_or(WormholeError::PakeFailed)
413            .and_then(|plaintext| {
414                serde_json::from_slice(&plaintext).map_err(WormholeError::ProtocolJson)
415            })?;
416
417        let peer_version = versions.app_versions;
418
419        if server.needs_nameplate_release() {
420            server.release_nameplate().await?;
421        }
422
423        tracing::info!("Found peer on the rendezvous server.");
424
425        /* We are now fully initialized! Up and running! :tada: */
426        #[allow(deprecated)]
427        Ok(Self {
428            server,
429            appid: config.id,
430            phase: 0,
431            key: key::Key::new(key.into()),
432            verifier: Box::new(key::derive_verifier(&key)),
433            our_version: Box::new(config.app_version),
434            peer_version,
435        })
436    }
437
438    /** Send an encrypted message to peer */
439    pub async fn send(&mut self, plaintext: Vec<u8>) -> Result<(), WormholeError> {
440        let phase_string = Phase::numeric(self.phase);
441        self.phase += 1;
442        let data_key = key::derive_phase_key(self.server.side(), &self.key, &phase_string);
443        let (_nonce, encrypted) = key::encrypt_data(&data_key, &plaintext);
444        self.server
445            .send_peer_message(phase_string, encrypted)
446            .await?;
447        Ok(())
448    }
449
450    /**
451     * Serialize and send an encrypted message to peer
452     *
453     * This will serialize the message as `json` string, which is most commonly
454     * used by upper layer protocols. The serialization may not fail
455     *
456     * ## Panics
457     *
458     * If the serialization fails
459     */
460    pub async fn send_json<T: serde::Serialize>(
461        &mut self,
462        message: &T,
463    ) -> Result<(), WormholeError> {
464        self.send(serde_json::to_vec(message).unwrap()).await
465    }
466
467    /** Receive an encrypted message from peer */
468    pub async fn receive(&mut self) -> Result<Vec<u8>, WormholeError> {
469        loop {
470            let peer_message = match self.server.next_peer_message().await? {
471                Some(peer_message) => peer_message,
472                None => continue,
473            };
474            if peer_message.phase.to_num().is_none() {
475                // TODO: log and ignore, for future expansion
476                todo!("log and ignore, for future expansion");
477            }
478
479            // TODO maybe reorder incoming messages by phase numeral?
480            let decrypted_message = peer_message
481                .decrypt(&self.key)
482                .ok_or(WormholeError::Crypto)?;
483
484            // Send to client
485            return Ok(decrypted_message);
486        }
487    }
488
489    /**
490     * Receive an encrypted message from peer
491     *
492     * This will deserialize the message as `json` string, which is most commonly
493     * used by upper layer protocols. We distinguish between the different layers
494     * on which a serialization error happened, hence the double `Result`.
495     */
496    pub async fn receive_json<T>(&mut self) -> Result<Result<T, serde_json::Error>, WormholeError>
497    where
498        T: for<'a> serde::Deserialize<'a>,
499    {
500        self.receive().await.map(|data: Vec<u8>| {
501            serde_json::from_slice(&data).inspect_err(|_| {
502                tracing::error!(
503                    "Received invalid data from peer: '{}'",
504                    String::from_utf8_lossy(&data)
505                );
506            })
507        })
508    }
509
510    /// Close the wormhole
511    pub async fn close(self) -> Result<(), WormholeError> {
512        tracing::debug!("Closing Wormhole…");
513        self.server.shutdown(Mood::Happy).await.map_err(Into::into)
514    }
515
516    /**
517     * The `AppID` this wormhole is bound to.
518     * This determines the upper-layer protocol. Only wormholes with the same value can talk to each other.
519     */
520    pub fn appid(&self) -> &AppID {
521        &self.appid
522    }
523
524    /**
525     * The symmetric encryption key used by this connection.
526     * Can be used to derive sub-keys for different purposes.
527     */
528    pub fn key(&self) -> &key::Key<key::WormholeKey> {
529        &self.key
530    }
531
532    /**
533     * If you're paranoid, let both sides check that they calculated the same verifier.
534     *
535     * PAKE hardens a standard key exchange with a password ("password authenticated") in order
536     * to mitigate potential man in the middle attacks that would otherwise be possible. Since
537     * the passwords usually are not of hight entropy, there is a low-probability possible of
538     * an attacker guessing the password correctly, enabling them to MitM the connection.
539     *
540     * Not only is that probability low, but they also have only one try per connection and a failed
541     * attempts will be noticed by both sides. Nevertheless, comparing the verifier mitigates that
542     * attack vector.
543     */
544    pub fn verifier(&self) -> &secretbox::Key {
545        #[allow(deprecated)]
546        &self.verifier
547    }
548
549    /**
550     * Our "app version" information that we sent. See the [`peer_version`](Self::peer_version()) for more information.
551     */
552    pub fn our_version(&self) -> &(dyn std::any::Any + Send + Sync) {
553        #[allow(deprecated)]
554        &*self.our_version
555    }
556
557    /**
558     * Protocol version information from the other side.
559     * This is bound by the [`AppID`]'s protocol and thus shall be handled on a higher level
560     * (e.g. by the file transfer API).
561     */
562    pub fn peer_version(&self) -> &serde_json::Value {
563        #[allow(deprecated)]
564        &self.peer_version
565    }
566}
567
568/// The close command accepts an optional "mood" string: this allows clients to tell the server
569/// (in general terms) about their experiences with the wormhole interaction. The server records
570/// the mood in its "usage" record, so the server operator can get a sense of how many connections
571/// are succeeding and failing. The moods currently recognized by the Mailbox server are:
572#[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize, derive_more::Display)]
573pub enum Mood {
574    /// The PAKE key-establishment worked, and the client saw at least one valid encrypted message from its peer
575    #[serde(rename = "happy")]
576    Happy,
577    /// The client gave up without hearing anything from its peer
578    #[serde(rename = "lonely")]
579    Lonely,
580    /// The client encountered some other error: protocol problem or internal error
581    #[serde(rename = "errory")]
582    Errory,
583    /// The client saw an invalid encrypted message from its peer,
584    /// indicating that either the wormhole code was typed in wrong,
585    /// or an attacker tried (and failed) to guess the code
586    #[serde(rename = "scary")]
587    Scared,
588    /// Clients are not welcome on the server right now
589    #[serde(rename = "unwelcome")]
590    Unwelcome,
591}
592
593/**
594 * Wormhole configuration corresponding to an uppler layer protocol
595 *
596 * There are multiple different protocols built on top of the core
597 * Wormhole protocol. They are identified by a unique URI-like ID string
598 * (`AppID`), an URL to find the rendezvous server (might be shared among
599 * multiple protocols), and client implementations also have a "version"
600 * data to do protocol negotiation.
601 *
602 * See [`crate::transfer::APP_CONFIG`].
603 */
604#[derive(PartialEq, Eq, Clone, Debug)]
605pub struct AppConfig<V> {
606    /// The ID of the used application
607    pub id: AppID,
608    /// The URL of the rendezvous server
609    pub rendezvous_url: Cow<'static, str>,
610    /// The client application version
611    pub app_version: V,
612}
613
614impl<V> AppConfig<V> {
615    /// Set the app id
616    pub fn id(mut self, id: AppID) -> Self {
617        self.id = id;
618        self
619    }
620
621    /// Set the rendezvous URL
622    pub fn rendezvous_url(mut self, rendezvous_url: Cow<'static, str>) -> Self {
623        self.rendezvous_url = rendezvous_url;
624        self
625    }
626}
627
628impl<V: serde::Serialize> AppConfig<V> {
629    /// Set the app version
630    pub fn app_version(mut self, app_version: V) -> Self {
631        self.app_version = app_version;
632        self
633    }
634}
635
636/// Newtype wrapper for application IDs
637///
638/// The application ID is a string that scopes all commands
639/// to that name, effectively separating different protocols
640/// on the same rendezvous server.
641#[derive(
642    PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
643)]
644#[deref(forward)]
645pub struct AppID(
646    #[deref]
647    #[deprecated(since = "0.7.0", note = "use the AsRef<str> implementation")]
648    pub Cow<'static, str>,
649);
650
651impl AppID {
652    /// Create a new app ID from an ID string
653    pub fn new(id: impl Into<Cow<'static, str>>) -> Self {
654        AppID(id.into())
655    }
656}
657
658impl From<String> for AppID {
659    fn from(s: String) -> Self {
660        Self::new(s)
661    }
662}
663
664impl AsRef<str> for AppID {
665    fn as_ref(&self) -> &str {
666        &self.0
667    }
668}
669
670// MySide is used for the String that we send in all our outbound messages
671#[derive(
672    PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
673)]
674#[serde(transparent)]
675#[display("MySide({})", "&*_0")]
676#[deprecated(
677    since = "0.7.0",
678    note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
679)]
680pub struct MySide(EitherSide);
681
682impl MySide {
683    pub fn generate() -> MySide {
684        use rand::{rngs::OsRng, RngCore};
685
686        let mut bytes: [u8; 5] = [0; 5];
687        OsRng.fill_bytes(&mut bytes);
688
689        MySide(EitherSide(hex::encode(bytes)))
690    }
691
692    // It's a minor type system feature that converting an arbitrary string into MySide is hard.
693    // This prevents it from getting swapped around with TheirSide.
694    #[cfg(test)]
695    pub fn unchecked_from_string(s: String) -> MySide {
696        MySide(EitherSide(s))
697    }
698}
699
700// TheirSide is used for the string that arrives inside inbound messages
701#[derive(
702    PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
703)]
704#[serde(transparent)]
705#[display("TheirSide({})", "&*_0")]
706#[deprecated(
707    since = "0.7.0",
708    note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
709)]
710pub struct TheirSide(EitherSide);
711
712impl<S: Into<String>> From<S> for TheirSide {
713    fn from(s: S) -> TheirSide {
714        TheirSide(EitherSide(s.into()))
715    }
716}
717
718#[derive(
719    PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
720)]
721#[serde(transparent)]
722#[deref(forward)]
723#[display("{}", "&*_0")]
724#[deprecated(
725    since = "0.7.0",
726    note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
727)]
728pub struct EitherSide(pub String);
729
730impl<S: Into<String>> From<S> for EitherSide {
731    fn from(s: S) -> EitherSide {
732        EitherSide(s.into())
733    }
734}
735
736#[derive(PartialEq, Eq, Clone, Debug, Hash, Deserialize, Serialize, derive_more::Display)]
737#[serde(transparent)]
738#[deprecated(
739    since = "0.7.0",
740    note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
741)]
742pub struct Phase(Cow<'static, str>);
743
744impl Phase {
745    pub const VERSION: Self = Phase(Cow::Borrowed("version"));
746    pub const PAKE: Self = Phase(Cow::Borrowed("pake"));
747
748    pub fn numeric(phase: u64) -> Self {
749        Phase(phase.to_string().into())
750    }
751
752    #[allow(dead_code)]
753    pub fn is_version(&self) -> bool {
754        self == &Self::VERSION
755    }
756
757    #[allow(dead_code)]
758    pub fn is_pake(&self) -> bool {
759        self == &Self::PAKE
760    }
761
762    pub fn to_num(&self) -> Option<u64> {
763        self.0.parse().ok()
764    }
765}
766
767impl AsRef<str> for Phase {
768    fn as_ref(&self) -> &str {
769        &self.0
770    }
771}
772
773#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display)]
774#[serde(transparent)]
775#[deprecated(
776    since = "0.7.0",
777    note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
778)]
779pub struct Mailbox(pub String);
780
781/// An error occurred when parsing a nameplate: Nameplate is not a number.
782#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Copy, derive_more::Display, Error)]
783#[display("Nameplate is not a number. Nameplates must be a number >= 1.")]
784#[non_exhaustive]
785pub struct ParseNameplateError {}
786
787/// Wormhole codes look like 4-purple-sausages, consisting of a number followed by some random words.
788/// This number is called a "Nameplate".
789#[derive(
790    PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
791)]
792#[serde(transparent)]
793#[deref(forward)]
794#[display("{}", _0)]
795#[cfg(not(feature = "entropy"))]
796pub struct Nameplate(
797    #[deprecated(since = "0.7.0", note = "use the AsRef<str> implementation")] pub String,
798);
799
800/// Wormhole codes look like 4-purple-sausages, consisting of a number followed by some random words.
801/// This number is called a "Nameplate".
802#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display)]
803#[serde(transparent)]
804#[display("{}", _0)]
805#[cfg(feature = "entropy")]
806pub struct Nameplate(String);
807
808#[allow(deprecated)]
809impl Nameplate {
810    /// Create a new nameplate from a string.
811    ///
812    /// Nameplate will be [required to be numbers](https://github.com/magic-wormhole/magic-wormhole-mailbox-server/issues/45) soon.
813    #[deprecated(
814        since = "0.7.2",
815        note = "Nameplates will be required to be numbers soon. Use the [std::str::FromStr] implementation"
816    )]
817    #[cfg(not(feature = "entropy"))]
818    pub fn new(n: impl Into<String>) -> Self {
819        let nameplate = n.into();
820        if let Err(err) = Nameplate::from_str(&nameplate) {
821            tracing::error!("{err}");
822        }
823
824        #[allow(unsafe_code)]
825        unsafe {
826            Self::new_unchecked(&nameplate)
827        }
828    }
829
830    /// Create a new nameplate from a string.
831    ///
832    /// Safety: Nameplate will be [required to be numbers](https://github.com/magic-wormhole/magic-wormhole-mailbox-server/issues/45) soon.
833    #[allow(unsafe_code)]
834    #[doc(hidden)]
835    pub unsafe fn new_unchecked(n: &str) -> Self {
836        Nameplate(n.into())
837    }
838}
839
840impl FromStr for Nameplate {
841    type Err = ParseNameplateError;
842
843    fn from_str(s: &str) -> Result<Self, Self::Err> {
844        if !s.chars().all(|c| c.is_ascii_digit()) || u128::from_str(s) == Ok(0) {
845            Err(ParseNameplateError {})
846        } else {
847            Ok(Self(s.to_string()))
848        }
849    }
850}
851
852/// Deprecated: Use the [`std::fmt::Display`] implementation
853#[allow(deprecated)]
854impl From<Nameplate> for String {
855    fn from(value: Nameplate) -> Self {
856        value.0
857    }
858}
859
860/// Deprecated: Use the [`std::str::FromStr`] implementation
861///
862/// Does not check if the nameplate is a number. This may be incompatible.
863#[allow(deprecated)]
864#[cfg(not(feature = "entropy"))]
865impl From<String> for Nameplate {
866    fn from(value: String) -> Self {
867        tracing::debug!(
868            "Implementation of From<String> for Nameplate is deprecated. Use the FromStr implementation instead"
869        );
870
871        if let Err(err) = Nameplate::from_str(&value) {
872            tracing::error!("{err} This will be a hard error in the future.");
873        }
874
875        Self(value)
876    }
877}
878
879/// Deprecated: Use the [`std::fmt::Display`] implementation
880#[allow(deprecated)]
881impl AsRef<str> for Nameplate {
882    fn as_ref(&self) -> &str {
883        &self.0
884    }
885}
886
887/// This is a compromise. Generally we want to be wordlist-agnostic here. But
888/// we can't ignore that the PGP wordlist is the most common wordlist in use.
889///
890/// - The shortest word in the PGP wordlist is 4 characters long. The longest
891///   word is 9 characters. This means we can't limit to more than 9 bytes here,
892///   'solo-orca' is 9 bytes, and we want to allow 2-word codes.
893/// - A 9 character random password is very strong. If it is only comprised of
894///   uniformly distributed lowercase ASCII characters, we have an entropy of
895///   26^9 >= 40 bits. This is much more than the default 16 bits we get from two
896///   words from the PGP wordlist.
897/// - An emoji contains at least 2 bytes of data. So two emoji are actually
898///   about the same security as two words from the PGP wordlist.
899///
900/// We ultimately can't protect people from making bad choices, as entropy is a
901/// very difficult thing. What we can do instead is offer guidance, by printing
902/// warnings in case of rather short passwords, and making people choose for
903/// themselves whether their privacy is worth it to them choosing longer codes.
904#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Copy, derive_more::Display, Error)]
905#[non_exhaustive]
906pub enum ParsePasswordError {
907    /// Password too short
908    #[display("Password too short. It is only {value} bytes, but must be at least {required}")]
909    TooShort {
910        /// The calculated value
911        value: usize,
912        /// The value that is required
913        required: usize,
914    },
915    /// Password does not have enough entropy
916    #[display(
917        "Password too weak. It can be guessed with an average of {value} tries, but must be at least {required}"
918    )]
919    LittleEntropy {
920        /// The calculated value
921        value: u64,
922        /// The value that is required
923        required: u64,
924    },
925}
926
927/// Wormhole codes look like 4-purple-sausages, consisting of a number followed by some random words.
928/// This number is called a "Nameplate", the rest is called the `Password`
929#[derive(Clone, Debug, Serialize, derive_more::Display)]
930#[serde(transparent)]
931#[display("{password}")]
932pub struct Password {
933    password: String,
934    #[serde(skip)]
935    #[cfg(feature = "entropy")]
936    entropy: zxcvbn::Entropy,
937}
938
939impl PartialEq for Password {
940    fn eq(&self, other: &Self) -> bool {
941        self.password == other.password
942    }
943}
944
945impl Eq for Password {}
946
947impl Password {
948    /// Create a new password from a string. Does not check the entropy of the password.
949    ///
950    /// You should use [`Password::from_str`] / [`String::parse`] instead.
951    ///
952    /// Safety: Does not check the entropy of the password, or if one exists at all. This can be a security risk.
953    #[allow(unsafe_code)]
954    #[doc(hidden)]
955    pub unsafe fn new_unchecked(n: impl Into<String>) -> Self {
956        let password = n.into();
957        #[cfg(feature = "entropy")]
958        let entropy = Self::calculate_entropy(&password);
959
960        Password {
961            password,
962            #[cfg(feature = "entropy")]
963            entropy,
964        }
965    }
966
967    #[cfg(feature = "entropy")]
968    fn calculate_entropy(password: &str) -> zxcvbn::Entropy {
969        static PGP_WORDLIST: std::sync::OnceLock<Vec<&str>> = std::sync::OnceLock::new();
970        let words = PGP_WORDLIST.get_or_init(|| {
971            // TODO: We leak the str: https://github.com/shssoichiro/zxcvbn-rs/issues/87
972            Wordlist::default_wordlist(2)
973                .into_words()
974                .map(|s| &*s.leak())
975                .collect::<Vec<_>>()
976        });
977
978        zxcvbn::zxcvbn(password, &words[..])
979    }
980}
981
982impl From<Password> for String {
983    fn from(value: Password) -> Self {
984        value.password
985    }
986}
987
988impl AsRef<str> for Password {
989    fn as_ref(&self) -> &str {
990        &self.password
991    }
992}
993
994impl FromStr for Password {
995    type Err = ParsePasswordError;
996
997    fn from_str(password: &str) -> Result<Self, Self::Err> {
998        let password = password.to_string();
999
1000        if password.len() < 4 {
1001            Err(ParsePasswordError::TooShort {
1002                value: password.len(),
1003                required: 4,
1004            })
1005        } else {
1006            #[cfg(feature = "entropy")]
1007            return {
1008                let entropy = Self::calculate_entropy(&password);
1009                if entropy.guesses() < 2_u64.pow(16) {
1010                    return Err(ParsePasswordError::LittleEntropy {
1011                        value: entropy.guesses(),
1012                        required: 2_u64.pow(16),
1013                    });
1014                }
1015                Ok(Self { password, entropy })
1016            };
1017
1018            #[cfg(not(feature = "entropy"))]
1019            Ok(Self { password })
1020        }
1021    }
1022}
1023
1024/// An error occurred parsing the string as a valid wormhole mailbox code
1025#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Copy, derive_more::Display, Error)]
1026#[non_exhaustive]
1027pub enum ParseCodeError {
1028    /// The code is empty
1029    #[display("The code is empty")]
1030    Empty,
1031    /// A code must contain at least one '-' to separate nameplate from password
1032    #[display("A code must contain at least one '-' to separate nameplate from password")]
1033    SeparatorMissing,
1034    /// An error occurred when parsing the nameplate
1035    #[display("{_0}")]
1036    Nameplate(#[from] ParseNameplateError),
1037    /// An error occurred when parsing the code
1038    #[display("{_0}")]
1039    Password(#[from] ParsePasswordError),
1040}
1041
1042/** A wormhole code à la 15-foo-bar
1043 *
1044 * The part until the first dash is called the "nameplate" and is purely numeric.
1045 * The rest is the password and may be arbitrary, although dash-joining words from
1046 * a wordlist is a common convention.
1047 */
1048#[derive(PartialEq, Eq, Clone, Debug, derive_more::Display, derive_more::Deref)]
1049#[display("{}", _0)]
1050#[cfg(not(feature = "entropy"))]
1051pub struct Code(
1052    #[deprecated(since = "0.7.0", note = "use the std::fmt::Display implementation")] pub String,
1053);
1054
1055/** A wormhole code à la 15-foo-bar
1056 *
1057 * The part until the first dash is called the "nameplate" and is purely numeric.
1058 * The rest is the password and may be arbitrary, although dash-joining words from
1059 * a wordlist is a common convention.
1060 */
1061#[derive(PartialEq, Eq, Clone, Debug, derive_more::Display)]
1062#[display("{}", _0)]
1063#[cfg(feature = "entropy")]
1064pub struct Code(String);
1065
1066#[allow(deprecated)]
1067impl Code {
1068    /// Create a new code, comprised of a [`Nameplate`] and a password.
1069    ///
1070    /// Safety: Does not check the entropy of the password, or if one exists at all. This can be a security risk.
1071    #[deprecated(
1072        since = "0.7.2",
1073        note = "Use [`from_components`] or the [std::str::FromStr] implementation"
1074    )]
1075    #[cfg(not(feature = "entropy"))]
1076    pub fn new(nameplate: &Nameplate, password: &str) -> Self {
1077        if let Err(err) = Password::from_str(password) {
1078            tracing::error!("{err}");
1079        }
1080
1081        #[allow(unsafe_code)]
1082        unsafe {
1083            Self::from_components(nameplate.clone(), Password::new_unchecked(password))
1084        }
1085    }
1086
1087    /// Create a new code, comprised of a [`Nameplate`] and a [`Password`].
1088    pub fn from_components(nameplate: Nameplate, password: Password) -> Self {
1089        Self(format!("{nameplate}-{password}"))
1090    }
1091
1092    /// Split the code into nameplate and password
1093    #[deprecated(since = "0.7.2", note = "Use [Self::nameplate] and [Self::password]")]
1094    pub fn split(&self) -> (Nameplate, String) {
1095        let mut iter = self.0.splitn(2, '-');
1096        #[allow(unsafe_code)]
1097        let nameplate = unsafe { Nameplate::new_unchecked(iter.next().unwrap()) };
1098        let password = iter.next().unwrap();
1099        (nameplate, password.to_string())
1100    }
1101
1102    /// Retrieve only the nameplate
1103    pub fn nameplate(&self) -> Nameplate {
1104        // Safety: We checked the validity of the nameplate before
1105        #[allow(unsafe_code)]
1106        unsafe {
1107            Nameplate::new_unchecked(self.0.split('-').next().unwrap())
1108        }
1109    }
1110
1111    /// Retrieve only the password
1112    pub fn password(&self) -> Password {
1113        // Safety: We checked the validity of the password before
1114        #[allow(unsafe_code)]
1115        unsafe {
1116            Password::new_unchecked(self.0.splitn(2, '-').last().unwrap())
1117        }
1118    }
1119}
1120
1121/// Deprecated: Use the [`std::fmt::Display`] implementation
1122#[allow(deprecated)]
1123impl From<Code> for String {
1124    fn from(value: Code) -> Self {
1125        value.0
1126    }
1127}
1128
1129/// Deprecated: Use the [`std::str::FromStr`] implementation
1130///
1131/// Safety: Does not check the entropy of the password, or if one exists at all. This can be a security risk.
1132#[cfg(not(feature = "entropy"))]
1133impl From<String> for Code {
1134    fn from(value: String) -> Self {
1135        tracing::debug!(
1136            "Implementation of From<String> for Code is deprecated. Use the FromStr implementation instead"
1137        );
1138
1139        if let Err(err) = Code::from_str(&value) {
1140            tracing::error!("{err} This will be a hard error in the future.");
1141        }
1142
1143        Self(value)
1144    }
1145}
1146
1147impl FromStr for Code {
1148    type Err = ParseCodeError;
1149
1150    fn from_str(s: &str) -> Result<Self, Self::Err> {
1151        match s.split_once('-') {
1152            Some((n, p)) => {
1153                let password: Password = p.parse()?;
1154                let nameplate: Nameplate = n.parse()?;
1155
1156                Ok(Self(format!("{}-{}", nameplate, password)))
1157            },
1158            None if s.is_empty() => Err(ParseCodeError::Empty),
1159            None => Err(ParseCodeError::SeparatorMissing),
1160        }
1161    }
1162}
1163
1164/// Deprecated: Use the [`std::fmt::Display`] implementation
1165#[allow(deprecated)]
1166impl AsRef<str> for Code {
1167    fn as_ref(&self) -> &str {
1168        &self.0
1169    }
1170}