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}