(in-package :cl-user) (defpackage chatikbot.secrets (:use :cl) (:export :*secret-ring* :*secret-pass-store* :*secret-pass-bin* :secret-get :secret-set :secret-del :secret-wipe :with-secret)) (in-package :chatikbot.secrets) (defvar *secret-ring* nil "GPG keyring path") (defvar *secret-pass-store* nil "pass store dir") (defvar *secret-pass-bin* "pass" "pass util binary") (defun pass (cmd path &key input (output :string) error-output) (let ((input-stream (when input (make-string-input-stream input)))) (unwind-protect (uiop:run-program (format nil "~@[GNUPGHOME=~A ~]~@[PASSWORD_STORE_DIR=~A ~]~A ~A~@[ ~{~A~^/~}~]" *secret-ring* *secret-pass-store* *secret-pass-bin* cmd path) :input input-stream :output output :error-output error-output) (when input-stream (close input-stream))))) (defun secret-get (path) (handler-case (let ((*read-eval* nil)) (values (read-from-string (pass "show" path)))) (error () (values)))) (defun secret-set (path value) (pass "insert --force --multiline" path :input (prin1-to-string value) :output nil :error-output :string)) (defun secret-del (path) (pass "rm --force" path)) (defun secret-wipe (data) (cond ((stringp data) (fill data #\Space)) ((vectorp data) (fill data 0)) ((consp data) (secret-wipe (car data)) (secret-wipe (cdr data))))) (defmacro with-secret ((var path) &body body) `(let ((,var (ignore-errors (secret-get ,path)))) (unwind-protect (progn ,@body) (secret-wipe ,var))))