From ed59adcc9280d937f0f6f3627104597681ce7347 Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Tue, 6 Oct 2020 22:38:10 -0700 Subject: Add otp-cache extension --- ext/otp-cache/src/ext.rs | 145 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 ext/otp-cache/src/ext.rs (limited to 'ext/otp-cache/src/ext.rs') diff --git a/ext/otp-cache/src/ext.rs b/ext/otp-cache/src/ext.rs new file mode 100644 index 0000000..9ed965b --- /dev/null +++ b/ext/otp-cache/src/ext.rs @@ -0,0 +1,145 @@ +// ext.rs + +// Copyright (C) 2020 The Nitrocli Developers +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::env; +use std::ffi; +use std::fmt; +use std::process; +use std::str; + +use anyhow::Context as _; + +pub struct Context { + /// The path to the nitrocli binary. + pub nitrocli: ffi::OsString, + /// The nitrokey model to use. + pub model: nitrokey::Model, + /// The verbosity level to use. + pub verbosity: u8, +} + +impl Context { + pub fn from_env() -> anyhow::Result { + let nitrocli = env::var_os("NITROCLI_BINARY") + .context("NITROCLI_BINARY environment variable not present") + .context("Failed to retrieve nitrocli path")?; + + let model = env::var_os("NITROCLI_MODEL") + .context("NITROCLI_MODEL environment variable not present") + .context("Failed to retrieve nitrocli model")?; + let model = model + .to_str() + .context("Provided model string is not valid UTF-8")?; + let model = match model { + "pro" => nitrokey::Model::Pro, + "storage" => nitrokey::Model::Storage, + _ => anyhow::bail!("Provided model is not valid: '{}'", model), + }; + + let verbosity = env::var_os("NITROCLI_VERBOSITY") + .context("NITROCLI_VERBOSITY environment variable not present") + .context("Failed to retrieve nitrocli verbosity")?; + let verbosity = verbosity + .to_str() + .context("Provided verbosity string is not valid UTF-8")?; + let verbosity = u8::from_str_radix(verbosity, 10).context("Failed to parse verbosity")?; + + Ok(Self { + nitrocli, + model, + verbosity, + }) + } +} + +#[derive(Debug)] +pub struct Nitrocli { + cmd: process::Command, +} + +impl Nitrocli { + pub fn from_context(ctx: &Context) -> Nitrocli { + Self { + cmd: process::Command::new(&ctx.nitrocli), + } + } + + pub fn arg(&mut self, arg: impl AsRef) -> &mut Nitrocli { + self.cmd.arg(arg); + self + } + + pub fn args(&mut self, args: I) -> &mut Nitrocli + where + I: IntoIterator, + S: AsRef, + { + self.cmd.args(args); + self + } + + pub fn text(&mut self) -> anyhow::Result { + let output = self.cmd.output().context("Failed to invoke nitrocli")?; + if output.status.success() { + String::from_utf8(output.stdout).map_err(From::from) + } else { + Err(anyhow::anyhow!( + "nitrocli call failed: {}", + String::from_utf8_lossy(&output.stderr) + )) + } + } +} + +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum OtpAlgorithm { + Hotp, + Totp, +} + +impl fmt::Display for OtpAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + OtpAlgorithm::Hotp => "hotp", + OtpAlgorithm::Totp => "totp", + } + ) + } +} + +impl str::FromStr for OtpAlgorithm { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "hotp" => Ok(OtpAlgorithm::Hotp), + "totp" => Ok(OtpAlgorithm::Totp), + _ => Err(anyhow::anyhow!("Unexpected OTP algorithm: {}", s)), + } + } +} + +impl<'de> serde::Deserialize<'de> for OtpAlgorithm { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error as _; + + str::FromStr::from_str(&String::deserialize(deserializer)?).map_err(D::Error::custom) + } +} + +impl serde::Serialize for OtpAlgorithm { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} -- cgit v1.2.3