]> git.earman.xyz Git - emacsinit.git/commitdiff
Add in pass-alike password and otp code management
authorrearman <rearman@r717.net>
Wed, 17 Sep 2025 16:58:07 +0000 (12:58 -0400)
committerrearman <rearman@r717.net>
Wed, 17 Sep 2025 16:58:07 +0000 (12:58 -0400)
Uses the same directory, format, and files as default pass(1), and uses system
gpg setup via built-in easypg.  Defuns to get passwords, generate otp codes, and
to copy them to clipboard.  Entry is as simple as editing the file, with easypg
taking care of the rest.

init.el
re/re-editing-defuns.el
re/re-pass-alike.el [new file with mode: 0644]

diff --git a/init.el b/init.el
index a4e8291e6acf70674c91f6dfc8d8fb29a4f818cb..c23520dd03e71148a85a30e88bcc86a79f062ce0 100644 (file)
--- a/init.el
+++ b/init.el
@@ -21,7 +21,8 @@
 (add-hook 'after-init-hook
          (lambda ()
            (require 're-repl-defuns)
-           (require 're-howm-defuns)))
+           (require 're-howm-defuns)
+           (require 're-pass-alike)))
 
 (add-to-list 'auto-mode-alist '("\\.keymap\\'" . c-mode))
 (add-hook 'c-mode-hook (lambda () (require 're-c-style)))
index 902b148f8a7228f54193d166d581a9abd7abff01..1aa35574cfa154273a02360ab9bcddecfb98b917 100644 (file)
@@ -171,5 +171,19 @@ Stolen from emacsredux.com."
        (abort-recursive-edit))
     (keyboard-quit)))
 
+(defun re/get-line-from-file (file-path line-number)
+  "Return the specified LINE-NUMBER from FILE-PATH as a string.
+Start from bottom if given a negative line number."
+  (with-temp-buffer
+    (insert-file-contents file-path)
+    (if (< line-number 0)
+       (progn
+         (goto-char (point-max))
+         (forward-line line-number))
+      (progn
+       (goto-char (point-min))
+       (forward-line (1- line-number))))
+    (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
+
 (provide 're-editing-defuns)
 ;;; re-editing-defuns.el ends here
diff --git a/re/re-pass-alike.el b/re/re-pass-alike.el
new file mode 100644 (file)
index 0000000..b9550fb
--- /dev/null
@@ -0,0 +1,102 @@
+;;; re-pass-alike.el -*- lexical-binding: t; -*-
+;; This file is not part of GNU Emacs.
+
+;;; Code:
+
+(require 'bindat)
+(require 'gnutls)
+(require 'hexl)
+(require 'auth-source)
+
+(defun totp--hex-decode-string (string)
+  "Hex-decode STRING and return the result as a unibyte string."
+  (apply #'unibyte-string
+        (seq-map (lambda (s) (hexl-htoi (aref s 0) (aref s 1)))
+                 (seq-partition string 2))))
+
+(defun totp (string &optional time digits)
+  "Return a TOTP token using the secret hex STRING and current time.
+TIME is used as counter value instead of current time, if non-nil.
+DIGITS is the number of pin digits and defaults to 6."
+  (let* ((key-bytes (totp--hex-decode-string (upcase string)))
+        (counter (truncate (/ (or time (time-to-seconds)) 30)))
+        (digits (or digits 6))
+        (format-string (format "%%0%dd" digits))
+        ;; we have to manually split the 64 bit number (u64 not supported in Emacs 27.2)
+        (counter-bytes (bindat-pack  '((:high u32) (:low u32))
+                                     `((:high . ,(ash counter -32)) (:low . ,(logand counter #xffffffff)))))
+        (mac (gnutls-hash-mac 'SHA1 key-bytes counter-bytes))
+        (offset (logand (bindat-get-field (bindat-unpack '((:offset u8)) mac 19) :offset) #xf)))
+    (format format-string
+           (mod
+            (logand (bindat-get-field (bindat-unpack '((:totp-pin u32)) mac  offset) :totp-pin)
+                    #x7fffffff)
+            (expt 10 digits)))))
+
+(defconst base32-alphabet
+  (let ((tbl (make-char-table nil)))
+    (dolist (mapping '(("A" . 0) ("B" . 1) ("C" . 2) ("D" . 3)
+                      ("E" . 4) ("F" . 5) ("G" . 6)
+                      ("H" . 7) ("I" . 8) ("J" . 9) ("K" . 10)
+                      ("L" . 11) ("M" . 12) ("N" . 13)
+                      ("O" . 14) ("P" . 15) ("Q" . 16) ("R" . 17)
+                      ("S" . 18) ("T" . 19) ("U" . 20)
+                      ("V" . 21) ("W" . 22) ("X" . 23) ("Y" . 24)
+                      ("Z" . 25) ("2" . 26) ("3" . 27)
+                      ("4" . 28) ("5" . 29) ("6" . 30) ("7" . 31)))
+      (aset tbl (string-to-char (car mapping)) (cdr mapping)))
+    tbl)
+  "Base-32 mapping table, as defined in RFC 4648.")
+
+(defun base32-hex-decode (string)
+  "The cheats' version of base-32 decode.
+
+This is not a 100% faithful implementation of RFC 4648. The
+concept of encoding partial quanta is not implemented fully.
+
+No attempt is made to pad the output either as that is not
+required for HMAC-TOTP."
+  (unless (mod (length string) 8)
+    (error "Padding is incorrect"))
+  (setq string (upcase string))
+  (let ((trimmed-array (append (string-trim-right string "=+") nil)))
+    (format "%X" (seq-reduce
+                 (lambda (acc char) (+ (ash acc 5) (aref base32-alphabet char)))
+                 trimmed-array 0))))
+
+(defun keyfile-string (filename)
+  "Return a string which is the filepath to a `pass(1)' file."
+  (let ((directory "~/.password-store/"))
+    (if (null filename)
+       (read-file-name "Select a password: " directory nil t nil)
+      (concat directory
+             (if (stringp filename)
+                 filename
+               (symbol-name filename))
+             ".gpg"))))
+
+(defun get-password (&optional filename)
+  "Return a password given a file where it is stored.
+Assumes same structure as `pass(1)'."
+  (re/get-line-from-file (keyfile-string filename) 1))
+
+(defun password-to-clipboard (&optional filename)
+  "Copy the returned password to the clipboard."
+  (interactive)
+  (kill-new (get-password filename)))
+
+(defun get-totp (&optional filename)
+  "Generate a totp code given a file with the secret hex string.
+If no filename given, prompt for one.
+Assumes the file has the string by itself on the last line of the file,
+similar to what pass-otp does, but without the full uri.
+Also assumes the file is in `~/.password-store' and has the `.gpg' extension."
+  (totp (base32-hex-decode (re/get-line-from-file (keyfile-string filename) -1))))
+
+(defun otp-to-clipboard (&optional filename)
+  "Copy the returned totp code to the clipboard."
+  (interactive)
+  (kill-new (get-totp filename)))
+
+(provide 're-pass-alike)
+;;; re-pass-alike.el ends here