Merge pull request #28 from constantoine/refinement
Make otpauth fields optionnal
This commit is contained in:
commit
5abd752db8
|
@ -21,8 +21,16 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Test
|
- name: All features
|
||||||
run: cargo test --all-features
|
run: cargo test --all-features
|
||||||
|
- name: No feature
|
||||||
|
run: cargo test
|
||||||
|
- name: otpauth feature
|
||||||
|
run: cargo test --features=otpauth
|
||||||
|
- name: gen_secret feature
|
||||||
|
run: cargo test --features=gen_secret
|
||||||
|
- name: otpauth+gensecret feature
|
||||||
|
run: cargo test --features=gen_secret,otpauth
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "totp-rs"
|
name = "totp-rs"
|
||||||
version = "2.0.1"
|
version = "3.0.0"
|
||||||
authors = ["Cleo Rebert <cleo.rebert@gmail.com>"]
|
authors = ["Cleo Rebert <cleo.rebert@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -33,4 +33,4 @@ constant_time_eq = "~0.2.1"
|
||||||
qrcodegen = { version = "~1.8", optional = true }
|
qrcodegen = { version = "~1.8", optional = true }
|
||||||
image = { version = "~0.24.2", features = ["png"], optional = true, default-features = false}
|
image = { version = "~0.24.2", features = ["png"], optional = true, default-features = false}
|
||||||
base64 = { version = "~0.13", optional = true }
|
base64 = { version = "~0.13", optional = true }
|
||||||
rand = { version = "~0.8.5", optional = true }
|
rand = { version = "~0.8.5", features = ["std_rng", "std"], optional = true, default-features = false }
|
168
README.md
168
README.md
|
@ -10,23 +10,48 @@ Be aware that some authenticator apps will accept the `SHA256` and `SHA512` algo
|
||||||
## Features
|
## Features
|
||||||
---
|
---
|
||||||
### qr
|
### qr
|
||||||
With optional feature "qr", you can use it to generate a base64 png qrcode. This will enable feature `otpauth`
|
With optional feature "qr", you can use it to generate a base64 png qrcode. This will enable feature `otpauth`.
|
||||||
### otpauth
|
### otpauth
|
||||||
With optional feature "otpauth", support parsing the TOTP parameters from an `otpauth` URL, and generating an `otpauth` URL
|
With optional feature "otpauth", support parsing the TOTP parameters from an `otpauth` URL, and generating an `otpauth` URL. It adds 2 fields to `TOTP`.
|
||||||
### serde_support
|
### serde_support
|
||||||
With optional feature "serde_support", library-defined types `TOTP` and `Algorithm` and will be Deserialize-able and Serialize-able
|
With optional feature "serde_support", library-defined types `TOTP` and `Algorithm` and will be Deserialize-able and Serialize-able.
|
||||||
|
### gen_secret
|
||||||
|
With optional feature "gen_secret", a secret will be generated for you to store in database.
|
||||||
|
|
||||||
## How to use
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## Summarry
|
||||||
|
|
||||||
|
0. [Understanding Secret](#understanding-secret)
|
||||||
|
1. [Generate a token](#generate-a-token)
|
||||||
|
2. [Enable qrcode generation](#with-qrcode-generation)
|
||||||
|
3. [Enable serde support](#with-serde-support)
|
||||||
|
4. [Enable otpauth url support](#with-otpauth-url-support)
|
||||||
|
5. [Enable gen_secret support](#with-gensecret)
|
||||||
|
6. [With RFC-6238 compliant default](#with-rfc-6238-compliant-default)
|
||||||
|
|
||||||
|
### Understanding Secret
|
||||||
|
---
|
||||||
|
This new type was added as a disambiguation between Raw and already base32 encoded secrets.
|
||||||
|
```Rust
|
||||||
|
Secret::Raw("TestSecretSuperSecret".as_bytes().to_vec())
|
||||||
|
```
|
||||||
|
Is equivalent to
|
||||||
|
```Rust
|
||||||
|
Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string())
|
||||||
|
```
|
||||||
|
### Generate a token
|
||||||
---
|
---
|
||||||
Add it to your `Cargo.toml`:
|
Add it to your `Cargo.toml`:
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
totp-rs = "^2.0"
|
totp-rs = "^3.0"
|
||||||
```
|
```
|
||||||
You can then do something like:
|
You can then do something like:
|
||||||
```Rust
|
```Rust
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use totp_rs::{Algorithm, TOTP};
|
use totp_rs::{Algorithm, TOTP, Secret};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let totp = TOTP::new(
|
let totp = TOTP::new(
|
||||||
|
@ -34,26 +59,16 @@ fn main() {
|
||||||
6,
|
6,
|
||||||
1,
|
1,
|
||||||
30,
|
30,
|
||||||
"supersecret",
|
Secret::Raw("TestSecretSuperSecret".as_bytes().to_vec()).to_bytes().unwrap(),
|
||||||
Some("Github".to_string()),
|
|
||||||
"constantoine@github.com".to_string(),
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let token = totp.generate_current().unwrap();
|
let token = totp.generate_current().unwrap();
|
||||||
println!("{}", token);
|
println!("{}", token);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
Which is equivalent to:
|
||||||
### With qrcode generation
|
|
||||||
|
|
||||||
Add it to your `Cargo.toml`:
|
|
||||||
```toml
|
|
||||||
[dependencies.totp-rs]
|
|
||||||
version = "^2.0"
|
|
||||||
features = ["qr"]
|
|
||||||
```
|
|
||||||
You can then do something like:
|
|
||||||
```Rust
|
```Rust
|
||||||
use totp_rs::{Algorithm, TOTP};
|
use std::time::SystemTime;
|
||||||
|
use totp_rs::{Algorithm, TOTP, Secret};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let totp = TOTP::new(
|
let totp = TOTP::new(
|
||||||
|
@ -61,29 +76,54 @@ fn main() {
|
||||||
6,
|
6,
|
||||||
1,
|
1,
|
||||||
30,
|
30,
|
||||||
"supersecret",
|
Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string()).to_bytes().unwrap(),
|
||||||
|
).unwrap();
|
||||||
|
let token = totp.generate_current().unwrap();
|
||||||
|
println!("{}", token);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### With qrcode generation
|
||||||
|
---
|
||||||
|
Add it to your `Cargo.toml`:
|
||||||
|
```toml
|
||||||
|
[dependencies.totp-rs]
|
||||||
|
version = "^3.0"
|
||||||
|
features = ["qr"]
|
||||||
|
```
|
||||||
|
You can then do something like:
|
||||||
|
```Rust
|
||||||
|
use totp_rs::{Algorithm, TOTP, Secret};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let totp = TOTP::new(
|
||||||
|
Algorithm::SHA1,
|
||||||
|
6,
|
||||||
|
1,
|
||||||
|
30,
|
||||||
|
Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string()).to_bytes().unwrap(),
|
||||||
Some("Github".to_string()),
|
Some("Github".to_string()),
|
||||||
"constantoine@github.com".to_string(),
|
"constantoine@github.com".to_string(),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let code = totp.get_qr("user@example.com", "my-org.com")?;
|
let code = totp.get_qr()?;
|
||||||
println!("{}", code);
|
println!("{}", code);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### With serde support
|
### With serde support
|
||||||
|
---
|
||||||
Add it to your `Cargo.toml`:
|
Add it to your `Cargo.toml`:
|
||||||
```toml
|
```toml
|
||||||
[dependencies.totp-rs]
|
[dependencies.totp-rs]
|
||||||
version = "^2.0"
|
version = "^3.0"
|
||||||
features = ["serde_support"]
|
features = ["serde_support"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### With otpauth url support
|
### With otpauth url support
|
||||||
|
---
|
||||||
Add it to your `Cargo.toml`:
|
Add it to your `Cargo.toml`:
|
||||||
```toml
|
```toml
|
||||||
[dependencies.totp-rs]
|
[dependencies.totp-rs]
|
||||||
version = "^2.0"
|
version = "^3.0"
|
||||||
features = ["otpauth"]
|
features = ["otpauth"]
|
||||||
```
|
```
|
||||||
You can then do something like:
|
You can then do something like:
|
||||||
|
@ -91,8 +131,84 @@ You can then do something like:
|
||||||
use totp_rs::TOTP;
|
use totp_rs::TOTP;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let otpauth = "otpauth://totp/GitHub:constantoine@github.com?secret=ABC&issuer=GitHub";
|
let otpauth = "otpauth://totp/GitHub:constantoine@github.com?secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&issuer=GitHub";
|
||||||
let totp = TOTP::from_url(otpauth).unwrap();
|
let totp = TOTP::from_url(otpauth).unwrap();
|
||||||
println!("{}", totp.generate_current().unwrap());
|
println!("{}", totp.generate_current().unwrap());
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### With gen_secret
|
||||||
|
---
|
||||||
|
Add it to your `Cargo.toml`:
|
||||||
|
```toml
|
||||||
|
[dependencies.totp-rs]
|
||||||
|
version = "^3.0"
|
||||||
|
features = ["gen_secret"]
|
||||||
|
```
|
||||||
|
You can then do something like:
|
||||||
|
```Rust
|
||||||
|
use totp_rs::{Algorithm, TOTP, Secret};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let totp = TOTP::new(
|
||||||
|
Algorithm::SHA1,
|
||||||
|
6,
|
||||||
|
1,
|
||||||
|
30,
|
||||||
|
Secret::default().to_bytes().unwrap(),
|
||||||
|
Some("Github".to_string()),
|
||||||
|
"constantoine@github.com".to_string(),
|
||||||
|
).unwrap();
|
||||||
|
let code = totp.get_qr()?;
|
||||||
|
println!("{}", code);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Which is equivalent to
|
||||||
|
```Rust
|
||||||
|
use totp_rs::{Algorithm, TOTP, Secret};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let totp = TOTP::new(
|
||||||
|
Algorithm::SHA1,
|
||||||
|
6,
|
||||||
|
1,
|
||||||
|
30,
|
||||||
|
Secret::generate_secret().to_bytes().unwrap(),
|
||||||
|
Some("Github".to_string()),
|
||||||
|
"constantoine@github.com".to_string(),
|
||||||
|
).unwrap();
|
||||||
|
let code = totp.get_qr()?;
|
||||||
|
println!("{}", code);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### With RFC-6238 compliant default
|
||||||
|
---
|
||||||
|
You can do something like this
|
||||||
|
```Rust
|
||||||
|
use totp_rs::{Algorithm, TOTP, Secret, Rfc6238};
|
||||||
|
|
||||||
|
fn main () {
|
||||||
|
let mut rfc = Rfc6238::with_defaults(
|
||||||
|
Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string()).to_bytes().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// optional, set digits
|
||||||
|
rfc.digits(8).unwrap();
|
||||||
|
|
||||||
|
// create a TOTP from rfc
|
||||||
|
let totp = TOTP::from_rfc6238(rfc).unwrap();
|
||||||
|
let code = totp.generate_current().unwrap();
|
||||||
|
println!("code: {}", code);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
With `gen_secret` feature, you can go even further and have all values by default and a secure secret.
|
||||||
|
|
||||||
|
Note: With `otpauth` feature, `TOTP.issuer` will be `None`, and `TOTP.account_name` will be `""`. Be sure to set those fields before generating an URL/QRCode
|
||||||
|
```Rust
|
||||||
|
fn main() {
|
||||||
|
let totp = TOTP::default();
|
||||||
|
println!("code: {}", code);
|
||||||
|
}
|
||||||
```
|
```
|
|
@ -1,8 +1,7 @@
|
||||||
#[cfg(not(feature = "gen_secret"))]
|
#[cfg(all(feature = "gen_secret", feature = "otpauth"))]
|
||||||
compile_error!("requires feature gen_secret");
|
|
||||||
|
|
||||||
use totp_rs::{Secret, TOTP, Algorithm};
|
use totp_rs::{Secret, TOTP, Algorithm};
|
||||||
|
|
||||||
|
#[cfg(all(feature = "gen_secret", feature = "otpauth"))]
|
||||||
fn main () {
|
fn main () {
|
||||||
|
|
||||||
let secret = Secret::generate_secret();
|
let secret = Secret::generate_secret();
|
||||||
|
@ -24,3 +23,6 @@ fn main () {
|
||||||
totp.generate_current().unwrap()
|
totp.generate_current().unwrap()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(all(feature = "gen_secret", feature = "otpauth")))]
|
||||||
|
fn main () {}
|
|
@ -1,5 +1,6 @@
|
||||||
use totp_rs::{Rfc6238, TOTP};
|
use totp_rs::{Rfc6238, TOTP};
|
||||||
|
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn main () {
|
fn main () {
|
||||||
let mut rfc = Rfc6238::with_defaults(
|
let mut rfc = Rfc6238::with_defaults(
|
||||||
"totp-sercret-123"
|
"totp-sercret-123"
|
||||||
|
@ -15,3 +16,18 @@ fn main () {
|
||||||
let code = totp.generate_current().unwrap();
|
let code = totp.generate_current().unwrap();
|
||||||
println!("code: {}", code);
|
println!("code: {}", code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
|
fn main () {
|
||||||
|
let mut rfc = Rfc6238::with_defaults(
|
||||||
|
"totp-sercret-123"
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// optional, set digits, issuer, account_name
|
||||||
|
rfc.digits(8).unwrap();
|
||||||
|
|
||||||
|
// create a TOTP from rfc
|
||||||
|
let totp = TOTP::from_rfc6238(rfc).unwrap();
|
||||||
|
let code = totp.generate_current().unwrap();
|
||||||
|
println!("code: {}", code);
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use totp_rs::{Secret, TOTP, Algorithm};
|
use totp_rs::{Secret, TOTP, Algorithm};
|
||||||
|
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn main () {
|
fn main () {
|
||||||
// create TOTP from base32 secret
|
// create TOTP from base32 secret
|
||||||
let secret_b32 = Secret::Encoded(String::from("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG"));
|
let secret_b32 = Secret::Encoded(String::from("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG"));
|
||||||
|
@ -9,8 +10,8 @@ fn main () {
|
||||||
1,
|
1,
|
||||||
30,
|
30,
|
||||||
secret_b32.to_bytes().unwrap(),
|
secret_b32.to_bytes().unwrap(),
|
||||||
None,
|
Some("issuer".to_string()),
|
||||||
"account".to_string(),
|
"user-account".to_string(),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
println!("base32 {} ; raw {}", secret_b32, secret_b32.to_raw().unwrap());
|
println!("base32 {} ; raw {}", secret_b32, secret_b32.to_raw().unwrap());
|
||||||
|
@ -28,10 +29,43 @@ fn main () {
|
||||||
1,
|
1,
|
||||||
30,
|
30,
|
||||||
secret_raw.to_bytes().unwrap(),
|
secret_raw.to_bytes().unwrap(),
|
||||||
None,
|
Some("issuer".to_string()),
|
||||||
"account".to_string(),
|
"user-account".to_string(),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
println!("raw {} ; base32 {}", secret_raw, secret_raw.to_encoded());
|
println!("raw {} ; base32 {}", secret_raw, secret_raw.to_encoded());
|
||||||
println!("code from raw secret:\t{}", totp_raw.generate_current().unwrap());
|
println!("code from raw secret:\t{}", totp_raw.generate_current().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
|
fn main () {
|
||||||
|
// create TOTP from base32 secret
|
||||||
|
let secret_b32 = Secret::Encoded(String::from("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG"));
|
||||||
|
let totp_b32 = TOTP::new(
|
||||||
|
Algorithm::SHA1,
|
||||||
|
6,
|
||||||
|
1,
|
||||||
|
30,
|
||||||
|
secret_b32.to_bytes().unwrap(),
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
println!("base32 {} ; raw {}", secret_b32, secret_b32.to_raw().unwrap());
|
||||||
|
println!("code from base32:\t{}", totp_b32.generate_current().unwrap());
|
||||||
|
|
||||||
|
// create TOTP from raw binary value
|
||||||
|
let secret = [
|
||||||
|
0x70, 0x6c, 0x61, 0x69, 0x6e, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x73, 0x65,
|
||||||
|
0x63, 0x72, 0x65, 0x74, 0x2d, 0x31, 0x32, 0x33,
|
||||||
|
];
|
||||||
|
let secret_raw = Secret::Raw(secret.to_vec());
|
||||||
|
let totp_raw = TOTP::new(
|
||||||
|
Algorithm::SHA1,
|
||||||
|
6,
|
||||||
|
1,
|
||||||
|
30,
|
||||||
|
secret_raw.to_bytes().unwrap(),
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
println!("raw {} ; base32 {}", secret_raw, secret_raw.to_encoded());
|
||||||
|
println!("code from raw secret:\t{}", totp_raw.generate_current().unwrap());
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use totp_rs::{Algorithm, TOTP};
|
use totp_rs::{Algorithm, TOTP};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn main() {
|
fn main() {
|
||||||
let totp = TOTP::new(
|
let totp = TOTP::new(
|
||||||
Algorithm::SHA1,
|
Algorithm::SHA1,
|
||||||
|
@ -7,8 +8,30 @@ fn main() {
|
||||||
1,
|
1,
|
||||||
30,
|
30,
|
||||||
"my-secret".to_string(),
|
"my-secret".to_string(),
|
||||||
None,
|
).unwrap();
|
||||||
"account".to_string(),
|
|
||||||
|
loop {
|
||||||
|
println!(
|
||||||
|
"code {}\t ttl {}\t valid until: {}",
|
||||||
|
totp.generate_current().unwrap(),
|
||||||
|
totp.ttl().unwrap(),
|
||||||
|
totp.next_step_current().unwrap()
|
||||||
|
);
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
|
fn main() {
|
||||||
|
let totp = TOTP::new(
|
||||||
|
Algorithm::SHA1,
|
||||||
|
6,
|
||||||
|
1,
|
||||||
|
30,
|
||||||
|
"my-secret".to_string(),
|
||||||
|
Some("Github".to_string()),
|
||||||
|
"constantoine@github.com".to_string()
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
124
src/lib.rs
124
src/lib.rs
|
@ -9,20 +9,22 @@
|
||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
|
//! # #[cfg(feature = "otpauth")] {
|
||||||
//! use std::time::SystemTime;
|
//! use std::time::SystemTime;
|
||||||
//! use totp_rs::{Algorithm, TOTP};
|
//! use totp_rs::{Algorithm, TOTP, Secret};
|
||||||
//!
|
//!
|
||||||
//! let totp = TOTP::new(
|
//! let totp = TOTP::new(
|
||||||
//! Algorithm::SHA1,
|
//! Algorithm::SHA1,
|
||||||
//! 6,
|
//! 6,
|
||||||
//! 1,
|
//! 1,
|
||||||
//! 30,
|
//! 30,
|
||||||
//! "supersecret_topsecret",
|
//! Secret::Raw("TestSecretSuperSecret".as_bytes().to_vec()).to_bytes().unwrap(),
|
||||||
//! Some("Github".to_string()),
|
//! Some("Github".to_string()),
|
||||||
//! "constantoine@github.com".to_string(),
|
//! "constantoine@github.com".to_string(),
|
||||||
//! ).unwrap();
|
//! ).unwrap();
|
||||||
//! let token = totp.generate_current().unwrap();
|
//! let token = totp.generate_current().unwrap();
|
||||||
//! println!("{}", token);
|
//! println!("{}", token);
|
||||||
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
|
@ -142,10 +144,12 @@ pub struct TOTP<T = Vec<u8>> {
|
||||||
///
|
///
|
||||||
/// non-encoded value
|
/// non-encoded value
|
||||||
pub secret: T,
|
pub secret: T,
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
/// The "Github" part of "Github:constantoine@github.com". Must not contain a colon `:`
|
/// The "Github" part of "Github:constantoine@github.com". Must not contain a colon `:`
|
||||||
/// For example, the name of your service/website.
|
/// For example, the name of your service/website.
|
||||||
/// Not mandatory, but strongly recommended!
|
/// Not mandatory, but strongly recommended!
|
||||||
pub issuer: Option<String>,
|
pub issuer: Option<String>,
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
/// The "constantoine@github.com" part of "Github:constantoine@github.com". Must not contain a colon `:`
|
/// The "constantoine@github.com" part of "Github:constantoine@github.com". Must not contain a colon `:`
|
||||||
/// For example, the name of your user's account.
|
/// For example, the name of your user's account.
|
||||||
pub account_name: String
|
pub account_name: String
|
||||||
|
@ -171,7 +175,23 @@ impl <T: AsRef<[u8]>> PartialEq for TOTP<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "gen_secret", not(feature = "otpauth")))]
|
||||||
|
impl Default for TOTP {
|
||||||
|
fn default() -> Self {
|
||||||
|
return TOTP::new(Algorithm::SHA1, 6, 1, 30, Secret::generate_secret().to_bytes().unwrap()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "gen_secret", feature = "otpauth"))]
|
||||||
|
impl Default for TOTP {
|
||||||
|
fn default() -> Self {
|
||||||
|
return TOTP::new(Algorithm::SHA1, 6, 1, 30, Secret::generate_secret().to_bytes().unwrap(), None, "".to_string()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: AsRef<[u8]>> TOTP<T> {
|
impl<T: AsRef<[u8]>> TOTP<T> {
|
||||||
|
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
/// 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
|
/// 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
|
||||||
///
|
///
|
||||||
/// # Description
|
/// # Description
|
||||||
|
@ -210,6 +230,35 @@ impl<T: AsRef<[u8]>> TOTP<T> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
/// # Description
|
||||||
|
/// * `secret`: expect a non-encoded value, to pass in base32 string use `Secret::Encoded(String)`
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use totp_rs::{Secret, TOTP, Algorithm};
|
||||||
|
/// let secret = Secret::Encoded("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG".to_string());
|
||||||
|
/// let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, secret.to_bytes().unwrap()).unwrap();
|
||||||
|
/// ```
|
||||||
|
/// * `digits`: MUST be between 6 & 8
|
||||||
|
/// * `secret`: Must have bitsize of at least 128
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return an error in case issuer or label contain the character ':'
|
||||||
|
pub fn new(algorithm: Algorithm, digits: usize,skew: u8, step: u64, secret: T) -> Result<TOTP<T>, TotpUrlError> {
|
||||||
|
crate::rfc::assert_digits(&digits)?;
|
||||||
|
crate::rfc::assert_secret_length(secret.as_ref())?;
|
||||||
|
Ok(TOTP {
|
||||||
|
algorithm,
|
||||||
|
digits,
|
||||||
|
skew,
|
||||||
|
step,
|
||||||
|
secret,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Will create a new instance of TOTP from the given [Rfc6238](struct.Rfc6238.html) struct
|
/// Will create a new instance of TOTP from the given [Rfc6238](struct.Rfc6238.html) struct
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
@ -462,6 +511,17 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "gen_secret")]
|
||||||
|
fn default_values() {
|
||||||
|
let totp = TOTP::default();
|
||||||
|
assert_eq!(totp.algorithm, Algorithm::SHA1);
|
||||||
|
assert_eq!(totp.digits, 6);
|
||||||
|
assert_eq!(totp.skew, 1);
|
||||||
|
assert_eq!(totp.step, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn new_wrong_issuer() {
|
fn new_wrong_issuer() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github:".to_string()), "constantoine@github.com".to_string());
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github:".to_string()), "constantoine@github.com".to_string());
|
||||||
assert!(totp.is_err());
|
assert!(totp.is_err());
|
||||||
|
@ -469,6 +529,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn new_wrong_account_name() {
|
fn new_wrong_account_name() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine:github.com".to_string());
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine:github.com".to_string());
|
||||||
assert!(totp.is_err());
|
assert!(totp.is_err());
|
||||||
|
@ -476,6 +537,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn new_wrong_account_name_no_issuer() {
|
fn new_wrong_account_name_no_issuer() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", None, "constantoine:github.com".to_string());
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", None, "constantoine:github.com".to_string());
|
||||||
assert!(totp.is_err());
|
assert!(totp.is_err());
|
||||||
|
@ -483,6 +545,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn comparison_ok() {
|
fn comparison_ok() {
|
||||||
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
||||||
let test = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let test = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
||||||
|
@ -490,37 +553,42 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn comparison_different_algo() {
|
fn comparison_different_algo() {
|
||||||
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
let test = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let test = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
assert_ne!(reference, test);
|
assert_ne!(reference, test);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn comparison_different_digits() {
|
fn comparison_different_digits() {
|
||||||
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
let test = TOTP::new(Algorithm::SHA1, 8, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let test = TOTP::new(Algorithm::SHA1, 8, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
assert_ne!(reference, test);
|
assert_ne!(reference, test);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn comparison_different_skew() {
|
fn comparison_different_skew() {
|
||||||
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
let test = TOTP::new(Algorithm::SHA1, 6, 0, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let test = TOTP::new(Algorithm::SHA1, 6, 0, 1, "TestSecretSuperSecret").unwrap();
|
||||||
assert_ne!(reference, test);
|
assert_ne!(reference, test);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn comparison_different_step() {
|
fn comparison_different_step() {
|
||||||
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
let test = TOTP::new(Algorithm::SHA1, 6, 1, 30, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let test = TOTP::new(Algorithm::SHA1, 6, 1, 30, "TestSecretSuperSecret").unwrap();
|
||||||
assert_ne!(reference, test);
|
assert_ne!(reference, test);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn comparison_different_secret() {
|
fn comparison_different_secret() {
|
||||||
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
let test = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretDifferentSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let test = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretDifferentSecret").unwrap();
|
||||||
assert_ne!(reference, test);
|
assert_ne!(reference, test);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,20 +625,23 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn returns_base32() {
|
fn returns_base32() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
assert_eq!(totp.get_secret_base32().as_str(), "KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ");
|
assert_eq!(totp.get_secret_base32().as_str(), "KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn generate_token() {
|
fn generate_token() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
assert_eq!(totp.generate(1000).as_str(), "659761");
|
assert_eq!(totp.generate(1000).as_str(), "659761");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn generate_token_current() {
|
fn generate_token_current() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
let time = SystemTime::now()
|
let time = SystemTime::now()
|
||||||
.duration_since(SystemTime::UNIX_EPOCH).unwrap()
|
.duration_since(SystemTime::UNIX_EPOCH).unwrap()
|
||||||
.as_secs();
|
.as_secs();
|
||||||
|
@ -578,49 +649,56 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn generates_token_sha256() {
|
fn generates_token_sha256() {
|
||||||
let totp = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let totp = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
assert_eq!(totp.generate(1000).as_str(), "076417");
|
assert_eq!(totp.generate(1000).as_str(), "076417");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn generates_token_sha512() {
|
fn generates_token_sha512() {
|
||||||
let totp = TOTP::new(Algorithm::SHA512, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let totp = TOTP::new(Algorithm::SHA512, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
assert_eq!(totp.generate(1000).as_str(), "473536");
|
assert_eq!(totp.generate(1000).as_str(), "473536");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn checks_token() {
|
fn checks_token() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 0, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let totp = TOTP::new(Algorithm::SHA1, 6, 0, 1, "TestSecretSuperSecret").unwrap();
|
||||||
assert!(totp.check("659761", 1000));
|
assert!(totp.check("659761", 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn checks_token_current() {
|
fn checks_token_current() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 0, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let totp = TOTP::new(Algorithm::SHA1, 6, 0, 1, "TestSecretSuperSecret").unwrap();
|
||||||
assert!(totp.check_current(&totp.generate_current().unwrap()).unwrap());
|
assert!(totp.check_current(&totp.generate_current().unwrap()).unwrap());
|
||||||
assert!(!totp.check_current("bogus").unwrap());
|
assert!(!totp.check_current("bogus").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn checks_token_with_skew() {
|
fn checks_token_with_skew() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
totp.check("174269", 1000) && totp.check("659761", 1000) && totp.check("260393", 1000)
|
totp.check("174269", 1000) && totp.check("659761", 1000) && totp.check("260393", 1000)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn next_step() {
|
fn next_step() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, "TestSecretSuperSecret").unwrap();
|
||||||
assert!(totp.next_step(0) == 30);
|
assert!(totp.next_step(0) == 30);
|
||||||
assert!(totp.next_step(29) == 30);
|
assert!(totp.next_step(29) == 30);
|
||||||
assert!(totp.next_step(30) == 60);
|
assert!(totp.next_step(30) == 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn next_step_current() {
|
fn next_step_current() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, "TestSecretSuperSecret").unwrap();
|
||||||
let t = system_time().unwrap();
|
let t = system_time().unwrap();
|
||||||
assert!(totp.next_step_current().unwrap() == totp.next_step(t));
|
assert!(totp.next_step_current().unwrap() == totp.next_step(t));
|
||||||
}
|
}
|
||||||
|
@ -689,7 +767,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "otpauth")]
|
#[cfg(feature = "otpauth")]
|
||||||
fn from_url_query_different_issuers() {
|
fn from_url_query_different_issuers() {
|
||||||
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?issuer=Gitlab&secret=TestSecretSuperSecret&digits=8&period=60&algorithm=SHA256");
|
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?issuer=Gitlab&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=8&period=60&algorithm=SHA256");
|
||||||
assert!(totp.is_err());
|
assert!(totp.is_err());
|
||||||
assert!(matches!(totp.unwrap_err(), TotpUrlError::IssuerMistmatch(_, _)));
|
assert!(matches!(totp.unwrap_err(), TotpUrlError::IssuerMistmatch(_, _)));
|
||||||
}
|
}
|
||||||
|
|
113
src/rfc.rs
113
src/rfc.rs
|
@ -59,8 +59,6 @@ pub fn assert_secret_length(secret: &[u8]) -> Result<(), Rfc6238Error> {
|
||||||
///
|
///
|
||||||
/// // optional, set digits, issuer, account_name
|
/// // optional, set digits, issuer, account_name
|
||||||
/// rfc.digits(8).unwrap();
|
/// rfc.digits(8).unwrap();
|
||||||
/// rfc.issuer("issuer".to_string());
|
|
||||||
/// rfc.account_name("user-account".to_string());
|
|
||||||
///
|
///
|
||||||
/// let totp = TOTP::from_rfc6238(rfc).unwrap();
|
/// let totp = TOTP::from_rfc6238(rfc).unwrap();
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -77,10 +75,12 @@ pub struct Rfc6238<T = Vec<u8>> {
|
||||||
step: u64,
|
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
|
/// 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
|
||||||
secret: T,
|
secret: T,
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
/// The "Github" part of "Github:constantoine@github.com". Must not contain a colon `:`
|
/// The "Github" part of "Github:constantoine@github.com". Must not contain a colon `:`
|
||||||
/// For example, the name of your service/website.
|
/// For example, the name of your service/website.
|
||||||
/// Not mandatory, but strongly recommended!
|
/// Not mandatory, but strongly recommended!
|
||||||
issuer: Option<String>,
|
issuer: Option<String>,
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
/// The "constantoine@github.com" part of "Github:constantoine@github.com". Must not contain a colon `:`
|
/// The "constantoine@github.com" part of "Github:constantoine@github.com". Must not contain a colon `:`
|
||||||
/// For example, the name of your user's account.
|
/// For example, the name of your user's account.
|
||||||
account_name: String,
|
account_name: String,
|
||||||
|
@ -94,6 +94,7 @@ impl<T: AsRef<[u8]>> Rfc6238<T> {
|
||||||
/// will return a [Rfc6238Error](enum.Rfc6238Error.html) when
|
/// will return a [Rfc6238Error](enum.Rfc6238Error.html) when
|
||||||
/// - `digits` is lower than 6 or higher than 8
|
/// - `digits` is lower than 6 or higher than 8
|
||||||
/// - `secret` is smaller than 128 bits (16 characters)
|
/// - `secret` is smaller than 128 bits (16 characters)
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
digits: usize,
|
digits: usize,
|
||||||
secret: T,
|
secret: T,
|
||||||
|
@ -113,6 +114,22 @@ impl<T: AsRef<[u8]>> Rfc6238<T> {
|
||||||
account_name,
|
account_name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
|
pub fn new(
|
||||||
|
digits: usize,
|
||||||
|
secret: T,
|
||||||
|
) -> Result<Rfc6238<T>, Rfc6238Error> {
|
||||||
|
assert_digits(&digits)?;
|
||||||
|
assert_secret_length(secret.as_ref())?;
|
||||||
|
|
||||||
|
Ok(Rfc6238 {
|
||||||
|
algorithm: Algorithm::SHA1,
|
||||||
|
digits,
|
||||||
|
skew: 1,
|
||||||
|
step: 30,
|
||||||
|
secret,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Create an [rfc-6238](https://tools.ietf.org/html/rfc6238) compliant set of options that can be turned into a [TOTP](struct.TOTP.html),
|
/// Create an [rfc-6238](https://tools.ietf.org/html/rfc6238) compliant set of options that can be turned into a [TOTP](struct.TOTP.html),
|
||||||
/// with a default value of 6 for `digits`, None `issuer` and an empty account
|
/// with a default value of 6 for `digits`, None `issuer` and an empty account
|
||||||
|
@ -122,8 +139,14 @@ impl<T: AsRef<[u8]>> Rfc6238<T> {
|
||||||
/// will return a [Rfc6238Error](enum.Rfc6238Error.html) when
|
/// will return a [Rfc6238Error](enum.Rfc6238Error.html) when
|
||||||
/// - `digits` is lower than 6 or higher than 8
|
/// - `digits` is lower than 6 or higher than 8
|
||||||
/// - `secret` is smaller than 128 bits (16 characters)
|
/// - `secret` is smaller than 128 bits (16 characters)
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
pub fn with_defaults(secret: T) -> Result<Rfc6238<T>, Rfc6238Error> {
|
pub fn with_defaults(secret: T) -> Result<Rfc6238<T>, Rfc6238Error> {
|
||||||
Rfc6238::new(6, secret, None, "".to_string())
|
Rfc6238::new(6, secret, Some("".to_string()), "".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
|
pub fn with_defaults(secret: T) -> Result<Rfc6238<T>, Rfc6238Error> {
|
||||||
|
Rfc6238::new(6, secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the `digits`
|
/// Set the `digits`
|
||||||
|
@ -133,17 +156,36 @@ impl<T: AsRef<[u8]>> Rfc6238<T> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
/// Set the `issuer`
|
/// Set the `issuer`
|
||||||
pub fn issuer(&mut self, value: String) {
|
pub fn issuer(&mut self, value: String) {
|
||||||
self.issuer = Some(value);
|
self.issuer = Some(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seet the `account_name`
|
#[cfg(feature = "otpauth")]
|
||||||
|
/// Set the `account_name`
|
||||||
pub fn account_name(&mut self, value: String) {
|
pub fn account_name(&mut self, value: String) {
|
||||||
self.account_name = value;
|
self.account_name = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
|
impl<T: AsRef<[u8]>> TryFrom<Rfc6238<T>> for TOTP<T> {
|
||||||
|
type Error = TotpUrlError;
|
||||||
|
|
||||||
|
/// Try to create a [TOTP](struct.TOTP.html) from a [Rfc6238](struct.Rfc6238.html) config
|
||||||
|
fn try_from(rfc: Rfc6238<T>) -> Result<Self, Self::Error> {
|
||||||
|
TOTP::new(
|
||||||
|
rfc.algorithm,
|
||||||
|
rfc.digits,
|
||||||
|
rfc.skew,
|
||||||
|
rfc.step,
|
||||||
|
rfc.secret,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
impl<T: AsRef<[u8]>> TryFrom<Rfc6238<T>> for TOTP<T> {
|
impl<T: AsRef<[u8]>> TryFrom<Rfc6238<T>> for TOTP<T> {
|
||||||
type Error = TotpUrlError;
|
type Error = TotpUrlError;
|
||||||
|
|
||||||
|
@ -156,30 +198,39 @@ impl<T: AsRef<[u8]>> TryFrom<Rfc6238<T>> for TOTP<T> {
|
||||||
rfc.step,
|
rfc.step,
|
||||||
rfc.secret,
|
rfc.secret,
|
||||||
rfc.issuer,
|
rfc.issuer,
|
||||||
rfc.account_name,
|
rfc.account_name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
use crate::TotpUrlError;
|
use crate::TotpUrlError;
|
||||||
|
|
||||||
use super::{Rfc6238, Rfc6238Error, TOTP};
|
use super::{Rfc6238, TOTP};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
|
use super::Rfc6238Error;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
|
use crate::Secret;
|
||||||
|
|
||||||
const GOOD_SECRET: &str = "01234567890123456789";
|
const GOOD_SECRET: &str = "01234567890123456789";
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
const ISSUER: Option<&str> = None;
|
const ISSUER: Option<&str> = None;
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
const ACCOUNT: &str = "valid-account";
|
const ACCOUNT: &str = "valid-account";
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
const INVALID_ACCOUNT: &str = ":invalid-account";
|
const INVALID_ACCOUNT: &str = ":invalid-account";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn new_rfc_digits() {
|
fn new_rfc_digits() {
|
||||||
for x in 0..=20 {
|
for x in 0..=20 {
|
||||||
let rfc = Rfc6238::new(
|
let rfc = Rfc6238::new(
|
||||||
x,
|
x,
|
||||||
GOOD_SECRET.to_string(),
|
GOOD_SECRET.to_string(),
|
||||||
ISSUER.map(str::to_string),
|
|
||||||
ACCOUNT.to_string(),
|
|
||||||
);
|
);
|
||||||
if !(6..=8).contains(&x) {
|
if !(6..=8).contains(&x) {
|
||||||
assert!(rfc.is_err());
|
assert!(rfc.is_err());
|
||||||
|
@ -191,6 +242,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn new_rfc_secret() {
|
fn new_rfc_secret() {
|
||||||
let mut secret = String::from("");
|
let mut secret = String::from("");
|
||||||
for _ in 0..=20 {
|
for _ in 0..=20 {
|
||||||
|
@ -198,8 +250,6 @@ mod tests {
|
||||||
let rfc = Rfc6238::new(
|
let rfc = Rfc6238::new(
|
||||||
6,
|
6,
|
||||||
secret.clone(),
|
secret.clone(),
|
||||||
ISSUER.map(str::to_string),
|
|
||||||
ACCOUNT.to_string(),
|
|
||||||
);
|
);
|
||||||
let rfc_default = Rfc6238::with_defaults(secret.clone());
|
let rfc_default = Rfc6238::with_defaults(secret.clone());
|
||||||
if secret.len() < 16 {
|
if secret.len() < 16 {
|
||||||
|
@ -215,12 +265,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn rfc_to_totp_ok() {
|
fn rfc_to_totp_ok() {
|
||||||
let rfc = Rfc6238::new(
|
let rfc = Rfc6238::new(
|
||||||
8,
|
8,
|
||||||
GOOD_SECRET.to_string(),
|
GOOD_SECRET.to_string(),
|
||||||
ISSUER.map(str::to_string),
|
|
||||||
ACCOUNT.to_string(),
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let totp = TOTP::try_from(rfc);
|
let totp = TOTP::try_from(rfc);
|
||||||
|
@ -228,14 +277,29 @@ mod tests {
|
||||||
let otp = totp.unwrap();
|
let otp = totp.unwrap();
|
||||||
assert_eq!(&otp.secret, GOOD_SECRET);
|
assert_eq!(&otp.secret, GOOD_SECRET);
|
||||||
assert_eq!(otp.algorithm, crate::Algorithm::SHA1);
|
assert_eq!(otp.algorithm, crate::Algorithm::SHA1);
|
||||||
assert_eq!(&otp.account_name, ACCOUNT);
|
|
||||||
assert_eq!(otp.digits, 8);
|
assert_eq!(otp.digits, 8);
|
||||||
assert_eq!(otp.issuer, ISSUER.map(str::to_string));
|
|
||||||
assert_eq!(otp.skew, 1);
|
assert_eq!(otp.skew, 1);
|
||||||
assert_eq!(otp.step, 30)
|
assert_eq!(otp.step, 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
|
fn rfc_to_totp_ok_2() {
|
||||||
|
let rfc = Rfc6238::with_defaults(
|
||||||
|
Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string()).to_bytes().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let totp = TOTP::try_from(rfc);
|
||||||
|
assert!(totp.is_ok());
|
||||||
|
let otp = totp.unwrap();
|
||||||
|
assert_eq!(otp.algorithm, crate::Algorithm::SHA1);
|
||||||
|
assert_eq!(otp.digits, 6);
|
||||||
|
assert_eq!(otp.skew, 1);
|
||||||
|
assert_eq!(otp.step, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn rfc_to_totp_fail() {
|
fn rfc_to_totp_fail() {
|
||||||
let rfc = Rfc6238::new(
|
let rfc = Rfc6238::new(
|
||||||
8,
|
8,
|
||||||
|
@ -250,14 +314,23 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
|
fn rfc_to_totp_ok() {
|
||||||
|
let rfc = Rfc6238::new(
|
||||||
|
8,
|
||||||
|
GOOD_SECRET.to_string(),
|
||||||
|
ISSUER.map(str::to_string),
|
||||||
|
ACCOUNT.to_string(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let totp = TOTP::try_from(rfc);
|
||||||
|
assert!(!totp.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(feature = "otpauth"))]
|
||||||
fn rfc_with_default_set_values() {
|
fn rfc_with_default_set_values() {
|
||||||
let new_account = "new-account";
|
|
||||||
let new_issuer = String::from("new-issuer");
|
|
||||||
let mut rfc = Rfc6238::with_defaults(GOOD_SECRET.to_string()).unwrap();
|
let mut rfc = Rfc6238::with_defaults(GOOD_SECRET.to_string()).unwrap();
|
||||||
rfc.issuer(new_issuer.clone());
|
|
||||||
assert_eq!(rfc.issuer, Some(new_issuer));
|
|
||||||
rfc.account_name(new_account.to_string());
|
|
||||||
assert_eq!(rfc.account_name, new_account.to_string());
|
|
||||||
let fail = rfc.digits(4);
|
let fail = rfc.digits(4);
|
||||||
assert!(fail.is_err());
|
assert!(fail.is_err());
|
||||||
assert!(matches!(fail.unwrap_err(), Rfc6238Error::InvalidDigits(_)));
|
assert!(matches!(fail.unwrap_err(), Rfc6238Error::InvalidDigits(_)));
|
||||||
|
|
145
src/secret.rs
145
src/secret.rs
|
@ -1,3 +1,82 @@
|
||||||
|
//! Representation of a secret either a "raw" \[u8\] or "base 32" encoded String
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! - Create a TOTP from a "raw" secret
|
||||||
|
//! ```
|
||||||
|
//! # #[cfg(not(feature = "otpauth"))] {
|
||||||
|
//! use totp_rs::{Secret, TOTP, Algorithm};
|
||||||
|
//!
|
||||||
|
//! let secret = [
|
||||||
|
//! 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x73, 0x65,
|
||||||
|
//! 0x63, 0x72, 0x65, 0x74, 0x2d, 0x31, 0x32, 0x33,
|
||||||
|
//! ];
|
||||||
|
//! let secret_raw = Secret::Raw(secret.to_vec());
|
||||||
|
//! let totp_raw = TOTP::new(
|
||||||
|
//! Algorithm::SHA1,
|
||||||
|
//! 6,
|
||||||
|
//! 1,
|
||||||
|
//! 30,
|
||||||
|
//! secret_raw.to_bytes().unwrap(),
|
||||||
|
//! ).unwrap();
|
||||||
|
//!
|
||||||
|
//! println!("code from raw secret:\t{}", totp_raw.generate_current().unwrap());
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! - Create a TOTP from a base32 encoded secret
|
||||||
|
//! ```
|
||||||
|
//! # #[cfg(not(feature = "otpauth"))] {
|
||||||
|
//! use totp_rs::{Secret, TOTP, Algorithm};
|
||||||
|
//!
|
||||||
|
//! let secret_b32 = Secret::Encoded(String::from("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG"));
|
||||||
|
//! let totp_b32 = TOTP::new(
|
||||||
|
//! Algorithm::SHA1,
|
||||||
|
//! 6,
|
||||||
|
//! 1,
|
||||||
|
//! 30,
|
||||||
|
//! secret_b32.to_bytes().unwrap(),
|
||||||
|
//! ).unwrap();
|
||||||
|
//!
|
||||||
|
//! println!("code from base32:\t{}", totp_b32.generate_current().unwrap());
|
||||||
|
//! # }
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! - Create a TOTP from a Generated Secret
|
||||||
|
//! ```
|
||||||
|
//! # #[cfg(all(feature = "gen_secret", not(feature = "otpauth")))] {
|
||||||
|
//! use totp_rs::{Secret, TOTP, Algorithm};
|
||||||
|
//!
|
||||||
|
//! let secret_b32 = Secret::default();
|
||||||
|
//! let totp_b32 = TOTP::new(
|
||||||
|
//! Algorithm::SHA1,
|
||||||
|
//! 6,
|
||||||
|
//! 1,
|
||||||
|
//! 30,
|
||||||
|
//! secret_b32.to_bytes().unwrap(),
|
||||||
|
//! ).unwrap();
|
||||||
|
//!
|
||||||
|
//! println!("code from base32:\t{}", totp_b32.generate_current().unwrap());
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//! - Create a TOTP from a Generated Secret 2
|
||||||
|
//! ```
|
||||||
|
//! # #[cfg(all(feature = "gen_secret", not(feature = "otpauth")))] {
|
||||||
|
//! use totp_rs::{Secret, TOTP, Algorithm};
|
||||||
|
//!
|
||||||
|
//! let secret_b32 = Secret::generate_secret();
|
||||||
|
//! let totp_b32 = TOTP::new(
|
||||||
|
//! Algorithm::SHA1,
|
||||||
|
//! 6,
|
||||||
|
//! 1,
|
||||||
|
//! 30,
|
||||||
|
//! secret_b32.to_bytes().unwrap(),
|
||||||
|
//! ).unwrap();
|
||||||
|
//!
|
||||||
|
//! println!("code from base32:\t{}", totp_b32.generate_current().unwrap());
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
use base32::{self, Alphabet};
|
use base32::{self, Alphabet};
|
||||||
|
|
||||||
|
@ -7,49 +86,6 @@ pub enum SecretParseError {
|
||||||
Utf8Error(FromUtf8Error),
|
Utf8Error(FromUtf8Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of a secret either a "raw" \[u8\] or "base 32" encoded String
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// - Create a TOTP from a "raw" secret
|
|
||||||
/// ```
|
|
||||||
/// use totp_rs::{Secret, TOTP, Algorithm};
|
|
||||||
///
|
|
||||||
/// let secret = [
|
|
||||||
/// 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x73, 0x65,
|
|
||||||
/// 0x63, 0x72, 0x65, 0x74, 0x2d, 0x31, 0x32, 0x33,
|
|
||||||
/// ];
|
|
||||||
/// let secret_raw = Secret::Raw(secret.to_vec());
|
|
||||||
/// let totp_raw = TOTP::new(
|
|
||||||
/// Algorithm::SHA1,
|
|
||||||
/// 6,
|
|
||||||
/// 1,
|
|
||||||
/// 30,
|
|
||||||
/// secret_raw.to_bytes().unwrap(),
|
|
||||||
/// None,
|
|
||||||
/// "account".to_string(),
|
|
||||||
/// ).unwrap();
|
|
||||||
///
|
|
||||||
/// println!("code from raw secret:\t{}", totp_raw.generate_current().unwrap());
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// - Create a TOTP from a base32 encoded secret
|
|
||||||
/// ```
|
|
||||||
/// use totp_rs::{Secret, TOTP, Algorithm};
|
|
||||||
///
|
|
||||||
/// let secret_b32 = Secret::Encoded(String::from("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG"));
|
|
||||||
/// let totp_b32 = TOTP::new(
|
|
||||||
/// Algorithm::SHA1,
|
|
||||||
/// 6,
|
|
||||||
/// 1,
|
|
||||||
/// 30,
|
|
||||||
/// secret_b32.to_bytes().unwrap(),
|
|
||||||
/// None,
|
|
||||||
/// "account".to_string(),
|
|
||||||
/// ).unwrap();
|
|
||||||
///
|
|
||||||
/// println!("code from base32:\t{}", totp_b32.generate_current().unwrap());
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Secret {
|
pub enum Secret {
|
||||||
/// represent a non-encoded "raw" secret
|
/// represent a non-encoded "raw" secret
|
||||||
|
@ -58,6 +94,13 @@ pub enum Secret {
|
||||||
Encoded(String),
|
Encoded(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gen_secret")]
|
||||||
|
impl Default for Secret {
|
||||||
|
fn default() -> Self {
|
||||||
|
return Secret::generate_secret()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Secret {
|
impl Secret {
|
||||||
|
|
||||||
/// Get the inner String value as a Vec of bytes
|
/// Get the inner String value as a Vec of bytes
|
||||||
|
@ -87,7 +130,7 @@ impl Secret {
|
||||||
match self {
|
match self {
|
||||||
Secret::Raw(s) => Secret::Encoded(base32::encode(
|
Secret::Raw(s) => Secret::Encoded(base32::encode(
|
||||||
Alphabet::RFC4648 { padding: false },
|
Alphabet::RFC4648 { padding: false },
|
||||||
&s,
|
s,
|
||||||
)),
|
)),
|
||||||
Secret::Encoded(_) => self.clone(),
|
Secret::Encoded(_) => self.clone(),
|
||||||
}
|
}
|
||||||
|
@ -108,7 +151,7 @@ impl Secret {
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let mut secret: [u8; 20] = Default::default();
|
let mut secret: [u8; 20] = Default::default();
|
||||||
rng.fill(&mut secret);
|
rng.fill(&mut secret[..]);
|
||||||
Secret::Raw(secret.to_vec())
|
Secret::Raw(secret.to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +186,7 @@ mod tests {
|
||||||
fn secret_display() {
|
fn secret_display() {
|
||||||
let base32_str = String::from(BASE32);
|
let base32_str = String::from(BASE32);
|
||||||
let secret_raw = Secret::Raw(BYTES.to_vec());
|
let secret_raw = Secret::Raw(BYTES.to_vec());
|
||||||
let secret_base32 = Secret::Encoded(base32_str.clone());
|
let secret_base32 = Secret::Encoded(base32_str);
|
||||||
println!("{}", secret_raw);
|
println!("{}", secret_raw);
|
||||||
assert_eq!(secret_raw.to_string(), BYTES_DISPLAY.to_string());
|
assert_eq!(secret_raw.to_string(), BYTES_DISPLAY.to_string());
|
||||||
assert_eq!(secret_base32.to_string(), BASE32.to_string());
|
assert_eq!(secret_base32.to_string(), BASE32.to_string());
|
||||||
|
@ -153,7 +196,7 @@ mod tests {
|
||||||
fn secret_convert_base32_raw() {
|
fn secret_convert_base32_raw() {
|
||||||
let base32_str = String::from(BASE32);
|
let base32_str = String::from(BASE32);
|
||||||
let secret_raw = Secret::Raw(BYTES.to_vec());
|
let secret_raw = Secret::Raw(BYTES.to_vec());
|
||||||
let secret_base32 = Secret::Encoded(base32_str.clone());
|
let secret_base32 = Secret::Encoded(base32_str);
|
||||||
|
|
||||||
assert_eq!(&secret_raw.to_encoded(), &secret_base32);
|
assert_eq!(&secret_raw.to_encoded(), &secret_base32);
|
||||||
assert_eq!(&secret_raw.to_raw().unwrap(), &secret_raw);
|
assert_eq!(&secret_raw.to_raw().unwrap(), &secret_raw);
|
||||||
|
@ -169,6 +212,14 @@ mod tests {
|
||||||
assert_eq!(Secret::Encoded(base32_str).to_bytes().unwrap(), BYTES.to_vec());
|
assert_eq!(Secret::Encoded(base32_str).to_bytes().unwrap(), BYTES.to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_from_string() {
|
||||||
|
let raw: Secret = Secret::Raw("TestSecretSuperSecret".as_bytes().to_vec());
|
||||||
|
let encoded: Secret = Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string());
|
||||||
|
assert_eq!(raw.to_encoded(), encoded);
|
||||||
|
assert_eq!(raw, encoded.to_raw().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "gen_secret")]
|
#[cfg(feature = "gen_secret")]
|
||||||
fn secret_gen_secret() {
|
fn secret_gen_secret() {
|
||||||
|
|
Loading…
Reference in New Issue