Skip to content

Commit

Permalink
clean up api surface
Browse files Browse the repository at this point in the history
  • Loading branch information
jakeswenson committed Jul 26, 2020
1 parent 6040664 commit ae20c7c
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 32 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ path = "src/lib.rs"

[[bin]]
name = "otpcli"
path = "src/main.rs"
path = "src/otpcli/main.rs"

[features]
default = ["keychain", "copy"]
Expand All @@ -37,6 +37,11 @@ stoken = { version = "^0", optional = true }
keyring = { version = "^0", optional = true }
clipboard = { version = "^0", optional = true }

[dev-dependencies.cargo-husky]
version = "1.5.0"
default-features = false # Disable features which are enabled by default
features = ["precommit-hook", "prepush-hook", "run-cargo-fmt", "run-cargo-test", "run-cargo-clippy"]

[profile.release]
lto = true
opt-level = 'z' # Optimize for size.
Expand Down
18 changes: 16 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
use super::TokenAlgorithm;
use std::collections::HashMap;
use std::default::Default;
use std::io::Result as IoResult;
use std::path::{Path, PathBuf};

use crate::totp::TokenAlgorithm;
use crate::{TotpConfigError, TotpResult};
use serde::{self, Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Config {
pub totp: std::collections::HashMap<String, TotpOptions>,
totp: HashMap<String, TotpOptions>,
}

impl Config {
pub fn codes(&self) -> &HashMap<String, TotpOptions> {
&self.totp
}

pub fn insert(&mut self, name: String, options: TotpOptions) {
self.totp.insert(name, options);
}

pub fn remove(&mut self, name: &str) {
self.totp.remove(name);
}
}

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down
34 changes: 12 additions & 22 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use serde::{self, Deserialize, Serialize};

//! OTP — a one time password code generator library
use config::Config;

pub mod config;
Expand All @@ -11,6 +10,7 @@ use crate::config::TotpOptions;
#[cfg(feature = "rsa_stoken")]
use stoken::{self, chrono::Utc};

use crate::totp::TokenAlgorithm;
use std::iter::FromIterator;
use std::path::Path;
use std::{
Expand Down Expand Up @@ -49,17 +49,6 @@ impl Error for TotpConfigError {}

pub type TotpResult<T> = Result<T, Box<dyn Error>>;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum TokenAlgorithm {
#[serde(rename = "sha1")]
TotpSha1,
#[cfg(feature = "rsa_stoken")]
#[serde(rename = "stoken")]
SToken,
}

impl Copy for TokenAlgorithm {}

#[cfg(feature = "rsa_stoken")]
fn stoken(name: &str, options: &TotpOptions) -> TotpResult<String> {
let token = stoken::export::import(secrets::get_secret(name, options)?.to_string())
Expand Down Expand Up @@ -120,7 +109,7 @@ pub fn add_secret<P: AsRef<Path>>(
) -> TotpResult<Config> {
let totp_options = secrets::store_secret(&name, &secret, algorithm)?;
let mut config: Config = config.clone();
config.totp.insert(name.to_string(), totp_options);
config.insert(name.to_string(), totp_options);
let string = toml::to_string(&config)?;

config::ensure_config_dir(&config_dir)?;
Expand All @@ -130,15 +119,15 @@ pub fn add_secret<P: AsRef<Path>>(
}

pub fn list_secrets(config: Config, _prefix: Option<String>) -> TotpResult<Vec<String>> {
Ok(Vec::from_iter(config.totp.keys().cloned()))
Ok(Vec::from_iter(config.codes().keys().cloned()))
}

pub fn delete_secret<P: AsRef<Path>>(
mut config: Config,
config_dir: P,
name: String,
) -> TotpResult<()> {
config.totp.remove(&name);
config.remove(&name);
let string = toml::to_string(&config).expect("unable to write config to TOML");
config::ensure_config_dir(&config_dir)?;
std::fs::write(config_dir.as_ref().join("config.toml"), string)?;
Expand All @@ -147,20 +136,21 @@ pub fn delete_secret<P: AsRef<Path>>(

#[cfg(feature = "keychain")]
pub fn migrate_secrets_to_keychain<P: AsRef<Path>>(
mut config: Config,
config: Config,
config_dir: P,
) -> TotpResult<()> {
for (name, value) in config.clone().totp.iter() {
) -> TotpResult<Config> {
let mut new_codes = config.clone();
for (name, value) in config.codes().iter() {
println!("Migrating {}", name);
let secret = secrets::get_secret(name, value)?;
config = add_secret(
&config,
new_codes = add_secret(
&new_codes,
config_dir.as_ref(),
name,
secret,
value.algorithm(),
)?;
}

Ok(())
Ok(new_codes)
}
3 changes: 1 addition & 2 deletions src/cli.rs → src/otpcli/cli.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#[cfg(feature = "ras_stoken")]
use std::path::PathBuf;

use crate::cli::Command::GenerateToken;
use otp::{TotpError, TotpResult};
use structopt::StructOpt;

Expand Down Expand Up @@ -73,7 +72,7 @@ impl Options {
)));
}

Ok(self.cmd.clone().unwrap_or_else(|| GenerateToken {
Ok(self.cmd.clone().unwrap_or_else(|| Command::GenerateToken {
name: self.name.clone().unwrap(),
}))
}
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/secrets.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::config::{SecretLocation, TotpOptions};
use crate::{TokenAlgorithm, TotpError, TotpResult};
use crate::{totp::TokenAlgorithm, TotpError, TotpResult};
#[cfg(feature = "keychain")]
use keyring::Keyring;

Expand Down
40 changes: 36 additions & 4 deletions src/totp.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Provides RFC6238 compliant TOTP token generation.
use std::time::{Duration, SystemTime};

pub use crypto;
Expand All @@ -7,20 +8,47 @@ pub use crypto::sha1::Sha1;
use crate::config::TotpOptions;
use crate::{TotpError, TotpResult};

use serde::{Deserialize, Serialize};

use super::secrets;

static ALPHABET: base32::Alphabet = base32::Alphabet::RFC4648 { padding: false };

/// RFC6238 recommended time step duration
/// See: https://tools.ietf.org/html/rfc6238#section-5.2
/// [RFC6238-timestep]: https://tools.ietf.org/html/rfc6238#section-5.2
/// [RFC6238 recommended][RFC6238-timestep] time step duration of 30 seconds.
pub const RFC6238_RECOMMENDED_TIMESTEP: Duration = Duration::from_secs(30);

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum TokenAlgorithm {
#[serde(rename = "sha1")]
TotpSha1,
#[cfg(feature = "rsa_stoken")]
#[serde(rename = "stoken")]
SToken,
}

impl Copy for TokenAlgorithm {}

trait AsDigest {
fn new(&self) -> Box<dyn Digest>;
}

impl AsDigest for TokenAlgorithm {
fn new(&self) -> Box<dyn Digest> {
Box::new(match self {
TokenAlgorithm::TotpSha1 => Sha1::new(),
#[cfg(feature = "rsa_stoken")]
TokenAlgorithm::SToken => unreachable!("SToken cannot be used as a digest method"),
})
}
}

/// Runs a standard TOTP for the provided config, looking up secrets using []()
///
/// # Examples
/// ```rust
/// use otp::config::TotpOptions;
/// use otp::TokenAlgorithm;
/// use otp::totp::TokenAlgorithm;
/// use otp::totp::standard_totp;
/// let options = TotpOptions::new_config_stored_secret(
/// "A SECRET".to_string(),
Expand All @@ -36,10 +64,14 @@ pub const RFC6238_RECOMMENDED_TIMESTEP: Duration = Duration::from_secs(30);
/// ```
pub fn standard_totp(name: &str, options: &TotpOptions) -> TotpResult<String> {
let secret = secrets::get_secret(name, &options)?;

generate_sha1_code(secret)
}

/// Cleans a base32 secret by removing spaces and making sure it's upper-cased.
pub fn clean_secret(secret: &str) -> String {
secret.replace(" ", "").to_uppercase()
}

/// Generate a SHA1 TOTP code
///
/// # Examples
Expand Down

0 comments on commit ae20c7c

Please sign in to comment.