If a certain number of touches (10 by default) did not lead to a
successful unlock, then the locker will go into a deeper lock state and
show a red background. This is a time-out state during which the device
can not be unlocked, it will time out after a number of seconds (20
by default) and go back to normal lock mode. This is a security measure
to prevent brute-force attacks.
The number of tries is configured via ``--tries``, the timeout (in
seconds) via ``--deeplock``.
You can set ``--paranoid`` to make the timeout double each time the deep
lock state is reached, which may lead to the timeout being so long that
you need to force a hard reboot.
---
This should be applied on top of all my earlier patches. And it should
be the last patch for a while hopefully ;)
README.md | 15 ++++++-
src/draw.rs | 4 +-
src/main.rs | 103 ++++++++++++++++++++++++++++++++++++----------
src/touch_pass.rs | 37 +++++++++++++++--
4 files changed, 132 insertions(+), 27 deletions(-)
diff --git a/README.md b/README.md
index dfd527f..4664363 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,11 @@ A touch will be acknowledged by flipping the background, this provides only
provides a indication whether the touch was registered, not whether it is a
valid touch in the passcode sequence. The feedback will be over the entire screen
and will not specifically highlight the quadrant you tapped (as a security measure).
-Visual feedback can be disabled entirely by passing the `-F` or `--nofeedback` option.
+
+## Security
+
+For extra security, visual feedback can be disabled entirely by passing the
+`-F` or `--nofeedback` option.
Another way to increase security, is to set the parameter `-3` to change the
resolution to 3 (3x3 = 9 quadrants). Which takes the following configuration:
@@ -112,6 +116,15 @@ You can even go up to `-4`, which will give you this next configuration (4x4 =
c d e f
```
+By default, if a certain number of touches (10 by default) did not lead to a
+successful unlock, then the locker will go into a deeper lock state and show
+red a background. This is a time-out state during which the device can not be
+unlocked, it will time out after a number of seconds (20 by default) and go
+back to normal lock mode. This is a security measure to prevent brute-force attacks.
+
+The number of tries is configured via ``--tries``, the timeout (in seconds) via ``--deeplock``.
+You can set ``--paranoid`` to make the timeout double each time the deep lock state is reached, which
+may lead to the timeout being so long that you need to force a hard reboot.
# Safety
diff --git a/src/draw.rs b/src/draw.rs
index 46e14ca..3a5a3b1 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -38,7 +38,9 @@ impl State {
)
.unwrap();
let context = cairo::Context::new(&surface).unwrap();
- if self.inverted {
+ if self.touch_state.is_some() && self.touch_state.as_ref().unwrap().permalocked() {
+ context.set_source_rgb(1.0, 0.0, 0.0);
+ } else if self.inverted {
context.set_source_rgb(1.0, 1.0, 1.0);
} else {
context.set_source_rgb(0.0, 0.0, 0.0);
diff --git a/src/main.rs b/src/main.rs
index 8a08f79..c557527 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,22 +15,40 @@ use wayland_protocol::TouchDown;
mod touch_pass;
use touch_pass::TouchState;
+#[derive(Copy, Clone, PartialEq)]
+enum ParseArg {
+ None,
+ Font,
+ StatusCommand,
+ Tries,
+ DeepLockTime,
+}
+
// The main function of our program
fn main() {
let mut debug = false;
let mut notouch = false;
let mut nofeedback = false;
let mut statuscommand: Option<Vec<String>> = None;
- let mut parsefont = false;
+ let mut parsearg = ParseArg::None;
let mut resolution = 2;
+ let mut tries: u16 = 10;
+ let mut deeplocktime = time::Duration::from_secs(20);
+ let mut paranoid = false;
let mut fontname: String = "Sans".into();
for argument in env::args().skip(1) {
match argument.as_str() {
"-d" | "--debug" => debug = true,
"-T" | "--notouch" => notouch = true,
"-F" | "--nofeedback" => nofeedback = true,
- "-s" | "--statuscommand" => statuscommand = Some(Vec::new()),
- "-f" | "--font" => parsefont = true,
+ "-s" | "--statuscommand" => {
+ statuscommand = Some(Vec::new());
+ parsearg = ParseArg::StatusCommand;
+ }
+ "-f" | "--font" => parsearg = ParseArg::Font,
+ "-t" | "--tries" => parsearg = ParseArg::Tries,
+ "-L" | "--deeplock" => parsearg = ParseArg::DeepLockTime,
+ "-p" | "--paranoid" => paranoid = true,
"-1" => resolution = 1,
"-2" => resolution = 2,
"-3" => resolution = 3,
@@ -43,7 +61,9 @@ fn main() {
eprintln!(" -T / --notouch Disable touch support");
eprintln!(" -F / --nofeedback Disable touch feedback");
eprintln!(" -f / --font [font Set font family");
- eprintln!(" -s / --statuscommand [command] Run this command for a status line, it should run continuously and each new output line will become the status line");
+ eprintln!(" -t / --tries [tries] The number of incorrect touches allowed before the locker goes into a deeper lock (0 = unlimited, default = {})", tries);
+ eprintln!(" -L / --deeplock [seconds] Timeout in seconds for deeplock mode, during which the device can not be unlocked. (default = {})", deeplocktime.as_secs());
+ eprintln!(" -p / --paranoid In paranoid mode, the deeplock timeout will be doubled every time it is reached, making successive attempts harder and harder.");
eprintln!(" -1 1x1 touch grid (insecure)");
eprintln!(
" -2 2x2 touch grid (1=topleft, 4=bottomright)"
@@ -54,23 +74,38 @@ fn main() {
eprintln!(
" -4 4x4 touch grid (0=topleft, f=bottomright)"
);
+ eprintln!(" -s / --statuscommand [command] Run this command for a status line, it should run continuously and each new output line will become the status line. This parameter should be the last parameter provided.");
std::process::exit(0);
}
"-v" | "--version" => {
println!(env!("CARGO_PKG_VERSION"));
std::process::exit(0);
}
- _ => {
- if parsefont {
+ _ => match parsearg {
+ ParseArg::Font => {
fontname = argument;
- parsefont = false;
- } else if let Some(statuscommand) = statuscommand.as_mut() {
- statuscommand.push(argument);
- } else {
+ parsearg = ParseArg::None;
+ }
+ ParseArg::StatusCommand => {
+ if let Some(statuscommand) = statuscommand.as_mut() {
+ statuscommand.push(argument);
+ }
+ }
+ ParseArg::Tries => {
+ tries = argument.parse().expect("Tries must be an integer");
+ }
+ ParseArg::DeepLockTime => {
+ deeplocktime = time::Duration::from_secs(
+ argument
+ .parse()
+ .expect("Deep lock time must be an integer (seconds)"),
+ );
+ }
+ ParseArg::None => {
eprintln!("invalid option: {}", argument);
std::process::exit(1);
}
- }
+ },
}
}
@@ -124,6 +159,7 @@ fn main() {
init_wayland(&fontname, debug, notouch, nofeedback, status);
let wait_between_lock_unlock = time::Duration::from_secs(2);
let wait_between_polling = time::Duration::from_millis(250);
+ let mut reset_touchstate: Option<chrono::DateTime<chrono::Local>> = None;
way_state.draw(); //initial frame
way_state.lock();
@@ -133,6 +169,7 @@ fn main() {
way_state.height.unwrap(),
passcode.as_deref(),
resolution,
+ tries,
));
//single blocking dispatch before the main loop
@@ -186,6 +223,17 @@ fn main() {
eprintln!("status changed");
}
}
+ if let Some(reset_time) = reset_touchstate.as_ref() {
+ //we're in a deep lock state, check if we can exit again
+ if time >= *reset_time {
+ if way_state.debug {
+ eprintln!("deeplock state timed-out");
+ }
+ way_state.touch_state.as_mut().unwrap().reset();
+ way_state.redraw = true;
+ reset_touchstate = None;
+ }
+ }
if time_changed || status_changed {
way_state.redraw = true;
}
@@ -196,12 +244,29 @@ fn main() {
|| previous_down.as_ref().unwrap().serial != last_down.serial
{
previous_down = Some(*last_down);
- if way_state
- .touch_state
- .as_mut()
- .unwrap()
- .add_touch(last_down.x, last_down.y)
- {
+ if let Some(touch_state) = way_state.touch_state.as_mut() {
+ let code = touch_state.add_touch(last_down.x, last_down.y);
+ if way_state.debug {
+ eprintln!("touch: {}", code);
+ }
+ if touch_state.is_pass_valid() {
+ if way_state.debug {
+ eprintln!("code accepted");
+ }
+ break; //exit main loop
+ }
+ touch_state.fail();
+ if reset_touchstate.is_none() && touch_state.permalocked() {
+ //we entered a deep lock timeout stage because the number of tries exceeded the allowed number
+ //set a timer when it expires again
+ reset_touchstate = Some(chrono::Local::now() + deeplocktime);
+ if way_state.debug {
+ eprintln!("entering deeplock state until {:?}", reset_touchstate);
+ }
+ if paranoid {
+ deeplocktime = deeplocktime * 2;
+ }
+ }
if !way_state.nofeedback {
way_state.invert();
way_state.redraw = true;
@@ -216,10 +281,6 @@ fn main() {
surface.commit();
}
}
-
- if way_state.touch_state.as_ref().unwrap().is_pass_valid() {
- break;
- }
} else {
thread::sleep(wait_between_polling);
}
diff --git a/src/touch_pass.rs b/src/touch_pass.rs
index e07c318..94e3232 100644
--- a/src/touch_pass.rs
+++ b/src/touch_pass.rs
@@ -43,10 +43,18 @@ pub struct TouchState {
max_touches: usize,
grid_width: f64,
grid_height: f64,
+ tries: usize,
+ max_tries: u16,
}
impl TouchState {
- pub fn new(width: i32, height: i32, passcode: Option<&str>, resolution: u8) -> Self {
+ pub fn new(
+ width: i32,
+ height: i32,
+ passcode: Option<&str>,
+ resolution: u8,
+ max_tries: u16,
+ ) -> Self {
if resolution < 1 || resolution > 4 {
panic!("Invalid resolution");
}
@@ -72,6 +80,8 @@ impl TouchState {
},
grid_width: width as f64 / (resolution as f64),
grid_height: height as f64 / (resolution as f64),
+ tries: 0,
+ max_tries,
}
}
@@ -80,7 +90,7 @@ impl TouchState {
self.grid_height = height as f64 / (self.resolution as f64);
}
- pub fn add_touch(&mut self, x_val: f64, y_val: f64) -> bool {
+ pub fn add_touch(&mut self, x_val: f64, y_val: f64) -> u8 {
let startindex: u8 = if self.resolution == 4 { 0 } else { 1 };
let x = (x_val / self.grid_width).floor() as u8;
@@ -91,10 +101,29 @@ impl TouchState {
while self.touches.len() > self.max_touches {
self.touches.pop_front();
}
- true
+ code
}
pub fn is_pass_valid(&self) -> bool {
- self.touches == self.passcode
+ if self.permalocked() {
+ false
+ } else {
+ self.touches == self.passcode
+ }
+ }
+
+ pub fn fail(&mut self) {
+ if self.touches.len() == self.passcode.len() && self.max_tries != 0 {
+ self.tries += 1
+ }
+ }
+
+ pub fn permalocked(&self) -> bool {
+ self.max_tries != 0
+ && self.tries >= std::cmp::max(self.passcode.len(), self.max_tries as usize)
+ }
+
+ pub fn reset(&mut self) {
+ self.tries = 0;
}
}
--
2.47.1