~mil/sxmo-devel

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
1

[PATCH peanutbutter] Add a deeplock mode after a certain number of failed tries

Details
Message ID
<20241202133030.1369266-2-proycon@anaproy.nl>
DKIM signature
pass
Download raw message
Patch: +132 -27
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
Details
Message ID
<D64FAFTWMQTB.1NBZ6PAQ5YXT7@momi.ca>
In-Reply-To
<20241202133030.1369266-2-proycon@anaproy.nl> (view parent)
DKIM signature
pass
Download raw message
Thanks!

To git@git.sr.ht:~anjan/peanutbutter
   3d78f0e..81d20ff  main -> main
Reply to thread Export thread (mbox)