git: 93eaa5412bfc - main - security/howdy: Face recognition based authentication provider.
Date: Mon, 03 Apr 2023 14:15:48 UTC
The branch main has been updated by arrowd: URL: https://cgit.FreeBSD.org/ports/commit/?id=93eaa5412bfca8daf12b7c81110c81b83211f54e commit 93eaa5412bfca8daf12b7c81110c81b83211f54e Author: Alexey Yushkin <636808@mail.ru> AuthorDate: 2023-04-03 14:06:36 +0000 Commit: Gleb Popov <arrowd@FreeBSD.org> CommitDate: 2023-04-03 14:15:32 +0000 security/howdy: Face recognition based authentication provider. Co-authored-by: Alexey Donskov <voxnod@gmail.com> Co-authored-by: Gleb Popov <arrowd@FreeBSD.org> Sponsored by: Serenity Cybersecurity, LLC --- security/Makefile | 1 + security/howdy/Makefile | 52 +++++ security/howdy/distinfo | 3 + security/howdy/files/patch-paths | 429 +++++++++++++++++++++++++++++++++++++++ security/howdy/pkg-descr | 3 + security/howdy/pkg-plist | 27 +++ 6 files changed, 515 insertions(+) diff --git a/security/Makefile b/security/Makefile index 034e957031c0..2f61c05f25c7 100644 --- a/security/Makefile +++ b/security/Makefile @@ -218,6 +218,7 @@ SUBDIR += honeytrap SUBDIR += honggfuzz SUBDIR += horcrux + SUBDIR += howdy SUBDIR += hpenc SUBDIR += hs-cryptol SUBDIR += hydra diff --git a/security/howdy/Makefile b/security/howdy/Makefile new file mode 100644 index 000000000000..675fca7c5aca --- /dev/null +++ b/security/howdy/Makefile @@ -0,0 +1,52 @@ +PORTNAME= howdy +PORTVERSION= 3.0.0.b +CATEGORIES= security + +MAINTAINER= arrowd@FreeBSD.org +COMMENT= Windows Hello(TM) style authentication provider +WWW= https://github.com/boltgolt/howdy + +LICENSE= MIT +LICENSE_FILE= ${WRKSRC}/LICENSE + +RUN_DEPENDS= opencv>0:graphics/opencv \ + ${PYTHON_PKGNAMEPREFIX}python-pam>0:security/py-python-pam@${PY_FLAVOR} \ + ${PYTHON_PKGNAMEPREFIX}dlib>0:science/py-dlib@${PY_FLAVOR} \ + ${PYTHON_PKGNAMEPREFIX}numpy>0:math/py-numpy@${PY_FLAVOR} + +USES= python:run shebangfix + +USE_GITHUB= yes +GH_ACCOUNT= boltgolt +GH_PROJECT= howdy +GH_TAGNAME= 30728a6d3634479c24ffd4e094c34a30bbb43058 + +SHEBANG_GLOB= *.py + +NO_BUILD= yes +NO_ARCH= yes +STRIP= + +post-patch: + ${ECHO_CMD} 'config_dir = "${ETCDIR}"' >> ${WRKSRC}/howdy/src/paths.py + ${ECHO_CMD} 'dlib_data_dir = "${LOCALBASE}/share/dlib-data/"' >> ${WRKSRC}/howdy/src/paths.py + ${ECHO_CMD} 'user_models_dir = "/var/db/howdy/models/"' >> ${WRKSRC}/howdy/src/paths.py + +do-install: + ${MKDIR} ${STAGEDIR}${PREFIX}/libexec/howdy + cd ${WRKSRC}/howdy/src/ && \ + ${COPYTREE_SHARE} . ${STAGEDIR}${PREFIX}/libexec/howdy + ${MKDIR} ${STAGEDIR}${ETCDIR} + ${MV} ${STAGEDIR}${PREFIX}/libexec/howdy/config.ini ${STAGEDIR}${ETCDIR}/config.ini.sample + ${LN} -s ../libexec/howdy/cli.py ${STAGEDIR}${PREFIX}/bin/howdy + ${INSTALL_PROGRAM} ${WRKSRC}/howdy/src/cli.py ${STAGEDIR}${PREFIX}/libexec/howdy + + ${MKDIR} ${STAGEDIR}${PREFIX}/share/bash-completion/completions + ${INSTALL_DATA} ${WRKSRC}/howdy/src/autocomplete/howdy ${STAGEDIR}${PREFIX}/share/bash-completion/completions/ + + ${RM} -r ${STAGEDIR}${PREFIX}/libexec/howdy/autocomplete + ${RM} -r ${STAGEDIR}${PREFIX}/libexec/howdy/dlib-data + ${RM} -r ${STAGEDIR}${PREFIX}/libexec/howdy/pam + ${RM} -r ${STAGEDIR}${PREFIX}/libexec/howdy/pam-config + +.include <bsd.port.mk> diff --git a/security/howdy/distinfo b/security/howdy/distinfo new file mode 100644 index 000000000000..8b91fb02664b --- /dev/null +++ b/security/howdy/distinfo @@ -0,0 +1,3 @@ +TIMESTAMP = 1680193772 +SHA256 (boltgolt-howdy-3.0.0.b-30728a6d3634479c24ffd4e094c34a30bbb43058_GH0.tar.gz) = 76078fbfb48a186678476cf9844987c97793a71135f1e612d761350c6b27fa42 +SIZE (boltgolt-howdy-3.0.0.b-30728a6d3634479c24ffd4e094c34a30bbb43058_GH0.tar.gz) = 130651 diff --git a/security/howdy/files/patch-paths b/security/howdy/files/patch-paths new file mode 100644 index 000000000000..57e375f2dea5 --- /dev/null +++ b/security/howdy/files/patch-paths @@ -0,0 +1,429 @@ +From 30034c66d72e8e15e2ad5db68d1a5940df2568b6 Mon Sep 17 00:00:00 2001 +From: Gleb Popov <6yearold@gmail.com> +Date: Thu, 30 Mar 2023 21:55:11 +0300 +Subject: [PATCH] Put all path variables into a separate module. + +This makes it easier for downstream packagers to customize where howdy installs +its files. +--- + howdy/src/cli/add.py | 22 ++++++++++------------ + howdy/src/cli/clear.py | 9 ++++----- + howdy/src/cli/config.py | 3 ++- + howdy/src/cli/disable.py | 3 ++- + howdy/src/cli/list.py | 7 +++---- + howdy/src/cli/remove.py | 9 ++++----- + howdy/src/cli/set.py | 3 ++- + howdy/src/cli/snap.py | 5 ++--- + howdy/src/cli/test.py | 11 ++++++----- + howdy/src/compare.py | 18 ++++++++---------- + howdy/src/paths.py | 12 ++++++++++++ + howdy/src/snapshot.py | 15 ++++++--------- + 12 files changed, 61 insertions(+), 56 deletions(-) + create mode 100644 howdy/src/paths.py + +diff --git a/howdy/src/cli/add.py b/howdy/src/cli/add.py +index 7a6d9eca..5a63bdfe 100644 +--- a/howdy/src/cli/add.py ++++ ./howdy/src/cli/add.py +@@ -8,6 +8,7 @@ + import configparser + import builtins + import numpy as np ++import paths + + from recorders.video_capture import VideoCapture + from i18n import _ +@@ -26,39 +27,36 @@ + # OpenCV needs to be imported after dlib + import cv2 + +-# Define the absolute path to the config directory +-config_path = "/etc/howdy" +- + # Test if at lest 1 of the data files is there and abort if it's not +-if not os.path.isfile(config_path + "/dlib-data/shape_predictor_5_face_landmarks.dat"): ++if not os.path.isfile(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat"): + print(_("Data files have not been downloaded, please run the following commands:")) +- print("\n\tcd " + config_path + "/dlib-data") ++ print("\n\tcd " + paths.dlib_data_dir) + print("\tsudo ./install.sh\n") + sys.exit(1) + + # Read config from disk + config = configparser.ConfigParser() +-config.read(config_path + "/config.ini") ++config.read(paths.config_dir + "/config.ini") + + use_cnn = config.getboolean("core", "use_cnn", fallback=False) + if use_cnn: +- face_detector = dlib.cnn_face_detection_model_v1(config_path + "/dlib-data/mmod_human_face_detector.dat") ++ face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir + "mmod_human_face_detector.dat") + else: + face_detector = dlib.get_frontal_face_detector() + +-pose_predictor = dlib.shape_predictor(config_path + "/dlib-data/shape_predictor_5_face_landmarks.dat") +-face_encoder = dlib.face_recognition_model_v1(config_path + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat") ++pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") ++face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") + + user = builtins.howdy_user + # The permanent file to store the encoded model in +-enc_file = config_path + "/models/" + user + ".dat" ++enc_file = paths.user_models_dir + user + ".dat" + # Known encodings + encodings = [] + + # Make the ./models folder if it doesn't already exist +-if not os.path.exists(config_path + "/models"): ++if not os.path.exists(paths.user_models_dir): + print(_("No face model folder found, creating one")) +- os.makedirs(config_path + "/models") ++ os.makedirs(paths.user_models_dir) + + # To try read a premade encodings file if it exists + try: +diff --git a/howdy/src/cli/clear.py b/howdy/src/cli/clear.py +index 6fa5f3ef..aa43e152 100644 +--- a/howdy/src/cli/clear.py ++++ ./howdy/src/cli/clear.py +@@ -4,21 +4,20 @@ + import os + import sys + import builtins ++import paths + + from i18n import _ + +-# Get the full path to this file +-path = "/etc/howdy/models" + # Get the passed user + user = builtins.howdy_user + + # Check if the models folder is there +-if not os.path.exists(path): ++if not os.path.exists(paths.user_models_dir): + print(_("No models created yet, can't clear them if they don't exist")) + sys.exit(1) + + # Check if the user has a models file to delete +-if not os.path.isfile(path + "/" + user + ".dat"): ++if not os.path.isfile(paths.user_models_dir + user + ".dat"): + print(_("{} has no models or they have been cleared already").format(user)) + sys.exit(1) + +@@ -34,5 +33,5 @@ + sys.exit(1) + + # Delete otherwise +-os.remove(path + "/" + user + ".dat") ++os.remove(paths.user_models_dir + user + ".dat") + print(_("\nModels cleared")) +diff --git a/howdy/src/cli/config.py b/howdy/src/cli/config.py +index 71064839..04c51798 100644 +--- a/howdy/src/cli/config.py ++++ ./howdy/src/cli/config.py +@@ -3,6 +3,7 @@ + # Import required modules + import os + import subprocess ++import paths + + from i18n import _ + +@@ -19,4 +20,4 @@ + editor = "/etc/alternatives/editor" + + # Open the editor as a subprocess and fork it +-subprocess.call([editor, "/etc/howdy/config.ini"]) ++subprocess.call([editor, paths.config_dir + "config.ini"]) +diff --git a/howdy/src/cli/disable.py b/howdy/src/cli/disable.py +index be78c97f..1f655412 100644 +--- a/howdy/src/cli/disable.py ++++ ./howdy/src/cli/disable.py +@@ -6,11 +6,12 @@ + import builtins + import fileinput + import configparser ++import paths + + from i18n import _ + + # Get the absolute filepath +-config_path = os.path.dirname("/etc/howdy") + "/config.ini" ++config_path = os.path.dirname(paths.config_dir) + "/config.ini" + + # Read config from disk + config = configparser.ConfigParser() +diff --git a/howdy/src/cli/list.py b/howdy/src/cli/list.py +index 3532e9f8..7539837d 100644 +--- a/howdy/src/cli/list.py ++++ ./howdy/src/cli/list.py +@@ -6,21 +6,20 @@ + import json + import time + import builtins ++import paths + + from i18n import _ + +-# Get the absolute path and the username +-path = "/etc/howdy" + user = builtins.howdy_user + + # Check if the models file has been created yet +-if not os.path.exists(path + "/models"): ++if not os.path.exists(paths.user_models_dir): + print(_("Face models have not been initialized yet, please run:")) + print("\n\tsudo howdy -U " + user + " add\n") + sys.exit(1) + + # Path to the models file +-enc_file = path + "/models/" + user + ".dat" ++enc_file = paths.user_models_dir + user + ".dat" + + # Try to load the models file and abort if the user does not have it yet + try: +diff --git a/howdy/src/cli/remove.py b/howdy/src/cli/remove.py +index 6321e0b5..37894422 100644 +--- a/howdy/src/cli/remove.py ++++ ./howdy/src/cli/remove.py +@@ -5,11 +5,10 @@ + import os + import json + import builtins ++import paths + + from i18n import _ + +-# Get the absolute path and the username +-path = "/etc/howdy" + user = builtins.howdy_user + + # Check if enough arguments have been passed +@@ -22,13 +21,13 @@ + sys.exit(1) + + # Check if the models file has been created yet +-if not os.path.exists(path + "/models"): ++if not os.path.exists(paths.user_models_dir): + print(_("Face models have not been initialized yet, please run:")) + print("\n\thowdy add\n") + sys.exit(1) + + # Path to the models file +-enc_file = path + "/models/" + user + ".dat" ++enc_file = paths.user_models_dir + user + ".dat" + + # Try to load the models file and abort if the user does not have it yet + try: +@@ -72,7 +71,7 @@ + + # Remove the entire file if this encoding is the only one + if len(encodings) == 1: +- os.remove(path + "/models/" + user + ".dat") ++ os.remove(paths.user_models_dir + user + ".dat") + print(_("Removed last model, howdy disabled for user")) + else: + # A place holder to contain the encodings that will remain +diff --git a/howdy/src/cli/set.py b/howdy/src/cli/set.py +index 14d15c20..efbbee5b 100644 +--- a/howdy/src/cli/set.py ++++ ./howdy/src/cli/set.py +@@ -5,11 +5,12 @@ + import os + import builtins + import fileinput ++import paths + + from i18n import _ + + # Get the absolute filepath +-config_path = os.path.dirname("/etc/howdy") + "/config.ini" ++config_path = os.path.dirname(paths.config_dir) + "/config.ini" + + # Check if enough arguments have been passed + if len(builtins.howdy_args.arguments) < 2: +diff --git a/howdy/src/cli/snap.py b/howdy/src/cli/snap.py +index cbcae501..2c625d3b 100644 +--- a/howdy/src/cli/snap.py ++++ ./howdy/src/cli/snap.py +@@ -5,15 +5,14 @@ + import configparser + import datetime + import snapshot ++import paths + from recorders.video_capture import VideoCapture + + from i18n import _ + +-path = "/etc/howdy" +- + # Read the config + config = configparser.ConfigParser() +-config.read(path + "/config.ini") ++config.read(paths.config_dir + "config.ini") + + # Start video capture + video_capture = VideoCapture(config) +diff --git a/howdy/src/cli/test.py b/howdy/src/cli/test.py +index 3a6e4d19..563be19b 100644 +--- a/howdy/src/cli/test.py ++++ ./howdy/src/cli/test.py +@@ -10,6 +10,7 @@ + import dlib + import cv2 + import numpy as np ++import paths + + from i18n import _ + from recorders.video_capture import VideoCapture +@@ -19,7 +20,7 @@ + + # Read config from disk + config = configparser.ConfigParser() +-config.read(path + "/config.ini") ++config.read(paths.config_dir + "config.ini") + + if config.get("video", "recording_plugin", fallback="opencv") != "opencv": + print(_("Howdy has been configured to use a recorder which doesn't support the test command yet, aborting")) +@@ -59,20 +60,20 @@ def print_text(line_number, text): + + if use_cnn: + face_detector = dlib.cnn_face_detection_model_v1( +- path + "/dlib-data/mmod_human_face_detector.dat" ++ paths.dlib_data_dir + "mmod_human_face_detector.dat" + ) + else: + face_detector = dlib.get_frontal_face_detector() + +-pose_predictor = dlib.shape_predictor(path + "/dlib-data/shape_predictor_5_face_landmarks.dat") +-face_encoder = dlib.face_recognition_model_v1(path + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat") ++pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") ++face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") + + encodings = [] + models = None + + try: + user = builtins.howdy_user +- models = json.load(open(path + "/models/" + user + ".dat")) ++ models = json.load(open(paths.user_models_dir + user + ".dat")) + + for model in models: + encodings += model["data"] +diff --git a/howdy/src/compare.py b/howdy/src/compare.py +index 99f5285b..f81fe386 100644 +--- a/howdy/src/compare.py ++++ ./howdy/src/compare.py +@@ -23,6 +23,7 @@ + import snapshot + import numpy as np + import _thread as thread ++import paths + + # Allow imports from the local howdy folder + sys.path.append('/lib/security/howdy') +@@ -48,22 +49,22 @@ def init_detector(lock): + global face_detector, pose_predictor, face_encoder + + # Test if at lest 1 of the data files is there and abort if it's not +- if not os.path.isfile(PATH + "/dlib-data/shape_predictor_5_face_landmarks.dat"): ++ if not os.path.isfile(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat"): + print(_("Data files have not been downloaded, please run the following commands:")) +- print("\n\tcd " + PATH + "/dlib-data") ++ print("\n\tcd " + paths.dlib_data_dir) + print("\tsudo ./install.sh\n") + lock.release() + exit(1) + + # Use the CNN detector if enabled + if use_cnn: +- face_detector = dlib.cnn_face_detection_model_v1(PATH + "/dlib-data/mmod_human_face_detector.dat") ++ face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir + "mmod_human_face_detector.dat") + else: + face_detector = dlib.get_frontal_face_detector() + + # Start the others regardless +- pose_predictor = dlib.shape_predictor(PATH + "/dlib-data/shape_predictor_5_face_landmarks.dat") +- face_encoder = dlib.face_recognition_model_v1(PATH + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat") ++ pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") ++ face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") + + # Note the time it took to initialize detectors + timings["ll"] = time.time() - timings["ll"] +@@ -103,9 +104,6 @@ def send_to_ui(type, message): + if len(sys.argv) < 2: + exit(12) + +-# Get the absolute path to the config directory +-PATH = "/etc/howdy" +- + # The username of the user being authenticated + user = sys.argv[1] + # The model file contents +@@ -129,7 +127,7 @@ def send_to_ui(type, message): + + # Try to load the face model from the models folder + try: +- models = json.load(open(PATH + "/models/" + user + ".dat")) ++ models = json.load(open(paths.user_models_dir + user + ".dat")) + + for model in models: + encodings += model["data"] +@@ -142,7 +140,7 @@ def send_to_ui(type, message): + + # Read config from disk + config = configparser.ConfigParser() +-config.read(PATH + "/config.ini") ++config.read(paths.config_dir + "config.ini") + + # Get all config values needed + use_cnn = config.getboolean("core", "use_cnn", fallback=False) +diff --git a/howdy/src/paths.py b/howdy/src/paths.py +new file mode 100644 +index 00000000..22825405 +--- /dev/null ++++ ./howdy/src/paths.py +@@ -0,0 +1,12 @@ ++ ++# Define the absolute path to the config directory ++config_dir = "/etc/howdy/" ++ ++# Define the absolute path to the DLib models data directory ++dlib_data_dir = config_dir + "/dlib-data/" ++ ++# Define the absolute path to the Howdy user models directory ++user_models_dir = config_dir + "/models/" ++ ++# Define path to any howdy logs ++log_path = "/var/log/howdy" +diff --git a/howdy/src/snapshot.py b/howdy/src/snapshot.py +index 324b5789..9f2f563c 100644 +--- a/howdy/src/snapshot.py ++++ ./howdy/src/snapshot.py +@@ -49,19 +49,16 @@ def generate(frames, text_lines): + + line_number += 1 + +- # Define path to any howdy logs +- log_path = "/var/log/howdy" +- + # Made sure a snapshot folder exist +- if not os.path.exists(log_path): +- os.makedirs(log_path) +- if not os.path.exists(log_path + "/snapshots"): +- os.makedirs(log_path + "/snapshots") ++ if not os.path.exists(paths.log_path): ++ os.makedirs(paths.log_path) ++ if not os.path.exists(paths.log_path + "/snapshots"): ++ os.makedirs(paths.log_path + "/snapshots") + + # Generate a filename based on the current time + filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg") + # Write the image to that file +- cv2.imwrite(log_path + "/snapshots/" + filename, snap) ++ cv2.imwrite(paths.log_path + "/snapshots/" + filename, snap) + + # Return the saved file location +- return log_path + "/snapshots/" + filename ++ return paths.log_path + "/snapshots/" + filename diff --git a/security/howdy/pkg-descr b/security/howdy/pkg-descr new file mode 100644 index 000000000000..1fd314d7c55e --- /dev/null +++ b/security/howdy/pkg-descr @@ -0,0 +1,3 @@ +Howdy is an authentication tool that allows you to unlock your desktop +session using your webcam. It uses facial recognition to authenticate and +unlock the session. diff --git a/security/howdy/pkg-plist b/security/howdy/pkg-plist new file mode 100644 index 000000000000..5353cffca512 --- /dev/null +++ b/security/howdy/pkg-plist @@ -0,0 +1,27 @@ +bin/howdy +@sample %%ETCDIR%%/config.ini.sample +libexec/howdy/cli.py +libexec/howdy/cli/__init__.py +libexec/howdy/cli/add.py +libexec/howdy/cli/clear.py +libexec/howdy/cli/config.py +libexec/howdy/cli/disable.py +libexec/howdy/cli/list.py +libexec/howdy/cli/remove.py +libexec/howdy/cli/set.py +libexec/howdy/cli/snap.py +libexec/howdy/cli/test.py +libexec/howdy/compare.py +libexec/howdy/i18n.py +libexec/howdy/logo.png +libexec/howdy/paths.py +libexec/howdy/recorders/__init__.py +libexec/howdy/recorders/ffmpeg_reader.py +libexec/howdy/recorders/pyv4l2_reader.py +libexec/howdy/recorders/v4l2.py +libexec/howdy/recorders/video_capture.py +libexec/howdy/rubberstamps/__init__.py +libexec/howdy/rubberstamps/hotkey.py +libexec/howdy/rubberstamps/nod.py +libexec/howdy/snapshot.py +share/bash-completion/completions/howdy