Commit 61fc895267f9c9ff8c290ef8027233e1ade8d426

evenorog 2020-08-05T13:24:34

Be generic over secret type Allow to store anything that implement AsRef<[u8]> as secret in TOTP struct.

diff --git a/README.md b/README.md
index 74f9bbd..689a66b 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ let totp = TOTP::new(
     6,
     1,
     30,
-    "supersecret".to_owned().into_bytes(),
+    "supersecret",
 );
 let time = SystemTime::now()
     .duration_since(SystemTime::UNIX_EPOCH).unwrap()
@@ -55,7 +55,7 @@ let totp = TOTP::new(
     6,
     1,
     30,
-    "supersecret".to_owned().into_bytes(),
+    "supersecret",
 );
 let code = totp.get_qr("user@example.com", "my-org.com")?;
 println!("{}", code);
diff --git a/src/lib.rs b/src/lib.rs
index b0334dc..8544fdd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11,7 +11,7 @@
 //!     6,
 //!     1,
 //!     30,
-//!     "supersecret".to_owned().into_bytes(),
+//!     "supersecret",
 //! );
 //! let time = SystemTime::now()
 //!     .duration_since(SystemTime::UNIX_EPOCH).unwrap()
@@ -30,7 +30,7 @@
 //!     6,
 //!     1,
 //!     30,
-//!     "supersecret".to_owned().into_bytes(),
+//!     "supersecret",
 //! );
 //! let code = totp.get_qr("user@example.com", "my-org.com").unwrap();
 //! println!("{}", code);
@@ -39,8 +39,6 @@
 #[cfg(feature = "serde_support")]
 use serde::{Deserialize, Serialize};
 
-use base32;
-
 use byteorder::{BigEndian, ReadBytesExt};
 use std::io::Cursor;
 
