core_crypto/e2e_identity/
id.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use super::{Error, Result};
use crate::prelude::ClientId;
use base64::Engine;

#[cfg(test)]
const DOMAIN: &str = "wire.com";
const COLON: u8 = 58;

/// This format: 'bd4c7053-1c5a-4020-9559-cd7bf7961954:4959bc6ab12f2846@wire.com'
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Into, derive_more::Deref)]
pub struct WireQualifiedClientId(ClientId);

#[cfg(test)]
impl WireQualifiedClientId {
    pub fn get_user_id(&self) -> String {
        let mut split = self.0.split(|b| b == &COLON);
        let user_id = split.next().unwrap();
        uuid::Uuid::try_parse_ascii(user_id).unwrap().to_string()
    }

    pub fn generate() -> Self {
        let user_id = uuid::Uuid::new_v4().to_string();
        let device_id = rand::random::<u64>();
        let client_id = format!("{user_id}:{device_id:x}@{DOMAIN}");
        Self(client_id.into_bytes().into())
    }
}

/// e.g. from 'vUxwUxxaQCCVWc1795YZVA:4959bc6ab12f2846@wire.com'
impl<'a> TryFrom<&'a [u8]> for WireQualifiedClientId {
    type Error = Error;

    fn try_from(bytes: &'a [u8]) -> Result<Self> {
        const COLON: u8 = 58;
        let mut split = bytes.split(|b| b == &COLON);
        let user_id = split.next().ok_or(Error::InvalidClientId)?;

        let user_id = base64::prelude::BASE64_URL_SAFE_NO_PAD
            .decode(user_id)
            .map_err(|_| Error::InvalidClientId)?;

        let user_id = uuid::Uuid::from_slice(&user_id).map_err(|_| Error::InvalidClientId)?;
        let mut buf = [0; uuid::fmt::Hyphenated::LENGTH];
        let user_id = user_id.hyphenated().encode_lower(&mut buf);

        let rest = split.next().ok_or(Error::InvalidClientId)?;
        if split.next().is_some() {
            return Err(Error::InvalidClientId);
        }

        let client_id = [user_id.as_bytes(), &[COLON], rest].concat();
        Ok(Self(client_id.into()))
    }
}

impl std::str::FromStr for WireQualifiedClientId {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        s.as_bytes().try_into()
    }
}

impl TryFrom<WireQualifiedClientId> for String {
    type Error = Error;

    fn try_from(id: WireQualifiedClientId) -> Result<Self> {
        String::from_utf8(id.to_vec()).map_err(|_| Error::InvalidClientId)
    }
}

/// This format: 'vUxwUxxaQCCVWc1795YZVA:4959bc6ab12f2846@wire.com'
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Into, derive_more::Deref)]
pub struct QualifiedE2eiClientId(ClientId);

#[cfg(test)]
impl QualifiedE2eiClientId {
    pub fn generate() -> Self {
        Self::generate_from_user_id(&uuid::Uuid::new_v4())
    }

    pub fn generate_with_domain(domain: &str) -> Self {
        Self::generate_from_user_id_and_domain(&uuid::Uuid::new_v4(), domain)
    }

    pub fn generate_from_user_id(user_id: &uuid::Uuid) -> Self {
        Self::generate_from_user_id_and_domain(user_id, DOMAIN)
    }

    pub fn generate_from_user_id_and_domain(user_id: &uuid::Uuid, domain: &str) -> Self {
        use base64::Engine as _;

        let user_id = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(user_id.as_bytes());

        let device_id = rand::random::<u64>();
        let client_id = format!("{user_id}:{device_id:x}@{domain}");
        Self(client_id.into_bytes().into())
    }

    pub fn from_str_unchecked(s: &str) -> Self {
        Self(s.as_bytes().into())
    }
}

/// e.g. from 'bd4c7053-1c5a-4020-9559-cd7bf7961954:4959bc6ab12f2846@wire.com'
impl<'a> TryFrom<&'a [u8]> for QualifiedE2eiClientId {
    type Error = Error;

    fn try_from(bytes: &'a [u8]) -> Result<Self> {
        let mut split = bytes.split(|b| b == &COLON);
        let user_id = split.next().ok_or(Error::InvalidClientId)?;

        let user_id = std::str::from_utf8(user_id)
            .map_err(|_| Error::InvalidClientId)?
            .parse::<uuid::Uuid>()
            .map_err(|_| Error::InvalidClientId)?;

        let user_id = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(user_id.as_bytes());

        let rest = split.next().ok_or(Error::InvalidClientId)?;
        if split.next().is_some() {
            return Err(Error::InvalidClientId);
        }

        let client_id = [user_id.as_bytes(), &[COLON], rest].concat();
        Ok(Self(client_id.into()))
    }
}

#[cfg(test)]
impl std::str::FromStr for QualifiedE2eiClientId {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        s.as_bytes().try_into()
    }
}

impl TryFrom<QualifiedE2eiClientId> for String {
    type Error = Error;

    fn try_from(id: QualifiedE2eiClientId) -> Result<Self> {
        String::from_utf8(id.to_vec()).map_err(|_| Error::InvalidClientId)
    }
}