summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--kasi-totp.el64
1 files changed, 64 insertions, 0 deletions
diff --git a/kasi-totp.el b/kasi-totp.el
new file mode 100644
index 0000000..3d197d6
--- /dev/null
+++ b/kasi-totp.el
@@ -0,0 +1,64 @@
1;; TOTP code taken from Mastering Emacs which took from Jürgen Hötzel's `totp.el'.
2;; Base32 code taken from same Mastering Emacs article.
3;; https://www.masteringemacs.org/article/securely-generating-totp-tokens-emacs
4;; https://github.com/juergenhoetzel/emacs-totp
5(require 'bindat)
6(require 'gnutls)
7(require 'hexl)
8(require 'auth-source)
9
10(defconst base32-alphabet
11 (let ((tbl (make-char-table nil)))
12 (dolist (mapping '(("A" . 0) ("B" . 1) ("C" . 2) ("D" . 3)
13 ("E" . 4) ("F" . 5) ("G" . 6)
14 ("H" . 7) ("I" . 8) ("J" . 9) ("K" . 10)
15 ("L" . 11) ("M" . 12) ("N" . 13)
16 ("O" . 14) ("P" . 15) ("Q" . 16) ("R" . 17)
17 ("S" . 18) ("T" . 19) ("U" . 20)
18 ("V" . 21) ("W" . 22) ("X" . 23) ("Y" . 24)
19 ("Z" . 25) ("2" . 26) ("3" . 27)
20 ("4" . 28) ("5" . 29) ("6" . 30) ("7" . 31)))
21 (aset tbl (string-to-char (car mapping)) (cdr mapping)))
22 tbl)
23 "Base-32 mapping table, as defined in RFC 4648.")
24
25(defun base32-hex-decode (string)
26 "The cheats' version of base-32 decode.
27
28This is not a 100% faithful implementation of RFC 4648. The
29concept of encoding partial quanta is not implemented fully.
30
31No attempt is made to pad the output either as that is not
32required for HMAC-TOTP."
33 (unless (mod (length string) 8)
34 (error "Padding is incorrect"))
35 (setq string (upcase string))
36 (let ((trimmed-array (append (string-trim-right string "=+") nil)))
37 (format "%X" (seq-reduce
38 (lambda (acc char) (+ (ash acc 5) (aref base32-alphabet char)))
39 trimmed-array 0))))
40
41(defun totp--hex-decode-string (string)
42 "Hex-decode STRING and return the result as a unibyte string."
43 (apply #'unibyte-string
44 (seq-map (lambda (s) (hexl-htoi (aref s 0) (aref s 1)))
45 (seq-partition string 2))))
46
47(defun totp (string &optional time digits)
48 "Return a TOTP token using the secret hex STRING and current time.
49TIME is used as counter value instead of current time, if non-nil.
50DIGITS is the number of pin digits and defaults to 6."
51 (let* ((key-bytes (totp--hex-decode-string (upcase string)))
52 (counter (truncate (/ (or time (time-to-seconds)) 30)))
53 (digits (or digits 6))
54 (format-string (format "%%0%dd" digits))
55 ;; we have to manually split the 64 bit number (u64 not supported in Emacs 27.2)
56 (counter-bytes (bindat-pack '((:high u32) (:low u32))
57 `((:high . ,(ash counter -32)) (:low . ,(logand counter #xffffffff)))))
58 (mac (gnutls-hash-mac 'SHA1 key-bytes counter-bytes))
59 (offset (logand (bindat-get-field (bindat-unpack '((:offset u8)) mac 19) :offset) #xf)))
60 (format format-string
61 (mod
62 (logand (bindat-get-field (bindat-unpack '((:totp-pin u32)) mac offset) :totp-pin)
63 #x7fffffff)
64 (expt 10 digits)))))