[go: up one dir, main page]

textnonce 0.3.0

Text based random nonce generator
// Copyright 2015-2016 textnonce Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

use std::mem;
use std::ptr;
use std::fmt;
use rand::{OsRng,Rng};
use std::ops::Deref;
use rustc_serialize::base64::{self,ToBase64};

/// A nonce is a cryptographic concept of an arbitrary number that is never used more than once.
///
/// `TextNonce` is a nonce because the first 16 characters represents the current time, which
/// will never have been generated before, nor will it be generated again, across the period of
/// time in which Timespec is valid.
///
/// `TextNonce` additionally includes bytes of randomness, making it difficult to predict.
/// This makes it suitable to be used for session IDs.
///
/// It is also text-based, using only characters in the base64 character set.
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct TextNonce(pub String);

impl TextNonce {

    /// Generate a new `TextNonce` with 16 characters of time and 16 characters of
    /// randomness
    pub fn new() -> TextNonce {
        TextNonce::sized(32).unwrap()
    }

    /// Generate a new `TextNonce`. `length` must be at least 16, and divisible by 4.
    /// The first 16 characters come from the time component, and all characters
    /// after that will be random.
    pub fn sized(length: usize) -> Result<TextNonce,String> {
        TextNonce::sized_configured(length, base64::Config {
            char_set: base64::CharacterSet::Standard,
            newline: base64::Newline::LF,
            pad: false,
            line_length: None
        })
    }

    /// Generate a new `TextNonce` using the UrlSafe variant of base64 (using '_' and '-')
    /// `length` must be at least 16, and divisible by 4.  The first 16 characters come
    /// from the time component, and all characters after that will be random.
    pub fn sized_urlsafe(length: usize) ->  Result<TextNonce,String> {
        TextNonce::sized_configured(length, base64::Config {
            char_set: base64::CharacterSet::UrlSafe,
            newline: base64::Newline::LF,
            pad: false,
            line_length: None
        })
    }

    /// Generate a new `TextNonce` specifying the Base64 configuration to use.
    /// `length` must be at least 16, and divisible by 4.  The first 16 characters come
    /// from the time component, and all characters after that will be random.
    pub fn sized_configured(length: usize, config: base64::Config) -> Result<TextNonce,String> {
        if length<16 { return Err("length must be >= 16".to_owned()); }
        if length % 4 != 0 { return Err("length must be divisible by 4".to_owned()); }

        let bytelength: usize = (length / 4) * 3;

        let mut raw: Vec<u8> = Vec::with_capacity(bytelength);
        unsafe { raw.set_len(bytelength); }

        // Get the first 12 bytes from the current time
        // (Timespec is actually 16 due to alignment, but we will only use 12)
        // Big-endian and Little-endian machines will have these in different orders,
        // However this is not of any concern to us.
        let time = ::time::get_time();
        unsafe {
            let timep: *const u8 = mem::transmute(&time);
            ptr::copy_nonoverlapping(timep, raw.as_mut_ptr(), 12);
        }

        // Get the last bytes from random data
        match OsRng::new() {
            Ok(mut g) => g.fill_bytes(&mut raw[12..bytelength]),
            Err(_) => ::rand::thread_rng().fill_bytes(&mut raw[12..bytelength])
        };

        // base64 encode
        Ok(TextNonce(raw.to_base64( config )))
    }

    pub fn into_string(self) -> String {
        let TextNonce(s) = self;
        s
    }
}

impl fmt::Display for TextNonce {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

impl Deref for TextNonce {
    type Target = str;
    fn deref<'a>(&'a self) -> &'a str {
        &*self.0
    }
}

#[cfg(test)]
mod tests {
    use super::TextNonce;
    use std::collections::HashSet;

    #[test]
    fn new() {
        // Test 100 nonces:
        let mut map = HashSet::new();
        for _ in 0..100 {
            let n = TextNonce::new();
            let TextNonce(s) = n;

            // Verify their length
            assert_eq!(s.len(), 32);

            // Verify their character content
            assert_eq!( s.chars()
                        .filter(|x| { x.is_digit(10) || x.is_alphabetic() || *x=='+' || *x=='/' })
                        .count(),
                        32 );

            // Add to the map
            map.insert(s);
        }
        assert_eq!( map.len(), 100 );
    }

    #[test]
    fn sized() {
        let n = TextNonce::sized(48);
        assert!( n.is_ok() );
        let TextNonce(s) = n.unwrap();
        assert_eq!(s.len(), 48);

        let n = TextNonce::sized(47);
        assert!( n.is_err() );
        let n = TextNonce::sized(12);
        assert!( n.is_err() );
    }
}