engine/vault/
base64.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use thiserror::Error as DeriveError;
5
6#[derive(Debug, DeriveError)]
7#[error("Base64 Error")]
8pub struct Base64Error;
9
10/// a [`Base64`] encoder and decoder used in the Vault.
11pub struct Base64;
12impl Base64 {
13    /// base64 padding character
14    const PADDING: u8 = b'=';
15
16    /// encode a [`&[u8]`] using a base64 uri-safe character set.
17    pub fn encode_data(data: &[u8]) -> String {
18        // encode data
19        let mut base = Vec::new();
20        for chunk in data.chunks(3) {
21            let num: usize = [16, 8, 0]
22                .iter()
23                .zip(chunk.iter())
24                .fold(0, |acc, (s, b)| acc + ((*b as usize) << *s));
25            [18usize, 12, 6, 0]
26                .iter()
27                .map(|s| (num >> s) & 0b0011_1111)
28                .for_each(|b| base.push(Self::encode_byte(b)));
29        }
30
31        // apply padding
32        let to_pad = match data.len() % 3 {
33            2 => 1,
34            1 => 2,
35            _ => 0,
36        };
37        base.iter_mut().rev().take(to_pad).for_each(|b| *b = Self::PADDING);
38
39        match String::from_utf8(base) {
40            Ok(s) => s,
41            Err(e) => {
42                let error = e.utf8_error();
43                let valid_up_to = error.valid_up_to();
44                let error_msg = format!("failure encoding to base64: valid_up_to({})", valid_up_to);
45                panic!("{}", error_msg)
46            }
47        }
48    }
49
50    /// decode a [`&[u8]`] from base64 based off of the URI safe character set
51    pub fn decode_data(base: &[u8]) -> Result<Vec<u8>, Base64Error> {
52        // find and remove padding.
53        let (padded, base) = match base.iter().rev().take_while(|b| **b == Self::PADDING).count() {
54            _ if base.len() % 4 != 0 => return Err(Base64Error),
55            padded if padded > 2 => return Err(Base64Error),
56            padded => (padded, &base[..base.len() - padded]),
57        };
58
59        // decode the data.
60        let mut data = Vec::new();
61        for chunk in base.chunks(4) {
62            let num: usize = [18usize, 12, 6, 0]
63                .iter()
64                .zip(chunk.iter())
65                .try_fold(0, |acc, (s, b)| Self::decode_byte(*b).map(|b| acc + (b << *s)))?;
66            [16, 8, 0].iter().map(|s| (num >> s) as u8).for_each(|b| data.push(b));
67        }
68
69        // remove any trailing padding related zeroes
70        data.truncate(data.len() - padded);
71        Ok(data)
72    }
73
74    /// encode a single byte
75    fn encode_byte(b: usize) -> u8 {
76        match b {
77            b @ 0..=25 => (b as u8) + b'A',
78            b @ 26..=51 => (b as u8 - 26) + b'a',
79            b @ 52..=61 => (b as u8 - 52) + b'0',
80            62 => b'-',
81            63 => b'_',
82            _ => panic!("{:?} ({})", Base64Error, b),
83        }
84    }
85
86    /// decode a single byte
87    fn decode_byte(b: u8) -> Result<usize, Base64Error> {
88        match b {
89            b @ b'A'..=b'Z' => Ok((b - b'A') as usize),
90            b @ b'a'..=b'z' => Ok((b - b'a') as usize + 26),
91            b @ b'0'..=b'9' => Ok((b - b'0') as usize + 52),
92            b'-' => Ok(62),
93            b'_' => Ok(63),
94            _ => Err(Base64Error),
95        }
96    }
97}
98
99/// a trait to make types base64 encodable
100pub trait Base64Encodable {
101    fn base64(&self) -> String;
102}
103
104/// a trait to make types base64 decodable
105pub trait Base64Decodable: Sized {
106    type Error;
107    fn from_base64(base: impl AsRef<[u8]>) -> Result<Self, Self::Error>;
108}
109
110impl<T: AsRef<[u8]>> Base64Encodable for T {
111    fn base64(&self) -> String {
112        Base64::encode_data(self.as_ref())
113    }
114}
115
116impl Base64Decodable for Vec<u8> {
117    type Error = Base64Error;
118    fn from_base64(base: impl AsRef<[u8]>) -> Result<Self, Self::Error> {
119        Base64::decode_data(base.as_ref())
120    }
121}