lil_jwt/
lib.rs

1use core::str::FromStr;
2use core::convert::Infallible;
3
4use base64::{prelude::{BASE64_URL_SAFE, BASE64_URL_SAFE_NO_PAD}, DecodeError, DecodeSliceError, Engine};
5use embedded_io::{ErrorType, Read, Write};
6use hmac::{digest::Update, Hmac, Mac};
7use lil_json::{parse_json_object, serialize_json_object, JsonField, JsonObject, JsonParseFailure, JsonValue, EMPTY_FIELD};
8use sha2::{Sha256, Sha384, Sha512};
9
10use crate::{authenticated_writer::AuthenticatedWriter, base64_writer::Base64UrlBlockEncoder};
11
12mod base64_writer;
13mod authenticated_writer;
14
15struct Empty{}
16
17impl ErrorType for Empty {
18    type Error = Infallible;
19}
20
21impl Write for Empty {
22    fn write(& mut self, data: &[u8]) -> Result<usize, <Self as ErrorType>::Error> { Ok(data.len()) }
23    fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) }
24}
25
26impl Read for Empty {
27    fn read(&mut self, _: &mut [u8]) -> Result<usize, <Self as ErrorType>::Error> { Ok(0) }
28}
29
30
31#[derive(Debug,PartialEq,Eq,Clone,Copy)]
32pub enum SignatureAlgorithm {
33    HS256,
34    HS384,
35    HS512,
36}
37
38impl core::fmt::Display for SignatureAlgorithm {
39    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
40        f.write_str(Self::as_static_string(&self))
41    }
42}
43
44impl FromStr for SignatureAlgorithm {
45    type Err = ();
46    fn from_str(string: &str) -> Result<Self, <Self as FromStr>::Err> {
47        match Self::from_string(string) {
48            Some(s) => Ok(s),
49            None => Err(())
50        }
51    }
52}
53
54impl SignatureAlgorithm {
55    const fn as_static_string(&self) -> &'static str {
56        match self {
57            SignatureAlgorithm::HS256 => "HS256",
58            SignatureAlgorithm::HS384 => "HS384",
59            SignatureAlgorithm::HS512 => "HS512",
60        }
61    }
62    fn from_string(string: &str) -> Option<Self> {
63        return Some(match string {
64            "HS256" => SignatureAlgorithm::HS256,
65            "HS384" => SignatureAlgorithm::HS384,
66            "HS512" => SignatureAlgorithm::HS512,
67            _ => return None,
68        })
69    }
70}
71
72#[derive(Debug,PartialEq,Eq,Clone,Copy)]
73pub enum EncryptionAlgorithm {
74    // TODO
75}
76
77#[derive(Debug,PartialEq,Eq,Clone,Copy)]
78pub enum JwtType {
79    Unsecured,
80    Signed(SignatureAlgorithm),
81    Encrypted(EncryptionAlgorithm)
82}
83
84impl JwtType {
85    const fn as_static_string(&self) -> &'static str {
86        match self {
87            Self::Unsecured => "none",
88            Self::Signed(signature_algorithm) => signature_algorithm.as_static_string(),
89            Self::Encrypted(_) => todo!(),
90        }
91    }
92    fn from_string(string: &str) -> Option<Self> {
93        if string == "none" {
94            return Some(Self::Unsecured);
95        } else if let Some(s) = SignatureAlgorithm::from_string(string) {
96            return Some(JwtType::Signed(s))
97        }
98        // TODO: encryption algorithms
99        return None;
100    }
101}
102
103#[derive(Debug)]
104pub enum JwtParseFailure {
105    NotEnoughDots,
106    InvalidBase64Url(DecodeError),
107    Base64BufferTooSmall,
108    InvalidSignature,
109    // InvalidEncryption,
110    AlgorithmMismatch,
111    IncorrectHeader,
112    InvalidHeader(JsonParseFailure),
113    InvalidClaims(JsonParseFailure),
114}
115
116pub struct JsonWebToken<'a> {
117    claims: &'a [JsonField<'a,'a>],
118}
119
120impl<'a> JsonWebToken<'a> {
121
122    pub fn from_claims(claims: &'a [JsonField<'a,'a>]) -> Self {
123        Self { claims }
124    }
125
126    pub fn serialize<T: Write>(&self, output: T, algorithm: JwtType, secret: &[u8]) -> Result<usize,T::Error> {
127        serialize_jwt(output, self.claims, &algorithm, secret)
128    }
129
130    pub fn deserialize_claims<const MAX_CLAIMS: usize>(data: &'a[u8], base64buffer: &'a mut [u8], algorithm: JwtType, secret: &[u8]) -> Result<JsonObject<'a,MAX_CLAIMS>,JwtParseFailure> {
131        let mut claims_buffer = [EMPTY_FIELD; MAX_CLAIMS];
132        let num_claims = deserialize_jwt(data, &mut claims_buffer, &algorithm, secret, base64buffer)?;
133        let mut ret = JsonObject::<MAX_CLAIMS>::new();
134        for claim in claims_buffer.split_at(num_claims).0 {
135            ret.push(*claim).expect("ret holds MAX_CLAIMS");
136        }
137        Ok(ret)
138    }
139}
140
141const fn get_jose_header(include_typ_header: bool, algorithm: &JwtType) -> JsonObject<'static,2> {
142    let mut ret = JsonObject::<2>::new();
143    match ret.push_field("alg", JsonValue::String(algorithm.as_static_string())) {
144        Ok(()) => {},
145        Err(()) => unreachable!(),
146    }
147    if include_typ_header {
148        match ret.push_field("typ", JsonValue::String("JWT")) {
149            Ok(()) => {},
150            Err(()) => unreachable!(),
151        }
152    }
153    ret
154}
155
156fn split_jwt_parts(data: &[u8]) -> Result<(&[u8],&[u8],&[u8]),JwtParseFailure>  {
157    let mut dot_indices = data
158        .iter()
159        .enumerate()
160        .filter_map(|(i, &b)| if b == b'.' { Some(i) } else { None });
161    let first_dot = match dot_indices.next() {
162        Some(f) => f,
163        None => return Err(JwtParseFailure::NotEnoughDots),
164    };
165    let second_dot = match dot_indices.next() {
166        Some(s) => s,
167        None => return Err(JwtParseFailure::NotEnoughDots),
168    };
169    let (header_slice, after_header) = data.split_at(first_dot);
170    let (body_with_dot, signature_with_dot) = after_header.split_at(second_dot - first_dot);
171    let body_slice = body_with_dot.split_at(1).1;
172    let signature_slice = signature_with_dot.split_at(1).1;
173    Ok((header_slice,body_slice,signature_slice))
174}
175
176fn verify_jose_header(header_fields: &[JsonField<'_,'_>], expected_algorithm: &JwtType) -> Result<(),JwtParseFailure> {
177    let mut alg_header: Option<&str> = None;
178    for header_field in header_fields {
179        if header_field.key == "alg" {
180            match header_field.value {
181                JsonValue::String(alg_value) => {
182                    match alg_header.replace(alg_value) {
183                        None => {},
184                        Some(_duplicate_alg_header) => return Err(JwtParseFailure::IncorrectHeader),
185                    }
186                },
187                _ => return Err(JwtParseFailure::IncorrectHeader)
188            }
189        }
190    }
191    let alg_header_value = match alg_header {
192        None => return Err(JwtParseFailure::IncorrectHeader),
193        Some(v) => v,
194    };
195    if alg_header_value != expected_algorithm.as_static_string() {
196        return Err(JwtParseFailure::AlgorithmMismatch);
197    }
198    Ok(())
199}
200
201pub fn deserialize_jwt<'a>(data: &'a [u8], claims_buffer: &mut [JsonField<'a,'a>], algorithm: &JwtType, secret: &[u8], base64buffer: &'a mut [u8]) -> Result<usize,JwtParseFailure> {
202    let (header_b64,body_b64,signature_b64) = split_jwt_parts(data)?;
203    match algorithm {
204        JwtType::Unsecured => {
205             let header_decoded_end = match BASE64_URL_SAFE_NO_PAD.decode_slice(header_b64, base64buffer) {
206                Ok(n) => n,
207                Err(DecodeSliceError::OutputSliceTooSmall) => return Err(JwtParseFailure::Base64BufferTooSmall),
208                Err(DecodeSliceError::DecodeError(e)) => return Err(JwtParseFailure::InvalidBase64Url(e)),
209            };
210            let (decoded_header,remaining_base64_buffer) = base64buffer.split_at_mut(header_decoded_end);
211            let body_decoded_end = match BASE64_URL_SAFE_NO_PAD.decode_slice(body_b64, remaining_base64_buffer) {
212                Ok(n) => n,
213                Err(DecodeSliceError::OutputSliceTooSmall) => return Err(JwtParseFailure::Base64BufferTooSmall),
214                Err(DecodeSliceError::DecodeError(e)) => return Err(JwtParseFailure::InvalidBase64Url(e)),
215            };
216            let decoded_claims = remaining_base64_buffer.split_at(body_decoded_end).0;
217            let mut header_buffer = [EMPTY_FIELD; 5];
218            let (_num_data,num_header_fields) = match parse_json_object(decoded_header, &mut header_buffer) {
219                Ok(n) => n,
220                Err(j) => return Err(JwtParseFailure::InvalidHeader(j)),
221            };
222            verify_jose_header(header_buffer.split_at(num_header_fields).0, algorithm)?;
223            let num_claims = match parse_json_object(decoded_claims, claims_buffer) {
224                Ok((_num_bytes,n)) => n,
225                Err(j) => return Err(JwtParseFailure::InvalidClaims(j)),
226            };
227            Ok(num_claims)
228        },
229        JwtType::Signed(SignatureAlgorithm::HS256) => {
230            let digest = Hmac::<Sha256>::new_from_slice(secret).expect("invalid HS256 secret");
231            let mac = digest
232            .chain_update(header_b64)
233            .chain_update(b".")
234            .chain_update(body_b64)
235            .finalize()
236            .into_bytes();
237            let signature_decoded_end = match BASE64_URL_SAFE_NO_PAD.decode_slice(signature_b64, base64buffer) {
238                Ok(n) => n,
239                Err(DecodeSliceError::OutputSliceTooSmall) => return Err(JwtParseFailure::Base64BufferTooSmall),
240                Err(DecodeSliceError::DecodeError(e)) => return Err(JwtParseFailure::InvalidBase64Url(e)),
241            };
242            if signature_decoded_end != 32 || mac.as_slice() != base64buffer.split_at(signature_decoded_end).0 {
243                return Err(JwtParseFailure::InvalidSignature);
244            }
245            let header_decoded_end = match BASE64_URL_SAFE_NO_PAD.decode_slice(header_b64, base64buffer) {
246                Ok(n) => n,
247                Err(DecodeSliceError::OutputSliceTooSmall) => return Err(JwtParseFailure::Base64BufferTooSmall),
248                Err(DecodeSliceError::DecodeError(e)) => return Err(JwtParseFailure::InvalidBase64Url(e)),
249            };
250            let (decoded_header,remaining_base64_buffer) = base64buffer.split_at_mut(header_decoded_end);
251            let body_decoded_end = match BASE64_URL_SAFE_NO_PAD.decode_slice(body_b64, remaining_base64_buffer) {
252                Ok(n) => n,
253                Err(DecodeSliceError::OutputSliceTooSmall) => return Err(JwtParseFailure::Base64BufferTooSmall),
254                Err(DecodeSliceError::DecodeError(e)) => return Err(JwtParseFailure::InvalidBase64Url(e)),
255            };
256            let decoded_claims = remaining_base64_buffer.split_at(body_decoded_end).0;
257            let mut header_buffer = [EMPTY_FIELD; 5];
258            let (_num_data,num_header_fields) = match parse_json_object(decoded_header, &mut header_buffer) {
259                Ok(n) => n,
260                Err(j) => return Err(JwtParseFailure::InvalidHeader(j)),
261            };
262            verify_jose_header(header_buffer.split_at(num_header_fields).0, algorithm)?;
263            let num_claims = match parse_json_object(decoded_claims, claims_buffer) {
264                Ok((_num_bytes,n)) => n,
265                Err(j) => return Err(JwtParseFailure::InvalidClaims(j)),
266            };
267            Ok(num_claims)
268        },
269        JwtType::Signed(SignatureAlgorithm::HS384) => {
270            let digest = Hmac::<Sha384>::new_from_slice(secret).expect("invalid HS256 secret");
271            let mac = digest
272            .chain_update(header_b64)
273            .chain_update(b".")
274            .chain_update(body_b64)
275            .finalize()
276            .into_bytes();
277            let signature_decoded_end = match BASE64_URL_SAFE_NO_PAD.decode_slice(signature_b64, base64buffer) {
278                Ok(n) => n,
279                Err(DecodeSliceError::OutputSliceTooSmall) => return Err(JwtParseFailure::Base64BufferTooSmall),
280                Err(DecodeSliceError::DecodeError(e)) => return Err(JwtParseFailure::InvalidBase64Url(e)),
281            };
282            if signature_decoded_end != 48 || mac.as_slice() != base64buffer.split_at(signature_decoded_end).0 {
283                return Err(JwtParseFailure::InvalidSignature);
284            }
285            todo!()
286        },
287        JwtType::Signed(SignatureAlgorithm::HS512) => {
288            let digest = Hmac::<Sha512>::new_from_slice(secret).expect("invalid HS256 secret");
289            let mac = digest
290            .chain_update(header_b64)
291            .chain_update(b".")
292            .chain_update(body_b64)
293            .finalize()
294            .into_bytes();
295            let signature_decoded_end = match BASE64_URL_SAFE_NO_PAD.decode_slice(signature_b64, base64buffer) {
296                Ok(n) => n,
297                Err(DecodeSliceError::OutputSliceTooSmall) => return Err(JwtParseFailure::Base64BufferTooSmall),
298                Err(DecodeSliceError::DecodeError(e)) => return Err(JwtParseFailure::InvalidBase64Url(e)),
299            };
300            if signature_decoded_end != 64 || mac.as_slice() != base64buffer.split_at(signature_decoded_end).0 {
301                return Err(JwtParseFailure::InvalidSignature);
302            }
303            todo!()
304        },
305        _ => todo!()
306    }
307}
308
309fn serialize_object_base64<T: embedded_io::Write>(output: T, claims: &[JsonField<'_,'_>]) -> Result<usize,T::Error> {
310    let mut body_encoder = Base64UrlBlockEncoder::new(output);
311    serialize_json_object(&mut body_encoder, claims)?;
312    body_encoder.finalize(false)
313}
314
315fn serialize_slice_base64<T: embedded_io::Write>(output: T, slice: &[u8]) -> Result<usize,T::Error> {
316    let mut slice_encoder = Base64UrlBlockEncoder::new(output);
317    slice_encoder.write_all(slice)?;
318    slice_encoder.finalize(false)
319}
320
321pub fn serialize_jwt<T: embedded_io::Write>(mut output: T, claims: &[JsonField<'_,'_>], algorithm: &JwtType, secret: &[u8]) -> Result<usize,T::Error> {
322    let header = get_jose_header(algorithm != &JwtType::Unsecured, algorithm);
323    let mut ret = 0;
324    match algorithm {
325        JwtType::Unsecured => {
326            ret += serialize_object_base64(&mut output, header.as_slice())?;
327            output.write_all(b".")?;
328            ret += 1;
329            ret += serialize_object_base64(&mut output, claims)?;
330            output.write_all(b".")?;
331            ret += 1;
332            Ok(ret)
333        },
334        JwtType::Signed(SignatureAlgorithm::HS256) => {
335            // assert!(secret.len() >= 256);
336            let digest = Hmac::<Sha256>::new_from_slice(secret).expect("invalid HS256 secret");
337            let mut authenticated_writer = AuthenticatedWriter::new(&mut output, digest);
338            ret += serialize_object_base64(&mut authenticated_writer, header.as_slice()).unwrap();
339            authenticated_writer.write_all(b".")?;
340            ret += 1;
341            ret += serialize_object_base64(&mut authenticated_writer, claims)?;
342            let mac = authenticated_writer.finalize_mac();
343            output.write_all(b".")?;
344            ret += 1;
345            ret += serialize_slice_base64(&mut output, &mac.into_bytes())?;
346            Ok(ret)
347        },
348        JwtType::Signed(SignatureAlgorithm::HS384) => {
349            // assert!(secret.len() >= 384);
350            let digest = Hmac::<Sha384>::new_from_slice(secret).expect("invalid HS384 secret");
351            let mut authenticated_writer = AuthenticatedWriter::new(&mut output, digest);
352            ret += serialize_object_base64(&mut authenticated_writer, header.as_slice()).unwrap();
353            authenticated_writer.write_all(b".")?;
354            ret += 1;
355            ret += serialize_object_base64(&mut authenticated_writer, claims)?;
356            let mac = authenticated_writer.finalize_mac();
357            output.write_all(b".")?;
358            ret += 1;
359            ret += serialize_slice_base64(&mut output, &mac.into_bytes())?;
360            Ok(ret)
361        },
362        JwtType::Signed(SignatureAlgorithm::HS512) => {
363            // assert!(secret.len() >= 512);
364            let digest = Hmac::<Sha512>::new_from_slice(secret).expect("invalid HS512 secret");
365            let mut authenticated_writer = AuthenticatedWriter::new(&mut output, digest);
366            ret += serialize_object_base64(&mut authenticated_writer, header.as_slice()).unwrap();
367            authenticated_writer.write_all(b".")?;
368            ret += 1;
369            ret += serialize_object_base64(&mut authenticated_writer, claims)?;
370            let mac = authenticated_writer.finalize_mac();
371            output.write_all(b".")?;
372            ret += 1;
373            ret += serialize_slice_base64(&mut output, &mac.into_bytes())?;
374            Ok(ret)
375        },
376        JwtType::Encrypted(_) => todo!(),
377    }
378}
379
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384
385     #[test]
386    fn test_serialize_unsecured_empty() {
387        let mut buffer = [0_u8; 256];
388        let n = JsonWebToken::from_claims(&[]).serialize(buffer.as_mut_slice(), JwtType::Unsecured, &[]).unwrap();
389        assert_eq!(b"eyJhbGciOiJub25lIn0.e30.", buffer.split_at(n).0)
390    }
391
392}