otptool 0.1.0

Simple tool to help you migrate your OTP secrets from Google Authenticator to another app.
Documentation
mod internal {
    include!(concat!(env!("OUT_DIR"), "/migration.rs"));

    pub fn secret_to_string(secret: &[u8]) -> String {
        return data_encoding::BASE32_NOPAD.encode(secret);
    }
}

#[derive(Debug, Clone)]
pub struct OtpParameters {
    pub name: String,
    pub secret: String,
    pub issuer: String,
    pub algorithm: Algorithm,
    pub digits: DigitCount,
    pub counter: u64,
}

impl OtpParameters {
    pub fn from_base64(data: &[u8]) -> anyhow::Result<Vec<Self>> {
        let decoded_data = match data_encoding::BASE64.decode(data) {
            Ok(data) => data,
            Err(e) => return Err(anyhow::anyhow!(e)),
        };

        let migration_data: internal::Payload =
            match prost::Message::decode(decoded_data.as_slice()) {
                Ok(data) => data,
                Err(e) => return Err(anyhow::anyhow!(e)),
            };

        let mut otp_parameters = Vec::new();
        for op in migration_data.otp_parameters {
            otp_parameters.push(Self::from_proto(&op)?);
        }

        return Ok(otp_parameters);
    }

    fn from_proto(op: &internal::payload::OtpParameters) -> anyhow::Result<Self> {
        return Ok(OtpParameters {
            name: op.name.clone(),
            secret: internal::secret_to_string(&op.secret),
            issuer: op.issuer.clone(),
            algorithm: Algorithm::try_from(op.algorithm)?,
            digits: DigitCount::try_from(op.digits)?,
            counter: op.counter,
        });
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(i32)]
pub enum Algorithm {
    Unspecified = 0,
    Sha1 = 1,
    Sha256 = 2,
    Sha512 = 3,
    Md5 = 4,
}

impl TryFrom<i32> for Algorithm {
    type Error = anyhow::Error;

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(Algorithm::Unspecified),
            1 => Ok(Algorithm::Sha1),
            2 => Ok(Algorithm::Sha256),
            3 => Ok(Algorithm::Sha512),
            4 => Ok(Algorithm::Md5),
            _ => Err(anyhow::anyhow!("unknown algorithm")),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(i32)]
pub enum DigitCount {
    Unspecified = 0,
    Six = 1,
    Eight = 2,
}

impl TryFrom<i32> for DigitCount {
    type Error = anyhow::Error;

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(DigitCount::Unspecified),
            1 => Ok(DigitCount::Six),
            2 => Ok(DigitCount::Eight),
            _ => Err(anyhow::anyhow!("unknown digit count")),
        }
    }
}