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