From 2c521439143a55a132fb5659927399e38db889d4 Mon Sep 17 00:00:00 2001 From: rearman Date: Mon, 11 May 2026 14:46:19 -0400 Subject: [PATCH] Bring pass-alike into the main config No reason to treat it as an external package, especially since it had become the only thing in that folder. --- config.org | 179 ++++++++++++++++++++++++++++++++++++++++++----- re/pass-alike.el | 149 --------------------------------------- 2 files changed, 163 insertions(+), 165 deletions(-) delete mode 100644 re/pass-alike.el diff --git a/config.org b/config.org index 5917a5e..f9da8e0 100644 --- a/config.org +++ b/config.org @@ -10,10 +10,10 @@ Set up the correct lexical binding to avoid screaming compiler * Load libraries Libraries Not from the package manager #+BEGIN_SRC emacs-lisp - (dolist (path '("sedit-mouse/" "themes/" "re/")) + (dolist (path '("sedit-mouse/" "themes/")) (add-to-list 'load-path (concat user-emacs-directory path))) - (dolist (file '(pass-alike eng-paper-theme sedit-mouse)) + (dolist (file '(eng-paper-theme sedit-mouse)) (require file)) #+END_SRC * OS specific stuff @@ -834,20 +834,6 @@ Stolen from https://www.masteringemacs.org/article/fixing-mark-commands-transien (keymap-global-set "C-x C-r" #'re/rename-current-buffer-file) -(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)))) - (defun re/get-file-modified-time (filepath) (interactive) (format-time-string "%Y-%m-%d-%H%M" @@ -1077,6 +1063,167 @@ Start from bottom if given a negative line number." (add-hook 'before-save-hook #'whitespace-cleanup) (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) #+END_SRC +* Pass-alike +** Password generation +#+BEGIN_SRC emacs-lisp +(defconst *alphanumerics* + '("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" + "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" + "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" + "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" + "0" "1" "2" "3" "4" "5" "6" "7" "8" "9") + "List of strings of the upper/lower-case English alphabet, and the single-digit numbers.") + +(defconst *special-characters* + '("!" "?" "$" "%" "&" "'" "(" ")" "*" "+" "," "-" "." + "/" ":" ";" "<" "=" ">" "?" "@" "[" "]" "^" "_" "{" + "|" "}" "~") + "List of strings containing the special visible characters excluding \\ and \" .") + +(defun pw-get-char (&optional no-special-chars) + "Select a single character at random to be used in a password. +If `no-special-chars' is t, use only alpha-numeric characters." + (let ((selected-charset (append *alphanumerics* + (when (null no-special-chars) + *special-characters*)))) + (elt selected-charset (random (length selected-charset))))) + +(defun pw-get-chars (password-length &optional no-special-chars) + "Generate a list of characters as strings to be made into a password. +If `no-special-chars' is t, use only alpha-numeric characters." + (append (list (pw-get-char no-special-chars)) + (when (> password-length 1) + (pw-get-chars (- password-length 1) no-special-chars)))) + +(defun generate-password (password-length &optional no-special-chars) + "Combine a list of randomly-selected characters into a single string of length given. +calls `pw-get-chars'. If `no-special-chars' is `t', use only alpha-numeric characters." + (apply #'concat (pw-get-chars password-length no-special-chars))) + +(defun insert-generated-password (&optional password-length) + "Insert a generated password into the current buffer. +Negative password-length (or negative arg) sets `no-special-chars' for `generate-password'." + (interactive "p") + (let ((len (if (or (null password-length) (= 1 (abs password-length))) + 16 + (abs password-length))) + (no-special-p (and (numberp password-length) (< password-length 0)))) + (insert (generate-password len no-special-p)))) +#+END_SRC +** Get a password from a pass(1) entry +Assume the password is on the first line. +#+BEGIN_SRC emacs-lisp +(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)))) + +(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))) +#+END_SRC +** TOTP +Generate TOTP tokens, generate the codes, put them on the clipboard. Assumes the format of pass(1), where the TOTP token is the last line in the file. +#+BEGIN_SRC emacs-lisp +(dolist (requirement '(bindat gnutls hexl auth-source)) + (require requirement)) + +(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-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))) +#+END_SRC + * Control #+BEGIN_SRC emacs-lisp ;; Control diff --git a/re/pass-alike.el b/re/pass-alike.el deleted file mode 100644 index 3ebf7cc..0000000 --- a/re/pass-alike.el +++ /dev/null @@ -1,149 +0,0 @@ -;;; pass-alike.el -*- lexical-binding: t; -*- -;; This file is not part of GNU Emacs. - -;;; Code: - -;; Generate totp code -(dolist (requirement '(bindat gnutls hexl auth-source)) - (require requirement)) - -(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-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))) - -;; Find password from pass(1) entry - -(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))) - -;; Password generation - -(defconst *alphanumerics* - '("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" - "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" - "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" - "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" - "0" "1" "2" "3" "4" "5" "6" "7" "8" "9") - "List of strings of the upper/lower-case English alphabet, and the single-digit numbers.") - -(defconst *special-characters* - '("!" "?" "$" "%" "&" "'" "(" ")" "*" "+" "," "-" "." - "/" ":" ";" "<" "=" ">" "?" "@" "[" "]" "^" "_" "{" - "|" "}" "~") - "List of strings containing the special visible characters excluding \\ and \" .") - -(defun pw-get-char (&optional no-special-chars) - "Select a single character at random to be used in a password. -If `no-special-chars' is t, use only alpha-numeric characters." - (let ((selected-charset (append *alphanumerics* - (when (null no-special-chars) - *special-characters*)))) - (elt selected-charset (random (length selected-charset))))) - -(defun pw-get-chars (password-length &optional no-special-chars) - "Generate a list of characters as strings to be made into a password. -If `no-special-chars' is t, use only alpha-numeric characters." - (append (list (pw-get-char no-special-chars)) - (when (> password-length 1) - (pw-get-chars (- password-length 1) no-special-chars)))) - -(defun generate-password (password-length &optional no-special-chars) - "Combine a list of randomly-selected characters into a single string of length given. -calls `pw-get-chars'. If `no-special-chars' is `t', use only alpha-numeric characters." - (apply #'concat (pw-get-chars password-length no-special-chars))) - -(defun insert-generated-password (&optional password-length) - "Insert a generated password into the current buffer. -Negative password-length (or negative arg) sets `no-special-chars' for `generate-password'." - (interactive "p") - (let ((len (if (or (null password-length) (= 1 (abs password-length))) - 16 - (abs password-length))) - (no-special-p (and (numberp password-length) (< password-length 0)))) - (insert (generate-password len no-special-p)))) - -(provide 'pass-alike) -;;; pass-alike.el ends here -- 2.39.5