#[cfg(feature = "online-tokio")]
pub mod async_verifier;
pub mod config;
mod sec;
pub mod sync_verifier;
pub mod yubicoerror;
use std::collections::BTreeMap;
use base64::{decode, encode};
use config::Config;
use rand::distributions::Alphanumeric;
use rand::rngs::OsRng;
use rand::Rng;
use yubicoerror::YubicoError;
#[cfg(feature = "online-tokio")]
pub use async_verifier::verify_async;
pub use sync_verifier::verify;
type Result<T> = ::std::result::Result<T, YubicoError>;
#[derive(Clone)]
pub struct Request {
query: String,
response_verifier: ResponseVerifier,
}
impl Request {
fn build_url(&self, for_api_host: &str) -> String {
format!("{}?{}", for_api_host, self.query)
}
}
#[derive(Clone)]
pub struct ResponseVerifier {
otp: String,
nonce: String,
key: Vec<u8>,
}
impl ResponseVerifier {
fn verify_response(&self, raw_response: String) -> Result<()> {
let response_map: BTreeMap<String, String> = build_response_map(raw_response);
let status: &str = &*response_map.get("status").unwrap();
if let "OK" = status {
let signature_response: &str = &*response_map
.get("h")
.ok_or_else(|| YubicoError::InvalidResponse)?;
verify_signature(signature_response, response_map.clone(), &self.key)?;
let otp_response: &str = &*response_map
.get("otp")
.ok_or_else(|| YubicoError::InvalidResponse)?;
if !self.otp.eq(otp_response) {
return Err(YubicoError::OTPMismatch);
}
let nonce_response: &str = &*response_map
.get("nonce")
.ok_or_else(|| YubicoError::InvalidResponse)?;
if !self.nonce.eq(nonce_response) {
return Err(YubicoError::NonceMismatch);
}
Ok(())
} else {
match status {
"BAD_OTP" => Err(YubicoError::BadOTP),
"REPLAYED_OTP" => Err(YubicoError::ReplayedOTP),
"BAD_SIGNATURE" => Err(YubicoError::BadSignature),
"MISSING_PARAMETER" => Err(YubicoError::MissingParameter),
"NO_SUCH_CLIENT" => Err(YubicoError::NoSuchClient),
"OPERATION_NOT_ALLOWED" => Err(YubicoError::OperationNotAllowed),
"BACKEND_ERROR" => Err(YubicoError::BackendError),
"NOT_ENOUGH_ANSWERS" => Err(YubicoError::NotEnoughAnswers),
"REPLAYED_REQUEST" => Err(YubicoError::ReplayedRequest),
_ => Err(YubicoError::UnknownStatus),
}
}
}
}
fn build_request<S>(otp: S, config: &Config) -> Result<Request>
where
S: Into<String>,
{
let str_otp = otp.into();
let str_otp = str_otp.trim().to_string();
if printable_characters(&str_otp) {
let nonce: String = generate_nonce();
let mut query = form_urlencoded::Serializer::new(String::new());
query.append_pair("id", &config.client_id);
query.append_pair("nonce", &nonce);
query.append_pair("otp", &str_otp);
query.append_pair("sl", &config.sync_level.to_string());
let query = query.finish();
match sec::build_signature(&config.key, query.as_bytes()) {
Ok(signature) => {
let mut query = form_urlencoded::Serializer::new(query);
let encoded_signature = encode(&signature.into_bytes());
query.append_pair("h", &encoded_signature);
let verifier = ResponseVerifier {
otp: str_otp,
nonce,
key: config.key.clone(),
};
let request = Request {
query: query.finish(),
response_verifier: verifier,
};
Ok(request)
}
Err(error) => Err(error),
}
} else {
Err(YubicoError::BadOTP)
}
}
fn printable_characters(otp: &str) -> bool {
for c in otp.chars() {
if !c.is_ascii() {
return false;
}
}
otp.len() > 32 && otp.len() < 48
}
fn generate_nonce() -> String {
OsRng{}
.sample_iter(&Alphanumeric)
.map(char::from)
.take(40)
.collect()
}
fn verify_signature(
signature_response: &str,
mut response_map: BTreeMap<String, String>,
key: &[u8],
) -> Result<()> {
response_map.remove("h");
let mut query = String::new();
for (key, value) in response_map {
let param = format!("{}={}&", key, value);
query.push_str(param.as_ref());
}
query.pop();
let decoded_signature = &decode(signature_response).unwrap()[..];
sec::verify_signature(key, query.as_bytes(), decoded_signature)
}
fn build_response_map(result: String) -> BTreeMap<String, String> {
let mut parameters = BTreeMap::new();
for line in result.lines() {
let param: Vec<&str> = line.splitn(2, '=').collect();
if param.len() > 1 {
parameters.insert(param[0].to_string(), param[1].to_string());
}
}
parameters
}