ddnet_account_client/
sign.rs

1use anyhow::anyhow;
2use ddnet_accounts_shared::client::{
3    account_data::AccountDataForClient, machine_id::machine_uid, sign::prepare_sign_request,
4};
5use thiserror::Error;
6use x509_parser::oid_registry::asn1_rs::FromDer;
7
8use crate::{
9    errors::{FsLikeError, HttpLikeError},
10    interface::Io,
11    safe_interface::{IoSafe, SafeIo},
12};
13
14/// The result of a [`sign`] request.
15#[derive(Error, Debug)]
16pub enum SignResult {
17    /// Session was invalid, must login again.
18    #[error("The session was not valid anymore.")]
19    SessionWasInvalid,
20    /// A file system like error occurred.
21    /// This usually means the user was not yet logged in.
22    #[error("{0}")]
23    FsLikeError(FsLikeError),
24    /// A http like error occurred.
25    #[error("{err}")]
26    HttpLikeError {
27        /// The actual error message
28        err: HttpLikeError,
29        /// The account data that the client could use as fallback
30        account_data: AccountDataForClient,
31    },
32    /// Errors that are not handled explicitly.
33    #[error("Signing failed: {err}")]
34    Other {
35        /// The actual error message
36        err: anyhow::Error,
37        /// The account data that the client could use as fallback
38        account_data: AccountDataForClient,
39    },
40}
41
42impl From<FsLikeError> for SignResult {
43    fn from(value: FsLikeError) -> Self {
44        Self::FsLikeError(value)
45    }
46}
47
48/// The sign data contains the signed certificate
49/// by the account server, which the client can send
50/// to a game server to proof account relationship.
51#[derive(Debug, Clone)]
52pub struct SignData {
53    /// Certificate that was signed by the account server to proof that
54    /// the client owns the account.
55    /// The cert is in der format.
56    pub certificate_der: Vec<u8>,
57    /// The account data for this session.
58    pub session_key_pair: AccountDataForClient,
59}
60
61/// Sign an existing session on the account server.
62///
63/// The account server will respond with a certificate,
64/// that can be used to verify account ownership on game servers.  
65/// __IMPORTANT__: Never share this certificate with anyone.
66/// Best is to not even save it to disk, re-sign instead.
67///
68/// # Errors
69///
70/// If an error occurs this usually means that the session is not valid anymore.
71pub async fn sign(io: &dyn Io) -> anyhow::Result<SignData, SignResult> {
72    sign_impl(io.into()).await
73}
74
75async fn sign_impl(io: IoSafe<'_>) -> anyhow::Result<SignData, SignResult> {
76    // read session's key-pair
77    let key_pair = io.read_serialized_session_key_pair().await?;
78
79    let hashed_hw_id = machine_uid().map_err(|err| SignResult::Other {
80        account_data: key_pair.clone(),
81        err,
82    })?;
83
84    // do the sign request using the above private key
85    let msg = prepare_sign_request(hashed_hw_id, &key_pair.private_key, key_pair.public_key);
86    let sign_res = io
87        .request_sign(msg)
88        .await
89        .map_err(|err| SignResult::HttpLikeError {
90            account_data: key_pair.clone(),
91            err,
92        })?
93        .map_err(|err| SignResult::Other {
94            err: err.into(),
95            account_data: key_pair.clone(),
96        })?;
97    let certificate = {
98        x509_parser::certificate::X509Certificate::from_der(&sign_res.cert_der)
99            .is_ok()
100            .then_some(sign_res.cert_der)
101    };
102
103    certificate.map_or_else(
104        || {
105            Err(SignResult::Other {
106                err: anyhow!("the certificate is not in a valid der format"),
107                account_data: key_pair.clone(),
108            })
109        },
110        |certificate| {
111            Ok(SignData {
112                certificate_der: certificate,
113                session_key_pair: key_pair.clone(),
114            })
115        },
116    )
117}