@@ -67,7 +65,7 @@ pub enum Algorithm {
 /// TOTP holds informations as to how to generate an auth code and validate it. Its [secret](struct.TOTP.html#structfield.secret) field is sensitive data, treat it accordingly
 #[derive(Debug, Clone)]
 #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
-pub struct TOTP {
+pub struct TOTP<T = Vec<u8>> {
     /// SHA-1 is the most widespread algorithm used, and for totp pursposes, SHA-1 hash collisions are [not a problem](https://tools.ietf.org/html/rfc4226#appendix-B.2) as HMAC-SHA-1 is not impacted. It's also the main one cited in [rfc-6238](https://tools.ietf.org/html/rfc6238#section-3) even though the [reference implementation](https://tools.ietf.org/html/rfc6238#appendix-A) permits the use of SHA-1, SHA-256 and SHA-512. Not all clients support other algorithms then SHA-1
     pub algorithm: Algorithm,
     /// The number of digits composing the auth code. Per [rfc-4226](https://tools.ietf.org/html/rfc4226#section-5.3), this can oscilate between 6 and 8 digits
@@ -77,18 +75,18 @@ pub struct TOTP {
     /// Duration in seconds of a step. The recommended value per [rfc-6238](https://tools.ietf.org/html/rfc6238#section-5.2) is 30 seconds
     pub step: u64,
     /// As per [rfc-4226](https://tools.ietf.org/html/rfc4226#section-4) the secret should come from a strong source, most likely a CSPRNG. It should be at least 128 bits, but 160 are recommended
-    pub secret: Vec<u8>,
+    pub secret: T,
 }
 
-impl TOTP {
+impl<T: AsRef<[u8]>> TOTP<T> {
     /// Will create a new instance of TOTP with given parameters. See [the doc](struct.TOTP.html#fields) for reference as to how to choose those values
-    pub fn new(algorithm: Algorithm, digits: usize, skew: u8, step: u64, secret: Vec<u8>) -> TOTP {
+    pub fn new(algorithm: Algorithm, digits: usize, skew: u8, step: u64, secret: T) -> TOTP<T> {
         TOTP {
-            algorithm: algorithm,
-            digits: digits,
-            skew: skew,
-            step: step,
-            secret: secret,
+            algorithm,
+            digits,
+            skew,
+            step,
+            secret,
         }
     }
 
@@ -97,17 +95,17 @@ impl TOTP {
         let ctr = (time / self.step).to_be_bytes().to_vec();
         match self.algorithm {
             Algorithm::SHA1 => {
-                let mut mac = HmacSha1::new_varkey(&self.secret).expect("no key");
+                let mut mac = HmacSha1::new_varkey(self.secret.as_ref()).expect("no key");
                 mac.update(&ctr);
                 mac.finalize().into_bytes().to_vec()
             }
             Algorithm::SHA256 => {
-                let mut mac = HmacSha256::new_varkey(&self.secret).expect("no key");
+                let mut mac = HmacSha256::new_varkey(self.secret.as_ref()).expect("no key");
                 mac.update(&ctr);
                 mac.finalize().into_bytes().to_vec()
             }
             Algorithm::SHA512 => {
-                let mut mac = HmacSha512::new_varkey(&self.secret).expect("no key");
+                let mut mac = HmacSha512::new_varkey(self.secret.as_ref()).expect("no key");
                 mac.update(&ctr);
                 mac.finalize().into_bytes().to_vec()
             }
@@ -117,8 +115,8 @@ impl TOTP {
     /// Will generate a token according to the provided timestamp in seconds
     pub fn generate(&self, time: u64) -> String {
         let result: &[u8] = &self.sign(time);
-        let offset = (result.as_ref()[19] & 15) as usize;
-        let mut rdr = Cursor::new(result.as_ref()[offset..offset + 4].to_vec());
+        let offset = (result[19] & 15) as usize;
+        let mut rdr = Cursor::new(result[offset..offset + 4].to_vec());
         let result = rdr.read_u32::<BigEndian>().unwrap() & 0x7fff_ffff;
         format!(
             "{1:00$}",
@@ -141,7 +139,10 @@ impl TOTP {
 
     /// Will return the base32 representation of the secret, which might be useful when users want to manually add the secret to their authenticator
     pub fn get_secret_base32(&self) -> String {
-        base32::encode(base32::Alphabet::RFC4648 { padding: false }, &self.secret)
+        base32::encode(
+            base32::Alphabet::RFC4648 { padding: false },
+            self.secret.as_ref(),
+        )
     }
 
     /// Will generate a standard URL used to automatically add TOTP auths. Usually used with qr codes
@@ -169,11 +170,7 @@ impl TOTP {
     ///
     /// It will also return an error in case it can't encode the qr into a png. This shouldn't happen unless either the qrcode library returns malformed data, or the image library doesn't encode the data correctly
     #[cfg(feature = "qr")]
-    pub fn get_qr(
-        &self,
-        label: &str,
-        issuer: &str,
-    ) -> Result<String, Box<dyn std::error::Error>> {
+    pub fn get_qr(&self, label: &str, issuer: &str) -> Result<String, Box<dyn std::error::Error>> {
         let url = self.get_url(label, issuer);
         let code = QrCode::new(&url)?;
         let mut vec = Vec::new();
@@ -195,57 +192,62 @@ mod tests {
 
     #[test]
     fn url_for_secret_matches() {
-        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
+        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
         let url = totp.get_url("test_url", "totp-rs");
         assert_eq!(url.as_str(), "otpauth://totp/test_url?secret=KRSXG5CTMVRXEZLU&issuer=totp-rs&digits=6&algorithm=SHA1");
     }
 
     #[test]
     fn returns_base32() {
-        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
+        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
         assert_eq!(totp.get_secret_base32().as_str(), "KRSXG5CTMVRXEZLU");
     }
 
     #[test]
     fn generates_token() {
-        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
+        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
         assert_eq!(totp.generate(1000).as_str(), "718996");
     }
 
     #[test]
     fn generates_token_sha256() {
-        let totp = TOTP::new(Algorithm::SHA256, 6, 1, 1, String::from("TestSecret").into_bytes());
+        let totp = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecret");
         assert_eq!(totp.generate(1000).as_str(), "423657");
     }
 
     #[test]
     fn generates_token_sha512() {
-        let totp = TOTP::new(Algorithm::SHA512, 6, 1, 1, String::from("TestSecret").into_bytes());
+        let totp = TOTP::new(Algorithm::SHA512, 6, 1, 1, "TestSecret");
         assert_eq!(totp.generate(1000).as_str(), "416767");
     }
 
     #[test]
     fn checks_token() {
-        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
+        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
         assert!(totp.check("718996", 1000));
     }
 
     #[test]
     fn checks_token_with_skew() {
-        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
-        assert!(totp.check("527544", 2000) && totp.check("712039", 2000) && totp.check("714250", 2000));
+        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
+        assert!(
+            totp.check("527544", 2000) && totp.check("712039", 2000) && totp.check("714250", 2000)
+        );
     }
 
     #[test]
     #[cfg(feature = "qr")]
     fn generates_qr() {
-        use sha1::{Sha1, Digest};
+        use sha1::{Digest, Sha1};
 
-        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
+        let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
         let qr = totp.get_qr("test_url", "totp-rs").unwrap();
 
         // Create hash from image
         let hash_digest = Sha1::digest(qr.as_bytes());
-        assert_eq!(format!("{:x}", hash_digest).as_str(), "3abc0127e7a2b1013fb25c97ef14422c1fe9e878");
+        assert_eq!(
+            format!("{:x}", hash_digest).as_str(),
+            "3abc0127e7a2b1013fb25c97ef14422c1fe9e878"
+        );
     }
 }