Skip to content

Commit

Permalink
Merge pull request #3 from Boltgolt/dev
Browse files Browse the repository at this point in the history
Adding new installer and CLI
  • Loading branch information
boltgolt authored Feb 13, 2018
2 parents 4b37c5e + f084411 commit 1d4308c
Show file tree
Hide file tree
Showing 16 changed files with 722 additions and 142 deletions.
39 changes: 17 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,41 @@
# Howdy for Ubuntu

Windows Hello™ style authentication for Ubuntu. Use your build in IR emitters and camera in combination with face recognition to prove who you are.
Windows Hello™ style authentication for Ubuntu. Use your built-in IR emitters and camera in combination with face recognition to prove who you are.

Using the central authentication system in Linux (PAM), this works everywhere you would otherwise need your password: Login, lock screen, sudo, su, etc.

### Installation

First we need to install pam-python, fswebcam and OpenCV from the Ubuntu repositories:
Run the installer by pasting (`ctrl+shift+V`) the following command into the terminal:

```
sudo apt install libpam-python fswebcam libopencv-dev python-opencv
wget -O /tmp/howdy_install.py https://raw.githubusercontent.com/Boltgolt/howdy/master/installer.py && sudo python3 /tmp/howdy_install.py
```

After that, install the face_recognition python module. There's an excellent step by step guide on how to do this on [its github page](https://github.com/ageitgey/face_recognition#installation).

In the root of your cloned repo is a file called `config.ini`. The `device_id` variable in this file is important, make sure it is the IR camera and not your normal webcam.
This will guide you through the installation. When that's done run `howdy USER add` and replace `USER` with your username to add a face model.

Now it's time to let Howdy learn your face. The learn.py script will make 3 models of your face and store them as an encoded set in the `models` folder. To run the script, open a terminal, navigate to this repository and run:
If nothing went wrong we should be able to run sudo by just showing your face. Open a new terminal and run `sudo -i` to see it in action.

```
python3 learn.py
```
**Note:** The build of dlib can hang on 100% for over a minute, give it time.

The script should guide you through the process.
### Command line

Finally we need to tell PAM that there's a new module installed. Open `/etc/pam.d/common-auth` as root (`sudo nano /etc/pam.d/common-auth`) and add the following line to the top of the file:
The installer adds a `howdy` command to manage face models for the current user. Use `howdy help` to list the available options.

```
auth sufficient pam_python.so /path/to/pam.py
```
### Troubleshooting

Replace the final argument with the full path to pam.py in this repository. The `sufficient` control tells PAM that Howdy is enough to authenticate the user, but if it fails we can fall back on more traditional methods.
Any python errors get logged directly into the console and should indicate what went wrong. If authentication still fails but no errors are printed you could take a look at the last lines in `/var/log/auth.log` to see if anything has been reported there.

If nothing went wrong we should be able to run sudo by just showing your face. Open a new terminal and run `sudo -i` to see it in action.

### Troubleshooting
If you encounter an error that hasn't been reported yet, don't be afraid to open a new issue.

Any errors in the script itself get logged directly into the console and should indicate what went wrong. If authentication still fails but no errors are printed you could take a look at the last lines in `/var/log/auth.log` to see if anything has been reported there.
### Uninstalling

There is an uninstaller available, run `sudo python3 /lib/security/howdy/uninstall.py` to remove Howdy from your system.

### A note on security

This script is in no way as secure as a password and will never be. Although it's harder to fool than normal face recognition, a person who looks similar to you or well-printed photo of you could be enough to do it.

To minimize the chance of this script being compromised, it's recommend to store this repo in `/etc/pam.d` and to make it read only.
To minimize the chance of this script being compromised, it's recommend to leave this repo in /lib/security and to keep it read only.

DO NOT USE THIS SCRIPT AS THE SOLE AUTHENTICATION METHOD FOR YOUR SYSTEM.
DO NOT USE HOWDY AS THE SOLE AUTHENTICATION METHOD FOR YOUR SYSTEM.
15 changes: 15 additions & 0 deletions autocomplete.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Autocomplete file run in bash
# Will sugest arguments on tab

_howdy() {
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="help list add remove clear"

COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}

complete -F _howdy howdy
41 changes: 41 additions & 0 deletions cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env python3

# CLI directly called by running the howdy command

# Import required modules
import sys
import os

# Check if the minimum of 3 arugemnts has been met and print help otherwise
if (len(sys.argv) < 3):
print("Howdy IR face recognition help")
import cli.help
sys.exit()

# The command given
cmd = sys.argv[2]

# Requre sudo for comamnds that need root rights to read the model files
if cmd in ["list", "add", "remove", "clear"] and os.getenv("SUDO_USER") is None:
print("Please run this command with sudo")
sys.exit()

# Call the right files for the given command
if cmd == "list":
import cli.list
elif cmd == "help":
print("Howdy IR face recognition")
import cli.help
elif cmd == "add":
import cli.add
elif cmd == "remove":
import cli.remove
elif cmd == "clear":
import cli.clear
else:
# If the comand is invalid, check if the user hasn't swapped the username and command
if sys.argv[1] in ["list", "add", "remove", "clear", "help"]:
print("Usage: howdy <user> <command>")
else:
print('Unknown command "' + cmd + '"')
import cli.help
Empty file added cli/__init__.py
Empty file.
69 changes: 50 additions & 19 deletions learn.py → cli/add.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
# Save the face of the user in encoded form

# Import required modules
import face_recognition
import subprocess
import time
import os
import sys
import json

