branca/
lib.rs

1/*!
2Branca - Authenticated and Encrypted API tokens using modern cryptography.
3
4This crate is a pure-Rust implementation of [Branca](https://branca.io)
5which allows generating authenticated and encrypted tamper-proof tokens.
6The [Branca specification](https://github.com/tuupola/branca-spec) is based on the
7[Fernet specification](https://github.com/fernet/spec/blob/master/Spec.md) and is also similar in
8its token format but it differs from the cipher that it uses for encryption and decryption and
9the encoding format of the token. Branca uses [IETF XChaCha20-Poly1305 AEAD](https://tools.ietf.org/html/draft-arciszewski-xchacha-00#section-2.3.1)
10for encryption and decryption and uses Base62 instead of Base64 for encoding the tokens to be URL safe.
11
12A Branca token is encrypted then encoded into Base62, and looks like this:
13
14`875GH233T7IYrxtgXxlQBYiFobZMQdHAT51vChKsAIYCFxZtL1evV54vYqLyZtQ0ekPHt8kJHQp0a`
15
16
17The internal format of a Branca token looks like this:
18
19`Version (1B) || Timestamp (4B) || Nonce (24B) || Ciphertext (*B) || Tag (16B)`
20
21
22The payload/ciphertext size can be of any arbitrary length, this means that contents of the payload can be anything from Text,
23[JSON](https://en.wikipedia.org/wiki/JSON), [MessagePacks](http://msgpack.org/), [JWTs](https://jwt.io/), URLs, etc.
24This allows a Branca Token to be a secure alternative to JWT, since it is authenticated and encrypted by default
25and supports only one cipher as the standard; unlike JWT.
26
27This [blog post](https://appelsiini.net/2017/branca-alternative-to-jwt/) explains
28in more detail on using Branca tokens as an alternative to JWTs.
29
30Also see: [branca-spec](https://github.com/tuupola/branca-spec) for more information about the token specification.
31
32# Examples
33
34A straightforward example of generating these tokens using the `Branca::new()` builder:
35
36```rust
37extern crate branca;
38extern crate getrandom;
39
40use branca::Branca;
41
42fn main() {
43    let mut key = [0u8; 32];
44    getrandom::fill(&mut key).unwrap();
45
46    let mut token = Branca::new(&key).unwrap();
47    let ciphertext = token.encode(b"Hello World!").unwrap();
48
49    let payload = token.decode(ciphertext.as_str(), 0).unwrap();
50    println!("{}", String::from_utf8(payload).unwrap()); // "Hello World!"
51}
52```
53
54You can also decide to set the other fields in the token before encoding it if you want to since
55this is a builder method.
56
57
58```rust
59extern crate branca;
60extern crate getrandom;
61
62use branca::Branca;
63
64fn main() {
65    let mut key = [0u8; 32];
66    getrandom::fill(&mut key).unwrap();
67
68    let mut token = Branca::new(&key).unwrap();
69
70    // You are able to use the builder to set the timestamp, ttL and the key.
71    // However, the nonce cannot be set, as that is a security risk for
72    // nonce-reuse misuse.
73
74    // All properties inside of the token can be retrieved.
75
76    let ciphertext = token
77                      .set_timestamp(1234567890)
78                      .set_key(key.to_vec())
79                      .set_ttl(300);
80                      //.encode(b"Hello World!").unwrap();
81
82    let timestamp = token.timestamp(); // 1234567890
83}
84```
85
86It is also possible to directly encode and decode functions without using the builder function.
87
88Please note that Branca uses [Orion](https://github.com/orion-rs/orion) to generate secure random nonces
89when using the encode() and builder methods. By default, Branca does not allow setting the nonce directly
90since that there is a risk that it can be reused by the user which is a foot-gun.
91
92```rust
93extern crate branca;
94extern crate getrandom;
95
96use branca::{encode, decode};
97
98let mut key = [0u8; 32];
99getrandom::fill(&mut key).unwrap();
100
101let token = encode(b"Hello World!", &key, 123206400).unwrap();
102// token = "875G...p0a"
103
104let ttl = 3600;
105// The token will expire at timestamp + ttl
106let payload = decode(token.as_str(), &key, 0).unwrap();
107
108println!("{}", String::from_utf8(payload).unwrap());
109// payload = "Hello World!"
110
111```
112*/
113
114extern crate base_x;
115extern crate byteorder;
116extern crate orion;
117
118#[cfg(test)]
119extern crate serde;
120#[cfg(test)]
121#[macro_use]
122extern crate serde_json;
123#[cfg(test)]
124#[macro_use]
125extern crate serde_derive;
126
127pub mod errors;
128
129use self::errors::Error as BrancaError;
130use base_x::{decode as b62_decode, encode as b62_encode};
131use byteorder::*;
132use orion::errors::UnknownCryptoError;
133use orion::hazardous::aead::xchacha20poly1305::*;
134use orion::hazardous::stream::chacha20::CHACHA_KEYSIZE;
135use orion::hazardous::stream::xchacha20::XCHACHA_NONCESIZE;
136use orion::util::secure_rand_bytes;
137use std::str;
138use std::time::{SystemTime, UNIX_EPOCH};
139
140// Branca magic byte.
141const VERSION: u8 = 0xBA;
142// Base 62 alphabet.
143const BASE62: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
144
145/// The Branca struct defines the structure of a Branca token for encoding and decoding.
146#[derive(Clone)]
147pub struct Branca {
148    /// The Branca Key which is exactly 32 bytes in length.
149    key: Vec<u8>,
150    /// The Branca Nonce which is exactly 24 bytes in length.
151    /// A new nonce is generated each time `encode()` is called.
152    nonce: Vec<u8>,
153    /// The Time-to-live field (TTL) to set the number of seconds
154    /// for the token to be valid for after its creation set in the `timestamp` field.
155    ///
156    /// If the TTL field is set to 0, then the expiration check is omitted.
157    /// By default it is set to 0 when using `Branca::new(&key)` method.
158    ttl: u32,
159    /// The creation time of the Branca token. If not specified manually, it is created
160    /// given the current system time, each time `encode()` is called.
161    ///
162    /// This is used together with the `ttl` to check if the token has expired or not.
163    timestamp: u32,
164}
165
166impl PartialEq for Branca {
167    fn eq(&self, other: &Branca) -> bool {
168        let key_eq: bool =
169            orion::util::secure_cmp(self.key[..].as_ref(), other.key[..].as_ref()).is_ok();
170
171        key_eq
172            & (self.nonce == other.nonce)
173            & (self.ttl == other.ttl)
174            & (self.timestamp == other.timestamp)
175    }
176}
177
178impl core::fmt::Debug for Branca {
179    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
180        write!(
181            f,
182            "Branca {{ key: [SECRET VALUE], nonce: {:?}, ttl: {:?},
183            timestamp: {:?} }}",
184            self.nonce, self.ttl, self.timestamp
185        )
186    }
187}
188
189impl Branca {
190    /// Create a new Branca struct with a specified key. The length of the key must be exactly 32 bytes.
191    ///
192    /// This panics if the current system time cannot be obtained.
193    ///
194    /// `key` - The key to be used for encrypting and decrypting the input.
195    ///
196    ///```rust
197    /// extern crate getrandom;
198    /// extern crate branca;
199    /// use branca::Branca;
200    ///
201    /// fn main() {
202    ///        let mut key = [0u8; 32];
203    ///        getrandom::fill(&mut key).unwrap();
204    ///        let token = Branca::new(&key);
205    /// }
206    ///```
207    pub fn new(key: &[u8]) -> Result<Branca, BrancaError> {
208        // Check the key length before going any further.
209        if key.len() != CHACHA_KEYSIZE {
210            return Err(BrancaError::BadKeyLength);
211        }
212
213        Ok(Branca {
214            key: key.to_vec(),
215            nonce: Vec::new(),
216            ttl: 0,
217            timestamp: 0,
218        })
219    }
220
221    /// The Branca key used for encrypting the message.
222    pub fn key(self) -> Vec<u8> {
223        self.key
224    }
225    /// The Branca nonce used with the key for encrypting the message.
226    /// A new nonce is generated each time `encode()` is called. If `encode()`
227    /// hasn't been called yet, this returns an empty vector.
228    pub fn nonce(self) -> Vec<u8> {
229        self.nonce
230    }
231    /// The Time-To-Live (TTL) field used for setting the expiration time of the
232    /// generated Branca token in seconds.
233    pub fn ttl(self) -> u32 {
234        self.ttl
235    }
236    /// The timestamp determines the validity of the token upon creation.
237    /// When used with the TTL, the expiration time is determined simply by:
238    /// `exp_timestamp = (timestamp + ttl)`
239    ///
240    /// If this hasn't been specified manually, or `encode()` hasn't been called yet,
241    /// this returns `0`.
242    ///
243    /// If the token has been set with a TTL, the decoder checks the validity of the token
244    /// and any timestamp set before the future_time is valid, otherwise it is expired.
245    pub fn timestamp(self) -> u32 {
246        self.timestamp
247    }
248    /// Sets the key used for encryption and decryption.
249    ///
250    /// Must be 32 bytes in length.
251    pub fn set_key(&mut self, key: Vec<u8>) -> &mut Self {
252        self.key = key;
253        self
254    }
255    /// Sets the TTL used for token expiration.
256    pub fn set_ttl(&mut self, ttl: u32) -> &mut Self {
257        self.ttl = ttl;
258        self
259    }
260    /// Sets the timestamp key used for validating with the TTL.
261    pub fn set_timestamp(&mut self, timestamp: u32) -> &mut Self {
262        self.timestamp = timestamp;
263        self
264    }
265    /// Encodes the message with the created Branca struct.
266    ///
267    /// This panics if unable to securely generate random bytes or if unable to obtain current system time.
268    ///
269    /// If the `timestamp` hasn't been set, the current system time is used. If `timestamp` has been set and is not `0`,
270    /// that value is used for all tokens created with this function (until changed).
271    ///
272    /// The contents of the message can be of any arbitrary sequence of bytes, ie. text, JSON, Protobuf, JWT or a MessagePack, etc.
273    ///
274    /// `message` - The data to be encoded as a Branca token.
275    ///
276    /// # Example
277    /// ```rust
278    /// extern crate getrandom;
279    /// extern crate branca;
280    /// use branca::Branca;
281    ///
282    /// fn main() {
283    ///     let mut key = [0u8; 32];
284    ///     getrandom::fill(&mut key).unwrap();
285    ///     let mut token = Branca::new(&key).unwrap();
286    ///
287    ///     let ciphertext = token.encode(b"Hello World!").unwrap();
288    ///     // Branca token is now in 'ciphertext' as a String.
289    /// }
290    /// ```
291    pub fn encode(&mut self, message: &[u8]) -> Result<String, BrancaError> {
292        // A timestamp has not been manually set, so we create one automatically.
293        let mut timestamp = self.timestamp;
294        if timestamp == 0 {
295            // Generate a timestamp instead of a zero supplied one.
296            let ts = SystemTime::now()
297                .duration_since(SystemTime::UNIX_EPOCH)
298                .expect("Failed to obtain timestamp from system clock.");
299            timestamp = ts.as_secs() as u32;
300        }
301
302        // Generate Nonce (24 bytes in length)
303        let mut nonce = [0; XCHACHA_NONCESIZE];
304        secure_rand_bytes(&mut nonce).unwrap();
305        self.nonce = nonce.to_vec();
306
307        encode_with_nonce(message, &self.key, &Nonce::from(nonce), timestamp)
308    }
309
310    /// Decodes a Branca token with the provided key in the struct.
311    ///
312    /// `ciphertext` - The input which is to be decrypted with the key found in the Branca struct.
313    ///
314    /// `ttl` - The time-to-live upon creation of the token with its timestamp in seconds.
315    ///
316    /// If the supplied ttl is set to 0, then the expiration check is omitted. It is recommended to
317    /// set this if you want to check the timestamp of an incoming token generated by the client.
318    ///
319    ///# Example
320    ///```rust
321    /// extern crate getrandom;
322    /// extern crate branca;
323    /// use branca::Branca;
324    ///
325    /// fn main() {
326    ///     let mut key = [0u8; 32];
327    ///     getrandom::fill(&mut key).unwrap();
328    ///
329    ///     let mut token = Branca::new(&key).unwrap();
330    ///     let crypted = token.encode(b"Hello World!").unwrap();
331    ///     // Branca token is now in 'crypted' as a String.
332    ///    
333    ///     let decrypted = token.decode(crypted.as_str(), 3600);
334    ///     let mut payload: Vec<u8> = Vec::new();
335    ///
336    ///     if decrypted.is_err() {
337    ///       // Something went wrong here...
338    ///     } else {
339    ///       payload = decrypted.unwrap();
340    ///      // payload now contains "Hello World!"
341    ///     }
342    /// }
343    /// ```
344    pub fn decode(&self, ciphertext: &str, ttl: u32) -> Result<Vec<u8>, BrancaError> {
345        decode(ciphertext, &self.key, ttl)
346    }
347}
348
349/// Encodes the message and returns a Branca Token as a String.
350///
351/// The contents of the message can be of any arbitrary sequence of bytes, ie. text, JSON, Protobuf, JWT or a MessagePack, etc.
352///
353/// `data` - The data to be encoded as a Branca token.
354///
355/// `key` - The key to use for encryption.
356///
357/// `timestamp` - The timestamp at which the token was created.
358///
359/// Note:
360///
361/// * The key must be 32 bytes in length, otherwise it returns a `BrancaError::BadKeyLength` Result.
362///
363/// * The generated nonce is 24 bytes in length, otherwise it returns a `BrancaError::BadNonceLength` Result.
364///
365/// * This function panics if unable to securely generate random bytes.
366pub fn encode(data: &[u8], key: &[u8], timestamp: u32) -> Result<String, BrancaError> {
367    // Use CSPRNG to generate a unique nonce.
368    let n = Nonce::generate();
369
370    encode_with_nonce(data, key, &n, timestamp)
371}
372
373fn encode_with_nonce(
374    data: &[u8],
375    key: &[u8],
376    nonce: &Nonce,
377    timestamp: u32,
378) -> Result<String, BrancaError> {
379    let sk: SecretKey = match SecretKey::from_slice(key) {
380        Ok(key) => key,
381        Err(UnknownCryptoError) => return Err(BrancaError::BadKeyLength),
382    };
383
384    // Version || Timestamp || Nonce
385    let mut header = [0u8; 29];
386
387    header[0] = VERSION;
388    BigEndian::write_u32(&mut header[1..5], timestamp);
389    header[5..29].copy_from_slice(nonce.as_ref());
390
391    let mut buf_crypt = vec![0u8; data.len() + 16 + 29]; // 16 bytes for the Poly1305 Tag.
392                                                         // The header is prepended to the ciphertext and tag.
393    buf_crypt[..29].copy_from_slice(header.as_ref());
394
395    match seal(
396        &sk,
397        nonce,
398        data,
399        Some(header.as_ref()),
400        &mut buf_crypt[29..],
401    ) {
402        Ok(()) => (),
403        Err(UnknownCryptoError) => return Err(BrancaError::EncryptFailed),
404    };
405
406    // Return payload encoded as base62.
407    Ok(b62_encode(BASE62, buf_crypt.as_ref()))
408}
409
410/// Decodes a Branca token and returns the plaintext as a String.
411///
412/// `data` - The input which is to be decrypted with the user-supplied key.
413///
414/// `key` - The user-supplied key to use for decryption.
415///
416/// `ttl` - The time-to-live upon creation of the token with its timestamp in seconds.
417///
418/// If the supplied ttl is set to 0, then the expiration check is omitted. It is recommended to
419/// set this if you want to check the timestamp of an incoming token generated by the client.
420///
421/// Note:
422///
423/// * The key must be 32 bytes in length, otherwise it returns a `BrancaError::BadKeyLength` Result.
424///
425/// * If the decryption fails, it returns a `BrancaError::DecryptFailed` Result.
426///
427/// * If the TTL is non-zero and the timestamp of the token is in the past, it is considered to be expired; returning a `BrancaError::ExpiredToken` Result.
428///
429/// * If the input is not in Base62 format, it returns a `BrancaError::InvalidBase62Token` Result.
430///
431/// * If adding TTL to the token timestamp results in an overflow, it returns a `BrancaError::OverflowingOperation` Result.
432///
433/// * This panics if the current system time cannot be obtained.
434pub fn decode(data: &str, key: &[u8], ttl: u32) -> Result<Vec<u8>, BrancaError> {
435    // Extract timestamp & payload.
436    let (timestamp, buf_crypt) = decode_with_timestamp(data, key)?;
437
438    // TTL check to determine if the token has expired.
439    if ttl != 0 {
440        let future = match timestamp.checked_add(ttl) {
441            Some(value) => value as u64,
442            None => return Err(BrancaError::OverflowingOperation),
443        };
444
445        let now = SystemTime::now()
446            .duration_since(UNIX_EPOCH)
447            .expect("Failed to obtain timestamp from system clock.")
448            .as_secs();
449        if future < now {
450            return Err(BrancaError::ExpiredToken);
451        }
452    }
453
454    // Return the plaintext.
455    Ok(buf_crypt)
456}
457
458/// Decodes a Branca token and returns the timestamp and plaintext as a tuple.
459/// This is the underlying function used by `decode()` to extract the timestamp
460/// and the plaintext from the token.
461/// This function is useful if you want to extract the timestamp without checking the TTL (or implement your own TTL logic).
462/// 
463/// `data` - The input which is to be decrypted with the user-supplied key.
464/// 
465/// `key` - The user-supplied key to use for decryption.
466/// 
467/// Note:
468/// 
469/// * The key must be 32 bytes in length, otherwise it returns a `BrancaError::BadKeyLength` Result.
470/// 
471/// * If the decryption fails, it returns a `BrancaError::DecryptFailed` Result.
472/// 
473/// * If the input is not in Base62 format, it returns a `BrancaError::InvalidBase62Token` Result.
474pub fn decode_with_timestamp(
475    data: &str,
476    key: &[u8],
477) -> Result<(u32, Vec<u8>), BrancaError> {
478    let sk: SecretKey = match SecretKey::from_slice(key) {
479        Ok(key) => key,
480        Err(UnknownCryptoError) => return Err(BrancaError::BadKeyLength),
481    };
482
483    if data.len() < 61 {
484        return Err(BrancaError::InvalidBase62Token);
485    }
486
487    let decoded_data = match b62_decode(BASE62, data) {
488        Ok(decoded) => decoded,
489        Err(_) => return Err(BrancaError::InvalidBase62Token),
490    };
491
492    // After we have decoded the data, the branca token is now represented
493    // by the following layout below:
494
495    // Branca( header[0..29] + ciphertext[29..] )
496    // Version (&u8) || Timestamp (u32) || Nonce ([u8;24]) || Ciphertext (&[u8]) || Tag ([u8:16])
497
498    // We then obtain the header, ciphertext, version and timestamp with this layout.
499
500    // Check if there is a version mismatch.
501    if decoded_data[0] != VERSION {
502        return Err(BrancaError::InvalidTokenVersion);
503    }
504
505    let header = &decoded_data[0..29];
506    let n: Nonce = Nonce::from_slice(decoded_data[5..29].as_ref()).unwrap();
507    let mut buf_crypt = vec![0u8; decoded_data.len() - 16 - 29];
508
509    match open(
510        &sk,
511        &n,
512        decoded_data[29..].as_ref(),
513        Some(header),
514        &mut buf_crypt,
515    ) {
516        Ok(()) => (),
517        Err(orion::errors::UnknownCryptoError) => return Err(BrancaError::DecryptFailed),
518    };
519
520    let timestamp: u32 = BigEndian::read_u32(&decoded_data[1..5]);
521
522    Ok((timestamp, buf_crypt))
523}
524
525#[cfg(test)]
526mod unit_tests {
527
528    use super::*;
529
530    mod json_test_vectors {
531        use super::*;
532        use std::fs::File;
533        use std::io::BufReader;
534
535        #[allow(non_snake_case)]
536        #[derive(Serialize, Deserialize, Debug)]
537        struct TestFile {
538            version: String,
539            numberOfTests: u32,
540            testGroups: Vec<TestGroup>,
541        }
542
543        #[allow(non_snake_case)]
544        #[derive(Serialize, Deserialize, Debug)]
545        struct TestGroup {
546            testType: String,
547            tests: Vec<TestVector>,
548        }
549
550        #[allow(non_snake_case)]
551        #[derive(Serialize, Deserialize, Debug)]
552        struct TestVector {
553            id: u32,
554            comment: String,
555            key: String,
556            nonce: Option<String>,
557            timestamp: u32,
558            token: String,
559            msg: String,
560            isValid: bool,
561        }
562
563        fn parse_hex(data: &str) -> Vec<u8> {
564            match data {
565                "" => vec![0u8; 0],
566                "80" => b"\x80".to_vec(),
567                _ => hex::decode(data).unwrap(),
568            }
569        }
570
571        #[test]
572        pub fn run_test_vectors() {
573            let file = File::open("test_data/test_vectors.json").unwrap();
574            let reader = BufReader::new(file);
575            let tests: TestFile = serde_json::from_reader(reader).unwrap();
576
577            let mut tests_run = 0;
578            for test_group in tests.testGroups.iter() {
579                for test in test_group.tests.iter() {
580                    if test_group.testType == "encoding" {
581                        debug_assert!(test.nonce.is_some());
582
583                        if test.isValid {
584                            let nonce = Nonce::from_slice(&parse_hex(test.nonce.as_ref().unwrap()))
585                                .unwrap();
586
587                            let res = encode_with_nonce(
588                                &parse_hex(&test.msg),
589                                &parse_hex(&test.key),
590                                &nonce,
591                                test.timestamp,
592                            )
593                            .unwrap();
594
595                            assert_eq!(res, test.token);
596                            assert_eq!(
597                                decode(&test.token, &parse_hex(&test.key), 0).unwrap(),
598                                parse_hex(&test.msg)
599                            );
600
601                            tests_run += 1;
602                        }
603
604                        if !test.isValid {
605                            let nonce = Nonce::from_slice(&parse_hex(test.nonce.as_ref().unwrap()));
606
607                            if nonce.is_err() {
608                                tests_run += 1;
609                                continue;
610                            }
611
612                            let res = encode_with_nonce(
613                                &parse_hex(&test.msg),
614                                &parse_hex(&test.key),
615                                &nonce.unwrap(),
616                                test.timestamp,
617                            );
618
619                            assert!(res.is_err());
620                            tests_run += 1;
621                        }
622                    }
623
624                    if test_group.testType == "decoding" {
625                        debug_assert!(test.nonce.is_none());
626
627                        let res = decode(
628                            &test.token,
629                            &parse_hex(&test.key),
630                            0, // Not a part of test vectors
631                        );
632
633                        assert_eq!(test.isValid, res.is_ok());
634                        tests_run += 1;
635                    }
636                }
637            }
638
639            assert_eq!(tests_run, tests.numberOfTests);
640        }
641    }
642
643    #[derive(Serialize, Deserialize, Debug)]
644    struct JSONTest {
645        a: String,
646        b: bool,
647    }
648
649    #[test]
650    pub fn test_encode_builder() {
651        let key = b"supersecretkeyyoushouldnotcommit";
652        let mut token = Branca::new(key).unwrap();
653        let ciphertext = token.set_timestamp(123206400).encode(b"Test");
654        assert!(ciphertext.is_ok());
655    }
656
657    #[test]
658    pub fn test_decode() {
659        let ciphertext =
660            "875GH233T7IYrxtgXxlQBYiFobZMQdHAT51vChKsAIYCFxZtL1evV54vYqLyZtQ0ekPHt8kJHQp0a";
661        let key = b"supersecretkeyyoushouldnotcommit";
662        let ttl = 0;
663
664        assert_eq!(decode(ciphertext, key, ttl).unwrap(), b"Hello world!");
665    }
666
667    #[test]
668    pub fn test_encode_and_decode() {
669        let message = b"Hello world!";
670        let timestamp = 123206400;
671        let branca_token = encode(message, b"supersecretkeyyoushouldnotcommit", timestamp).unwrap();
672
673        assert_eq!(
674            decode(
675                branca_token.as_str(),
676                b"supersecretkeyyoushouldnotcommit",
677                0
678            )
679            .unwrap(),
680            b"Hello world!"
681        );
682    }
683
684    #[test]
685    pub fn test_encode_and_decode_random_nonce() {
686        let message = b"Hello world!";
687        let timestamp = 123206400;
688        let branca_token = encode(message, b"supersecretkeyyoushouldnotcommit", timestamp).unwrap();
689
690        assert_eq!(
691            decode(
692                branca_token.as_str(),
693                b"supersecretkeyyoushouldnotcommit",
694                0
695            )
696            .unwrap(),
697            b"Hello world!"
698        );
699    }
700
701    #[test]
702    pub fn test_encode_and_decode_json() {
703        let literal_json = json!({ "a": "some string", "b": false });
704        let message = literal_json.to_string();
705        let timestamp = 123206400;
706        let branca_token = encode(
707            message.as_bytes(),
708            b"supersecretkeyyoushouldnotcommit",
709            timestamp,
710        )
711        .unwrap();
712        let json = decode(
713            branca_token.as_str(),
714            b"supersecretkeyyoushouldnotcommit",
715            0,
716        )
717        .unwrap();
718        let serialized_json: JSONTest =
719            serde_json::from_str(&String::from_utf8_lossy(&json)).unwrap();
720
721        assert_eq!(serialized_json.a, "some string");
722        assert!(!serialized_json.b);
723    }
724
725    #[test]
726    pub fn test_encode_and_decode_json_literal() {
727        let message = r#"{
728                 "a":"some string",
729                 "b":false
730          }"#;
731        let timestamp = 123206400;
732        let branca_token = encode(
733            message.as_bytes(),
734            b"supersecretkeyyoushouldnotcommit",
735            timestamp,
736        )
737        .unwrap();
738        let json = decode(
739            branca_token.as_str(),
740            b"supersecretkeyyoushouldnotcommit",
741            0,
742        )
743        .unwrap();
744        let serialized_json: JSONTest =
745            serde_json::from_str(&String::from_utf8_lossy(&json)).unwrap();
746
747        assert_eq!(serialized_json.a, "some string");
748        assert!(!serialized_json.b);
749    }
750
751    #[test]
752    pub fn test_encode_and_decode_builder() {
753        let key = b"supersecretkeyyoushouldnotcommit";
754        let mut token = Branca::new(key).unwrap();
755        let ciphertext = token.encode(b"Test").unwrap();
756        let payload = token.decode(ciphertext.as_str(), 0).unwrap();
757
758        assert_eq!(payload, b"Test");
759    }
760
761    #[test]
762    pub fn test_encode_and_decode_builder_with_exp_ttl() {
763        let key = b"supersecretkeyyoushouldnotcommit";
764        let mut token = Branca::new(key).unwrap();
765        let ciphertext = token.set_timestamp(123206400).encode(b"Test").unwrap();
766        let payload = token.decode(ciphertext.as_str(), 0);
767
768        if let Err(e) = payload {
769            assert_eq!(e, BrancaError::ExpiredToken)
770        }
771    }
772
773    #[test]
774    pub fn test_expired_ttl() {
775        let ciphertext =
776            "875GH233T7IYrxtgXxlQBYiFobZMQdHAT51vChKsAIYCFxZtL1evV54vYqLyZtQ0ekPHt8kJHQp0a";
777        let key = b"supersecretkeyyoushouldnotcommit";
778        let ttl = 3600;
779        let message = decode(ciphertext, key, ttl);
780
781        if let Err(e) = message {
782            assert_eq!(e, BrancaError::ExpiredToken)
783        }
784    }
785
786    #[test]
787    pub fn test_decryption_fail() {
788        let ciphertext =
789            "875GH233T7IYrxtgXxlQBYiFobZMQdHAT51vChKsAIYCFxZtL1evV54vYqLyZtQ0ekPHt8kJHQp0a";
790        let key = b"supersecretkeyyoushouldnotcommi.";
791        let ttl = 0;
792        let branca_token = decode(ciphertext, key, ttl);
793
794        if let Err(e) = branca_token {
795            assert_eq!(e, BrancaError::DecryptFailed)
796        }
797    }
798
799    #[test]
800    pub fn test_base62_fail() {
801        let ciphertext = "875GH233T7IYrxtgXxlQBYiFo";
802        let key = b"supersecretkeyyoushouldnotcommit";
803        let ttl = 0;
804        let branca_token = decode(ciphertext, key, ttl);
805
806        if let Err(e) = branca_token {
807            assert_eq!(e, BrancaError::InvalidBase62Token)
808        }
809    }
810
811    #[test]
812    pub fn test_bad_key() {
813        let key = b"supersecretkey";
814        let message = b"Hello world!";
815        let timestamp = 123206400;
816        let branca_token = encode(message, key, timestamp);
817
818        if let Err(e) = branca_token {
819            assert_eq!(e, BrancaError::BadKeyLength)
820        }
821    }
822
823    #[test]
824    pub fn test_version_mismatch() {
825        let ciphertext =
826            "005GH233T7IYrxtgXxlQBYiFobZMQdHAT51vChKsAIYCFxZtL1evV54vYqLyZtQ0ekPHt8kJHQp0a";
827        let key = b"supersecretkeyyoushouldnotcommit";
828        let ttl = 0;
829        let branca_token = decode(ciphertext, key, ttl);
830
831        if let Err(e) = branca_token {
832            assert_eq!(e, BrancaError::InvalidTokenVersion)
833        }
834    }
835
836    #[test]
837    pub fn test_modified_timestamp_returns_bad_tag() {
838        let key = b"supersecretkeyyoushouldnotcommit";
839        let mut ctx = Branca::new(key).unwrap();
840        ctx.timestamp = 0; // Make sure current gets used.
841
842        let token = ctx.encode(b"Test").unwrap();
843        let mut decoded = b62_decode(BASE62, &token).unwrap();
844
845        // 651323084: Some day in 1990
846        BigEndian::write_u32(&mut decoded[1..5], 651323084);
847
848        assert_eq!(
849            decode(&b62_encode(BASE62, &decoded), key, 1000).unwrap_err(),
850            BrancaError::DecryptFailed
851        );
852    }
853
854    #[test]
855    pub fn test_no_panic_on_display() {
856        // to_string() should not panic.
857        // See: https://github.com/return/branca/issues/14
858        let _tostr = BrancaError::InvalidTokenVersion.to_string();
859    }
860
861    #[test]
862    pub fn test_empty_payload_encode_decode() {
863        let key = b"supersecretkeyyoushouldnotcommit";
864        let mut ctx = Branca::new(key).unwrap();
865        assert!(ctx.encode(b"").is_ok());
866
867        // Empty token cross-checked with pybranca
868        let decoded = ctx
869            .decode(
870                "4tGtt5wP5DCXzPhNbovMwEg9saksXSdmhvFbdrZrQjXEWf09BtuAK1wG5lpG0",
871                0,
872            )
873            .unwrap();
874        assert_eq!(b"", &decoded[..]);
875    }
876
877    #[test]
878    pub fn test_non_utf8_encode_decode() {
879        // See: https://github.com/return/branca/issues/10
880        let key = b"supersecretkeyyoushouldnotcommit";
881        let mut ctx = Branca::new(key).unwrap();
882        let own_token = ctx.encode(b"\x80").unwrap();
883        assert_eq!(b"\x80", &ctx.decode(own_token.as_str(), 0).unwrap()[..]);
884
885        let decoded = ctx
886            .decode(
887                "K9i9jp23WMENUOulBifHPEnfBp67LfQBE3wYBCPSCu2uTBEeFHwGJZfH8DOTa1",
888                0,
889            )
890            .unwrap();
891        assert_eq!(b"\x80", &decoded[..]);
892    }
893
894    #[test]
895    pub fn test_correct_err_on_invalid_base62() {
896        let key = b"supersecretkeyyoushouldnotcommit";
897        let mut token = encode(b"Hello world!", key, 0).unwrap();
898        token.push('_');
899
900        assert_eq!(
901            decode(&token, key, 0).unwrap_err(),
902            BrancaError::InvalidBase62Token
903        );
904    }
905
906    #[test]
907    pub fn test_builder_nonce_is_correctly_used() {
908        let key = b"supersecretkeyyoushouldnotcommit";
909        let mut ctx = Branca::new(key).unwrap();
910        assert!(ctx.nonce.is_empty());
911
912        let token = ctx.encode(b"").unwrap();
913        assert!(!ctx.nonce.is_empty());
914
915        // Ensure the builder's nonce is used
916        let raw_token = b62_decode(BASE62, &token).unwrap();
917        let raw_token_nonce = &raw_token[5..29];
918        assert_eq!(raw_token_nonce, &ctx.nonce[..]);
919
920        // Ensure a new nonce is generated when calling encode
921        let token_again = ctx.encode(b"").unwrap();
922        let raw_token_again = b62_decode(BASE62, &token_again).unwrap();
923        let raw_token_nonce_again = &raw_token_again[5..29];
924        assert_eq!(raw_token_nonce_again, &ctx.nonce[..]);
925        assert_ne!(raw_token_nonce_again, raw_token_nonce);
926    }
927
928    #[test]
929    pub fn test_error_on_overflowing_timestamp() {
930        let key = b"supersecretkeyyoushouldnotcommit";
931        let token = encode(b"", key, 4294967295).unwrap();
932
933        assert_eq!(
934            decode(&token, key, 1).unwrap_err(),
935            BrancaError::OverflowingOperation
936        );
937    }
938}