Skip to content

Commit bd51049

Browse files
authored
Merge pull request #192 from replydev/fix/crash_on_decode
Fix/crash on decode
2 parents 54cb983 + 77fe1bd commit bd51049

File tree

15 files changed

+133
-58
lines changed

15 files changed

+133
-58
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cotp"
3-
version = "1.2.2"
3+
version = "1.2.3"
44
authors = ["replydev <[email protected]>"]
55
edition = "2021"
66
description = "Trustworthy, encrypted, command-line TOTP/HOTP authenticator app with import functionality."

src/interface/app.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use tui::style::{Color, Modifier, Style};
1111
use tui::terminal::Frame;
1212
use tui::widgets::{Block, Borders, Cell, Clear, Gauge, Paragraph, Row, Table, Wrap};
1313

14-
use crate::interface::table::{fill_table, StatefulTable};
14+
use crate::interface::stateful_table::{fill_table, StatefulTable};
1515
use crate::utils::percentage;
1616

1717
use super::enums::PopupAction;
@@ -20,7 +20,7 @@ use super::popup::centered_rect;
2020
const LARGE_APPLICATION_WIDTH: u16 = 75;
2121

2222
/// Application result type.
23-
pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>;
23+
pub type AppResult<T> = Result<T, Box<dyn error::Error>>;
2424

