use rand;
use base32;
use hmacsha1::hmac_sha1;
use std::mem;
use std::time::{SystemTime, UNIX_EPOCH};
use urlencoding;
#[cfg(any(feature = "with-qrcode"))]
use qrcode::{QrCode, Version, EcLevel};
#[cfg(any(feature = "with-qrcode"))]
use qrcode::render::svg;
const SECRET_MAX_LEN:usize = 128;
const SECRET_MIN_LEN:usize = 16;
pub struct GoogleAuthenticator{
code_len:usize,
_base32_alphabet:Vec<char>
}
impl GoogleAuthenticator{
pub fn new() -> GoogleAuthenticator{
GoogleAuthenticator{
code_len: 6,
_base32_alphabet: vec![
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '2', '3', '4', '5', '6', '7',
'=',
]
}
}
pub fn create_secret(&self, length:u8) -> String{
let mut secret = Vec::<char>::new();
let mut index:usize ;
for _ in 0 .. length {
index = (rand::random::<u8>() & 0x1F) as usize;
secret.push(self._base32_alphabet[ index ]);
}
secret.into_iter().collect()
}
pub fn get_code(&self, secret:&str, times_slice:i64) -> Result<String>{
if secret.len() < SECRET_MIN_LEN || secret.len() > SECRET_MAX_LEN {
return Err(GAError::Error("bad secret length. must be less than 128 and more than 16, recommand 32"));
}
let mut message: i64 = times_slice;
if times_slice == 0 {
message = (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64) / 30;
}
let key = self._base32_decode(secret)?;
let msg_bytes = message.to_be_bytes();
let hash = hmac_sha1(&key, &msg_bytes);
let offset = hash[hash.len() - 1] & 0x0F;
let mut truncated_hash:[u8;4] = Default::default();
truncated_hash.copy_from_slice(&hash[offset as usize .. (offset + 4) as usize]);
let mut code:i32 = unsafe { mem::transmute::<[u8;4], i32>(truncated_hash) };
if cfg!(target_endian = "big") {
} else {
code = i32::from_be(code);
}
code = code & 0x7FFFFFFF;
code = code % 1_000_000i32;
let mut code_str = code.to_string();
for i in 0 .. (self.code_len - code_str.len()) {
code_str.insert(i,'0');
}
Ok(code_str)
}
pub fn verify_code(&self, secret:&str, code: &str, discrepancy:i64, time_slice:i64) -> Result<bool>{
let mut curr_time_slice: i64 = time_slice;
if time_slice == 0 {
curr_time_slice = (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64) / 30 ;
}
if code.len() != self.code_len {
return Ok(false);
}
for _time_slice in curr_time_slice.wrapping_sub(discrepancy) .. curr_time_slice.wrapping_add(discrepancy + 1) {
println!("{:?}",_time_slice);
println!("{:?}",_time_slice);
if code.eq(self.get_code(secret, _time_slice)?.as_str()) {
return Ok(true);
}
}
Ok(false)
}
pub fn qr_code_url(&self, secret:&str, name:&str, title:&str, width:u16, height:u16, level:char) -> String {
let _width = if width == 0 {200} else {width};
let _height = if height == 0 {200} else {height};
let levels = vec!['L', 'M', 'Q', 'H'];
let _level = if levels.contains(&level) {level} else {'M'};
let scheme = urlencoding::encode(
format!("otpauth://totp/{}?secret={}&issuer={}"
,name
,secret
,urlencoding::encode(title)).as_str());
format!("https://chart.googleapis.com/chart?chs={}x{}&chld={}|0&cht=qr&chl={}", _width, _height, level, scheme)
}
#[cfg(any(feature = "with-qrcode"))]
pub fn qr_code(&self, secret:&str, name:&str, title:&str, width:u16, height:u16, level:char) -> Result<String>{
let _width = if width == 0 {200} else {width};
let _height = if height == 0 {200} else {height};
let levels = vec!['L', 'M', 'Q', 'H'];
let _level = match level {
'L' => EcLevel::L,
'H' => EcLevel::H,
_ => EcLevel::M
};
let scheme = format!("otpauth://totp/{}?secret={}&issuer={}", name, secret, title);
let code = QrCode::with_error_correction_level(scheme.as_bytes(), _level)?;
Ok(code.render()
.min_dimensions(_width as u32, _height as u32)
.dark_color(svg::Color("#000000"))
.light_color(svg::Color("#ffffff"))
.build())
}
fn _base32_decode(&self, secret:&str) -> Result<Vec<u8>>{
match base32::decode(base32::Alphabet::RFC4648 { padding: true }, secret) {
Some(_decode_str) => Ok(_decode_str),
_ => Err(GAError::Error("secret must can decode by base32."))
}
}
}
use std::error;
use std::result;
use std::fmt;
#[cfg(any(feature = "with-qrcode"))]
use qrcode::types::QrError;
#[derive(Debug)]
pub enum GAError {
Error(&'static str),
#[cfg(any(feature = "with-qrcode"))]
QrError(QrError)
}
impl error::Error for GAError{
fn description(&self) -> &str {
match *self {
GAError::Error(description) =>description,
#[cfg(any(feature = "with-qrcode"))]
GAError::QrError(ref err) => ""
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
#[cfg(any(feature = "with-qrcode"))]
GAError::QrError(ref err) => None,
GAError::Error(_) => None,
}
}
}
#[cfg(any(feature = "with-qrcode"))]
impl From<QrError> for GAError{
fn from(err:QrError) -> GAError {
GAError::QrError(err)
}
}
impl fmt::Display for GAError{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
GAError::Error(desc) => f.write_str(desc),
#[cfg(any(feature = "with-qrcode"))]
GAError::QrError(ref err) => fmt::Display::fmt(err, f),
}
}
}
type Result<T> = result::Result<T,GAError>;