# Import config and extra functions
import configparser
import utils


# Try to import face_recognition and give a nice error if we can't
# Add should be the first point where import issues show up
try:
import face_recognition
except ImportError as err:
print(err)

print("\nCan't import the face_recognition module, check the output of")
print("pip3 show face_recognition")
sys.exit()

# Get the absolute path to the current file
path = os.path.dirname(os.path.abspath(__file__))

# Read config from disk
config = configparser.ConfigParser()
config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini")
config.read(path + "/../config.ini")

def captureFrame(delay):
"""Capture and encode 1 frame of video"""
global encodings
global insert_model

# Call fswebcam to save a frame to /tmp with a set delay
exit_code = subprocess.call(["fswebcam", "-S", str(delay), "--no-banner", "-d", "/dev/video" + str(config.get("video", "device_id")), tmp_file])
Expand Down Expand Up @@ -54,36 +65,53 @@ def captureFrame(delay):
for point in enc[0]:
clean_enc.append(point)

encodings.append(clean_enc)
insert_model["data"].append(clean_enc)

# The current user
user = os.environ.get("USER")
user = sys.argv[1]
# The name of the tmp frame file to user
tmp_file = "/tmp/howdy_" + user + ".jpg"
# The permanent file to store the encoded model in
enc_file = "./models/" + user + ".dat"
enc_file = path + "/../models/" + user + ".dat"
# Known encodings
encodings = []

# Make the ./models folder if it doesn't already exist
if not os.path.exists("models"):
if not os.path.exists(path + "/../models"):
print("No face model folder found, creating one")
os.makedirs("models")
os.makedirs(path + "/../models")

# To try read a premade encodings file if it exists
try:
encodings = json.load(open(enc_file))
except FileNotFoundError:
encodings = False

# If a file does exist, ask the user what needs to be done
if encodings != False:
encodings = utils.print_menu(encodings)
else:
encodings = []

print("\nLearning face for the user account " + user)
print("Please look straight into the camera for 5 seconds")
print("Adding face model for the user account " + user)

# Set the default label
label = "Initial model"

# If models already exist, set that default label
if len(encodings) > 0:
label = "Model #" + str(len(encodings) + 1)

# Ask the user for a custom label
label_in = input("Enter a label for this new model [" + label + "]: ")

# Set the custom label (if any) and limit it to 24 characters
if label_in != "":
label = label_in[:24]

# Prepare the metadata for insertion
insert_model = {
"time": int(time.time()),
"label": label,
"id": len(encodings),
"data": []
}

print("\nPlease look straight into the camera for 5 seconds")

# Give the user time to read
time.sleep(2)
Expand All @@ -93,6 +121,9 @@ def captureFrame(delay):
time.sleep(.3)
captureFrame(delay)

# Insert full object into the list
encodings.append(insert_model)

# Save the new encodings to disk
with open(enc_file, "w") as datafile:
json.dump(encodings, datafile)
Expand Down
33 changes: 33 additions & 0 deletions cli/clear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Clear all models by deleting the whole file

# Import required modules
import os
import sys

# Get the full path to this file
path = os.path.dirname(os.path.abspath(__file__))
# Get the passed user
user = sys.argv[1]

# Check if the models folder is there
if not os.path.exists(path + "/../models"):
print("No models created yet, can't clear them if they don't exist")
sys.exit()

# Check if the user has a models file to delete
if not os.path.isfile(path + "/../models/" + user + ".dat"):
print(user + " has no models or they have been cleared already")
sys.exit()

# Double check with the user
print("This will clear all models for " + user)
ans = input("Do you want to continue [y/N]: ")

# Abort if they don't answer y or Y
if (ans.lower() != "y"):
print('\nInerpeting as a "NO"')
sys.exit()

# Delete otherwise
os.remove(path + "/../models/" + user + ".dat")
print("\nModels cleared")
16 changes: 16 additions & 0 deletions cli/help.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Prints a simple help page for the CLI

print("""
Usage:
howdy <user> <command> [argument]
Commands:
help Show this help page
list List all saved face models for the current user
add Add a new face model for the current user
remove [id] Remove a specific model
clear Remove all face models for the current user
For support please visit
https://github.com/Boltgolt/howdy\
""")
46 changes: 46 additions & 0 deletions cli/list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# List all models for a user

# Import required modules
import sys
import os
import json
import time

# Get the absolute path and the username
path = os.path.dirname(os.path.realpath(__file__)) + "/.."
user = sys.argv[1]

# Check if the models file has been created yet
if not os.path.exists(path + "/models"):
print("Face models have not been initialized yet, please run:")
print("\n\thowdy " + user + " add\n")
sys.exit(1)

# Path to the models file
enc_file = path + "/models/" + user + ".dat"

# Try to load the models file and abort if the user does not have it yet
try:
encodings = json.load(open(enc_file))
except FileNotFoundError:
print("No face model known for the user " + user + ", please run:")
print("\n\thowdy " + user + " add\n")
sys.exit(1)

# Print a header
print("Known face models for " + user + ":")
print("\n\t\033[1;29mID Date Label\033[0m")

# Loop through all encodings and print info about them
for enc in encodings:
# Start with a tab and print the id
print("\t" + str(enc["id"]), end="")
# Print padding spaces after the id
print((4 - len(str(enc["id"]))) * " ", end="")
# Format the time as ISO in the local timezone
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(enc["time"])), end="")
# End with the label
print(" " + enc["label"])

# Add a closing enter
print()
Loading

0 comments on commit 1d4308c

Please sign in to comment.