ssi_jws/compact/
str.rs

1use base64::Engine;
2use core::fmt;
3use std::{ops::Deref, str::FromStr};
4
5use crate::{
6    utils::is_url_safe_base64_char, DecodeError, DecodedJws, Header, InvalidJws, JwsSlice,
7};
8
9/// Borrowed UTF-8 encoded JWS.
10///
11/// This is an unsized type borrowing the JWS and meant to be referenced as
12/// `&JwsStr`, just like `&str`.
13/// Use [`JwsString`] if you need to own the JWS.
14///
15/// This type is similar to the [`Jws`](crate::Jws) type.
16/// However contrarily to `Jws`, there is no guarantee that the JWS is URL-safe.
17///
18/// Use [`Jws`](crate::Jws) if you expect URL-safe JWSs.
19#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[repr(transparent)]
21pub struct JwsStr(JwsSlice);
22
23impl JwsStr {
24    pub fn new<T: ?Sized + AsRef<[u8]>>(data: &T) -> Result<&Self, InvalidJws<&T>> {
25        let bytes = data.as_ref();
26        match std::str::from_utf8(bytes) {
27            Ok(_) => {
28                let _ = JwsSlice::new(bytes).map_err(|_| InvalidJws(data))?;
29                Ok(unsafe { Self::new_unchecked(bytes) })
30            }
31            Err(_) => Err(InvalidJws(data)),
32        }
33    }
34
35    pub const fn validate(bytes: &[u8]) -> bool {
36        Self::validate_range(bytes, 0, bytes.len())
37    }
38
39    pub const fn validate_range(bytes: &[u8], mut i: usize, end: usize) -> bool {
40        let mut j = if end > bytes.len() { bytes.len() } else { end };
41
42        // Header.
43        loop {
44            if i >= j {
45                // Missing `.`
46                return false;
47            }
48
49            if bytes[i] == b'.' {
50                break;
51            }
52
53            if !is_url_safe_base64_char(bytes[i]) {
54                return false;
55            }
56
57            i += 1
58        }
59
60        // Signature.
61        if i >= j {
62            return false;
63        }
64        j -= 1;
65        loop {
66            if i >= j {
67                // Missing `.`
68                return false;
69            }
70
71            if bytes[j] == b'.' {
72                break;
73            }
74
75            if !is_url_safe_base64_char(bytes[j]) {
76                return false;
77            }
78
79            j -= 1
80        }
81
82        // Payload.
83        i += 1;
84        let payload_bytes = unsafe { std::slice::from_raw_parts(bytes.as_ptr().add(i), j - i) };
85
86        std::str::from_utf8(payload_bytes).is_ok()
87    }
88
89    /// Creates a new compact JWS without checking the data.
90    ///
91    /// # Safety
92    ///
93    /// The input `data` must represent a valid compact JWS where the payload
94    /// is an UTF-8 string.
95    pub const unsafe fn new_unchecked(data: &[u8]) -> &Self {
96        std::mem::transmute(data)
97    }
98
99    pub fn as_str(&self) -> &str {
100        unsafe {
101            // Safety: we already checked that the bytes are a valid UTF-8
102            // string.
103            std::str::from_utf8_unchecked(self.0.as_bytes())
104        }
105    }
106}
107
108impl Deref for JwsStr {
109    type Target = JwsSlice;
110
111    fn deref(&self) -> &Self::Target {
112        &self.0
113    }
114}
115
116impl fmt::Display for JwsStr {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        self.as_str().fmt(f)
119    }
120}
121
122impl fmt::Debug for JwsStr {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        self.as_str().fmt(f)
125    }
126}
127
128impl serde::Serialize for JwsStr {
129    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130    where
131        S: serde::Serializer,
132    {
133        self.as_str().serialize(serializer)
134    }
135}
136
137impl PartialEq<str> for JwsStr {
138    fn eq(&self, other: &str) -> bool {
139        self.as_str() == other
140    }
141}
142
143impl PartialEq<String> for JwsStr {
144    fn eq(&self, other: &String) -> bool {
145        self.as_str() == other
146    }
147}
148
149impl<'a> PartialEq<String> for &'a JwsStr {
150    fn eq(&self, other: &String) -> bool {
151        self.as_str() == other
152    }
153}
154
155impl PartialEq<JwsStr> for str {
156    fn eq(&self, other: &JwsStr) -> bool {
157        self == other.as_str()
158    }
159}
160
161impl PartialEq<JwsStr> for String {
162    fn eq(&self, other: &JwsStr) -> bool {
163        self == other.as_str()
164    }
165}
166
167impl<'a> PartialEq<&'a JwsStr> for String {
168    fn eq(&self, other: &&'a JwsStr) -> bool {
169        self == other.as_str()
170    }
171}
172
173/// Owned UTF-8 encoded JWS.
174///
175/// This type is similar to the [`JwsBuf`](crate::JwsBuf) type.
176/// However contrarily to `JwsBuf`, there is no guarantee that the JWS is
177/// URL-safe.
178///
179/// Use [`JwsBuf`](crate::JwsBuf) if you expect URL-safe JWSs.
180#[derive(Clone, serde::Serialize)]
181#[serde(transparent)]
182pub struct JwsString(String);
183
184impl JwsString {
185    pub fn new(bytes: Vec<u8>) -> Result<Self, InvalidJws<Vec<u8>>> {
186        match String::from_utf8(bytes) {
187            Ok(string) => {
188                if JwsSlice::validate(string.as_bytes()) {
189                    Ok(Self(string))
190                } else {
191                    Err(InvalidJws(string.into_bytes()))
192                }
193            }
194            Err(e) => Err(InvalidJws(e.into_bytes())),
195        }
196    }
197
198    pub fn from_string(string: String) -> Result<Self, InvalidJws<String>> {
199        if JwsSlice::validate(string.as_bytes()) {
200            Ok(Self(string))
201        } else {
202            Err(InvalidJws(string))
203        }
204    }
205
206    /// # Safety
207    ///
208    /// The input `bytes` must represent a valid compact JWS where the payload
209    /// is UTF-8 encoded.
210    pub unsafe fn new_unchecked(bytes: Vec<u8>) -> Self {
211        Self(String::from_utf8_unchecked(bytes))
212    }
213
214    /// Creates a new detached JWS from a header and base64-encoded signature.
215    ///
216    /// Detached means the payload will not appear in the JWS.
217    pub fn new_detached(header: Header, b64_signature: &[u8]) -> Result<Self, InvalidJws<Vec<u8>>> {
218        let mut bytes = header.encode().into_bytes();
219        bytes.extend(b"..");
220        bytes.extend(b64_signature.iter().copied());
221        Self::new(bytes)
222    }
223
224    /// Creates a new detached JWS from a header and unencoded signature.
225    ///
226    /// Detached means the payload will not appear in the JWS.
227    pub fn encode_detached(header: Header, signature: &[u8]) -> Self {
228        let b64_signature = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(signature);
229        Self::new_detached(header, b64_signature.as_bytes()).unwrap()
230    }
231
232    /// Encodes the given signature in base64 and returns a compact JWS.
233    pub fn encode_from_signing_bytes_and_signature(
234        signing_bytes: Vec<u8>,
235        signature: &[u8],
236    ) -> Result<Self, InvalidJws<Vec<u8>>> {
237        let b64_signature = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(signature);
238        let mut bytes = signing_bytes;
239        bytes.push(b'.');
240        bytes.extend_from_slice(b64_signature.as_bytes());
241        Self::new(bytes)
242    }
243
244    pub fn from_signing_bytes_and_signature(
245        signing_bytes: Vec<u8>,
246        signature: impl IntoIterator<Item = u8>,
247    ) -> Result<Self, InvalidJws<Vec<u8>>> {
248        let mut bytes = signing_bytes;
249        bytes.push(b'.');
250        bytes.extend(signature);
251        Self::new(bytes)
252    }
253
254    /// # Safety
255    ///
256    /// The input `signing_bytes` and `signature` must form a valid compact JWS
257    /// once concatenated with a `.`.
258    pub unsafe fn from_signing_bytes_and_signature_unchecked(
259        signing_bytes: Vec<u8>,
260        signature: Vec<u8>,
261    ) -> Self {
262        let mut bytes = signing_bytes;
263        bytes.push(b'.');
264        bytes.extend(signature);
265        Self::new_unchecked(bytes)
266    }
267
268    pub fn as_compact_jws_str(&self) -> &JwsStr {
269        unsafe { JwsStr::new_unchecked(self.0.as_bytes()) }
270    }
271
272    pub fn into_signing_bytes(mut self) -> String {
273        self.0.truncate(self.payload_end()); // remove the signature.
274        self.0
275    }
276
277    pub fn into_string(self) -> String {
278        self.0
279    }
280
281    /// Decodes the entire JWS while preserving the signing bytes so they can
282    /// be verified.
283    pub fn into_decoded(self) -> Result<DecodedJws<'static>, DecodeError> {
284        Ok(self.decode()?.into_owned())
285    }
286}
287
288impl Deref for JwsString {
289    type Target = JwsStr;
290
291    fn deref(&self) -> &Self::Target {
292        self.as_compact_jws_str()
293    }
294}
295
296impl fmt::Display for JwsString {
297    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298        self.as_str().fmt(f)
299    }
300}
301
302impl fmt::Debug for JwsString {
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        self.as_str().fmt(f)
305    }
306}
307
308impl FromStr for JwsString {
309    type Err = InvalidJws;
310
311    fn from_str(s: &str) -> Result<Self, Self::Err> {
312        Self::from_string(s.to_owned())
313    }
314}
315
316impl TryFrom<String> for JwsString {
317    type Error = InvalidJws<String>;
318
319    fn try_from(value: String) -> Result<Self, Self::Error> {
320        Self::from_string(value)
321    }
322}
323
324impl<'de> serde::Deserialize<'de> for JwsString {
325    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
326    where
327        D: serde::Deserializer<'de>,
328    {
329        struct Visitor;
330
331        impl<'de> serde::de::Visitor<'de> for Visitor {
332            type Value = JwsString;
333
334            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
335                formatter.write_str("compact JWS")
336            }
337
338            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
339            where
340                E: serde::de::Error,
341            {
342                self.visit_string(v.to_owned())
343            }
344
345            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
346            where
347                E: serde::de::Error,
348            {
349                JwsString::from_string(v).map_err(|e| E::custom(e))
350            }
351        }
352
353        deserializer.deserialize_string(Visitor)
354    }
355}
356
357impl PartialEq<str> for JwsString {
358    fn eq(&self, other: &str) -> bool {
359        self.as_str() == other
360    }
361}
362
363impl<'a> PartialEq<&'a str> for JwsString {
364    fn eq(&self, other: &&'a str) -> bool {
365        self.as_str() == *other
366    }
367}
368
369impl PartialEq<String> for JwsString {
370    fn eq(&self, other: &String) -> bool {
371        self.as_str() == other
372    }
373}
374
375impl PartialEq<JwsString> for str {
376    fn eq(&self, other: &JwsString) -> bool {
377        self == other.as_str()
378    }
379}
380
381impl<'a> PartialEq<JwsString> for &'a str {
382    fn eq(&self, other: &JwsString) -> bool {
383        *self == other.as_str()
384    }
385}
386
387impl PartialEq<JwsString> for String {
388    fn eq(&self, other: &JwsString) -> bool {
389        self == other.as_str()
390    }
391}