From 0419c90c9b4441618c0457646fd9723a44224eb3 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sun, 26 Jan 2025 18:36:38 +0000 Subject: [PATCH] Add some helper functions to interact with pass --- modules/ordo/password-store.scm | 65 +++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 modules/ordo/password-store.scm diff --git a/modules/ordo/password-store.scm b/modules/ordo/password-store.scm new file mode 100644 index 0000000..fefbab2 --- /dev/null +++ b/modules/ordo/password-store.scm @@ -0,0 +1,65 @@ +(define-module (ordo password-store) + #:use-module (ice-9 exceptions) + #:use-module (ice-9 format) + #:use-module (ice-9 popen) + #:use-module ((srfi srfi-1) #:select (last)) + #:use-module ((srfi srfi-9) #:select (define-record-type)) + #:use-module (ordo util read-lines) + #:use-module (ordo util shell-quote) + #:export (make-password-store + get-password + generate-password)) + +(define-exception-type &password-store-error &external-error + make-password-store-error + password-store-error? + (message password-store-error-message) + (cause password-store-error-cause)) + +(define-record-type + (make-password-store dir) + password-store? + (dir password-store-dir)) + +(define (pass-command store . args) + (let ((base-cmd (if (password-store-dir store) + (format #f "env PASSWORD_STORE_DIR=~a pass" (string-shell-quote (password-store-dir store))) + "pass"))) + (string-append base-cmd + " " + (string-join (map string-shell-quote args) " ") + " 2>&1"))) + +(define (get-password store path) + (let* ((command (pass-command store "show" path)) + (port (open-input-pipe command)) + (data (read-lines port)) + (status (close-pipe port))) + (unless (zero? (status:exit-val status)) + (raise-exception (make-password-store-error (format #f "Error getting password ~a" path) data))) + (car data))) + +(define (password-exists? store path) + (and (false-if-exception (get-password store path)) #t)) + +(define* (generate-password store path #:key (overwrite? #f) (password-length 25)) + ;; WARNING: there is a race condition here between checking the password + ;; exists and calling pass generate to create it. We have to pass the + ;; -f option to generate in case we hit this race condition, when pass will prompt + ;; for confirmation to overwrite an existing file. With the -f option, we will + ;; go ahead and overwrite it, which seems the lesser of two evils. + (unless (or overwrite? (not (password-exists? store path))) + (raise-exception (make-password-store-error (format #f "Error generating password ~a" path) + "Password already exists"))) + (let* ((command (pass-command store "generate" "-f" path (number->string password-length))) + (port (open-input-pipe command)) + (data (read-lines port)) + (status (close-pipe port))) + (unless (zero? (status:exit-val status)) + (raise-exception (make-password-store-error (format #f "Error generating password for ~a" path) data))) + (let ((password (last data))) + ;; Pass wraps the generated password in an escape sequence to change the + ;; displayed colour: we strip this from the result. + (define prefix-len (string-length "\x1b[1m\x1b[93m")) + (define suffix-len (string-length "\x1b[0m")) + (substring password prefix-len (- (string-length password) suffix-len)))))