2525
/// Application.
2626
pub struct App {
@@ -222,14 +222,9 @@ impl App {
222222
.height(1)
223223
.bottom_margin(1);
224224
let rows = self.table.items.iter().map(|item| {
225-
let height = item
226-
.iter()
227-
.map(|content| content.chars().filter(|c| *c == '\n').count())
228-
.max()
229-
.unwrap_or(0)
230-
+ 1;
231-
let cells = item.iter().map(|c| Cell::from(c.as_str()));
232-
Row::new(cells).height(height as u16).bottom_margin(1)
225+
Row::new(item.cells())
226+
.height(item.height())
227+
.bottom_margin(1)
233228
});
234229

235230
let t = Table::new(rows)

src/interface/handler.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ fn delete_selected_code(app: &mut App) -> Result<String, String> {
214214
fn copy_selected_code_to_clipboard(app: &mut App) -> String {
215215
match app.table.state.selected() {
216216
Some(selected) => match app.table.items.get(selected) {
217-
Some(element) => match element.get(3) {
217+
Some(element) => match element.values.get(3) {
218218
Some(otp_code) => {
219219
if let Ok(result) = copy_string_to_clipboard(otp_code.to_owned()) {
220220
match result {
@@ -261,9 +261,10 @@ fn handle_switch_page(app: &mut App, page: Page) {
261261

262262
fn search_and_select(app: &mut App) {
263263
// Check for issuer
264-
for row in app.table.items.iter().enumerate() {
265-
let (index, values) = row;
266-
if values
264+
for iter in app.table.items.iter().enumerate() {
265+
let (index, row) = iter;
266+
if row
267+
.values
267268
.get(1)
268269
.unwrap()
269270
.to_lowercase()
@@ -274,9 +275,10 @@ fn search_and_select(app: &mut App) {
274275
}
275276
}
276277
// Check for label
277-
for row in app.table.items.iter().enumerate() {
278-
let (index, values) = row;
279-
if values
278+
for iter in app.table.items.iter().enumerate() {
279+
let (index, row) = iter;
280+
if row
281+
.values
280282
.get(2)
281283
.unwrap()
282284
.to_lowercase()
@@ -287,9 +289,10 @@ fn search_and_select(app: &mut App) {
287289
}
288290
}
289291
// Check if issuer contains the query
290-
for row in app.table.items.iter().enumerate() {
291-
let (index, values) = row;
292-
if values
292+
for iter in app.table.items.iter().enumerate() {
293+
let (index, row) = iter;
294+
if row
295+
.values
293296
.get(1)
294297
.unwrap()
295298
.to_lowercase()
@@ -300,9 +303,10 @@ fn search_and_select(app: &mut App) {
300303
}
301304
}
302305
// Check if label contains the query
303-
for row in app.table.items.iter().enumerate() {
304-
let (index, values) = row;
305-
if values
306+
for iter in app.table.items.iter().enumerate() {
307+
let (index, row) = iter;
308+
if row
309+
.values
306310
.get(2)
307311
.unwrap()
308312
.to_lowercase()

src/interface/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ mod enums;
33
pub mod event;
44
pub mod handler;
55
mod popup;
6-
pub mod table;
6+
mod row;
7+
pub mod stateful_table;
78
pub mod ui;

src/interface/row.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use tui::style::Color::{Black, Yellow};
2+
use tui::style::Style;
3+
use tui::widgets::Cell;
4+
5+
pub(crate) struct Row {
6+
pub(crate) values: Vec<String>,
7+
has_error: bool,
8+
}
9+
10+
impl Row {
11+
pub(crate) fn new(values: Vec<String>, has_error: bool) -> Self {
12+
Row { values, has_error }
13+
}
14+
pub fn height(&self) -> u16 {
15+
(self
16+
.values
17+
.iter()
18+
.map(|content| content.chars().filter(|c| *c == '\n').count())
19+
.max()
20+
.unwrap_or(0)
21+
+ 1) as u16
22+
}
23+
24+
pub fn cells(&self) -> Vec<Cell> {
25+
self.values
26+
.iter()
27+
.map(|c| {
28+
let style = if self.has_error {
29+
Style::default().bg(Yellow).fg(Black)
30+
} else {
31+
Style::default()
32+
};
33+
Cell::from(c.as_str()).style(style)
34+
})
35+
.collect()
36+
}
37+
}

src/interface/table.rs renamed to src/interface/stateful_table.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use crate::interface::row::Row;
12
use tui::widgets::TableState;
23

34
use crate::otp::{otp_element::OTPElement, otp_type::OTPType};
45

56
pub struct StatefulTable {
67
pub(crate) state: TableState,
7-
pub(crate) items: Vec<Vec<String>>,
8+
pub(crate) items: Vec<Row>,
89
}
910

1011
impl StatefulTable {
@@ -66,11 +67,20 @@ pub fn fill_table(table: &mut StatefulTable, elements: &[OTPElement]) {
6667
},
6768
_ => element.label.to_owned(),
6869
};
69-
table.items.push(vec![
70-
(i + 1).to_string(),
71-
element.issuer.to_owned(),
72-
label,
73-
element.get_otp_code().unwrap(),
74-
]);
70+
let result = element.get_otp_code();
71+
72+
let error = result.is_err();
73+
table.items.push(Row::new(
74+
vec![
75+
(i + 1).to_string(),
76+
element.issuer.to_owned(),
77+
label,
78+
match result {
79+
Ok(code) => code,
80+
Err(e) => e.to_string(),
81+
},
82+
],
83+
error,
84+
));
7585
}
7686
}

src/otp/algorithms/hotp_maker.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@ use sha1::Sha1;
1313
use sha2::{Sha256, Sha512};
1414

1515
use crate::otp::otp_algorithm::OTPAlgorithm;
16+
use crate::otp::otp_error::OtpError;
1617

17-
pub fn hotp(secret: &str, algorithm: OTPAlgorithm, counter: u64) -> Result<u32, String> {
18+
pub fn hotp(secret: &str, algorithm: OTPAlgorithm, counter: u64) -> Result<u32, OtpError> {
1819
match algorithm {
1920
OTPAlgorithm::Sha256 => generate_hotp::<Sha256>(secret, counter),
2021
OTPAlgorithm::Sha512 => generate_hotp::<Sha512>(secret, counter),
2122
_ => generate_hotp::<Sha1>(secret, counter),
2223
}
2324
}
2425

25-
fn generate_hotp<D>(secret: &str, counter: u64) -> Result<u32, String>
26+
fn generate_hotp<D>(secret: &str, counter: u64) -> Result<u32, OtpError>
2627
where
2728
D: CoreProxy,
2829
D::Core: HashMarker
@@ -37,21 +38,21 @@ where
3738
// decode the base32 secret
3839
let secret_decoded = match BASE32_NOPAD.decode(secret.as_bytes()) {
3940
Ok(result) => result,
40-
Err(e) => return Err(format!("{e:?}")),
41+
Err(e) => return Err(OtpError::SecretEncoding(e.kind, e.position)),
4142
};
4243

4344
let hash = hotp_hash::<D>(&secret_decoded, counter);
4445

4546
// calculate offset
4647
let offset: usize = match hash.last() {
4748
Some(result) => *result & 0xf,
48-
None => return Err(String::from("Invalid digest")),
49+
None => return Err(OtpError::InvalidOffset),
4950
} as usize;
5051

5152
// calculate code
5253
let code_bytes: [u8; 4] = match hash[offset..offset + 4].try_into() {
5354
Ok(x) => x,
54-
Err(_) => return Err(String::from("Invalid digest")),
55+
Err(_) => return Err(OtpError::InvalidDigest),
5556
};
5657
Ok(u32::from_be_bytes(code_bytes) & 0x7fffffff)
5758
}

src/otp/algorithms/motp_maker.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::time::SystemTime;
22

3+
use crate::otp::otp_error::OtpError;
34
use md5::{Digest, Md5};
45

5-
pub fn motp(secret: &str, pin: &str, period: u64, digits: usize) -> Result<String, String> {
6+
pub fn motp(secret: &str, pin: &str, period: u64, digits: usize) -> Result<String, OtpError> {
67
let seconds = SystemTime::now()
78
.duration_since(SystemTime::UNIX_EPOCH)
89
.unwrap()
@@ -17,7 +18,7 @@ fn get_motp_code(
1718
period: u64,
1819
digits: usize,
1920
seconds: u64,
20-
) -> Result<String, String> {
21+
) -> Result<String, OtpError> {
2122
// TODO MOTP Secrets are hex encoded, so do not use BASE32 at all
2223
let hex_secret = secret;
2324
let counter = seconds / period;

src/otp/algorithms/steam_otp_maker.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// Ported from https://github.com/beemdevelopment/Aegis/blob/master/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/OTP.java
22

33
use crate::otp::otp_algorithm::OTPAlgorithm;
4+
use crate::otp::otp_error::OtpError;
45

56
use super::totp_maker::totp;
67

78
const STEAM_ALPHABET: &str = "23456789BCDFGHJKMNPQRTVWXY";
89

9-
pub fn steam(secret: &str, algorithm: OTPAlgorithm, digits: usize) -> Result<String, String> {
10+
pub fn steam(secret: &str, algorithm: OTPAlgorithm, digits: usize) -> Result<String, OtpError> {
1011
match totp(secret, algorithm) {
1112
Ok(v) => Ok(to_steam_string(v as usize, digits)),
1213
Err(e) => Err(e),

0 commit comments

Comments
 (0)