Skip to content

Instantly share code, notes, and snippets.

@mmontone
Last active July 13, 2017 16:43
Show Gist options
  • Save mmontone/435a66ebc84daac48de4297e1dd42aa4 to your computer and use it in GitHub Desktop.
Save mmontone/435a66ebc84daac48de4297e1dd42aa4 to your computer and use it in GitHub Desktop.
2FA test - Two Factor Authentication test via FreeOTP and Common Lisp
(ql:quickload :cl-base32)
(ql:quickload :hunchentoot)
(ql:quickload :cl-one-time-passwords)
(ql:quickload :cl-qrencode)
(ql:quickload :cl-pass)
(ql:quickload :babel)
(ql:quickload :flexi-streams)
(ql:quickload :cl-who)
(ql:quickload :ironclad)
(defpackage otp-test
(:use :cl))
(in-package :otp-test)
(setf *random-state* (make-random-state t))
(setf crypto:*prng* (crypto:make-prng :fortuna))
(setf totp:*time-step-in-seconds* 30)
(defun make-otp-secret ()
(ironclad:byte-array-to-hex-string (crypto:random-data 20)))
(defclass user ()
((username :initarg :username
:accessor username
:initform nil)
(password :initarg :password
:accessor password
:initform nil)
(otp-secret :initarg :otp-secret
:accessor otp-secret
:initform nil)))
(defmethod totp-uri ((user user))
(format nil "otpauth://totp/2FATest:~A?secret=~A&issuer=2FATest"
(username user)
(cl-base32:bytes-to-base32
(ironclad:hex-string-to-byte-array
(otp-secret user)))))
(defparameter *user* (make-instance 'user
:username "testuser"
:password (cl-pass:hash "testuser")
:otp-secret (make-otp-secret)))
(hunchentoot:define-easy-handler (qrcode :uri "/qrcode") ()
(setf (hunchentoot:content-type*) "image/png")
(flex:with-output-to-sequence (stream)
(cl-qrencode:encode-png-stream (totp-uri *user*) stream
:level :level-m
:mode :byte)))
(defun call-with-test-page (function)
(who:with-html-output-to-string (html)
(:html
(:header
(:title "2FA FreeOTP test"))
(:body
(:h1 (who:str "2FA FreeOTP test"))
(:p (who:str "This is a FreeOTP test for Two Factor Authentication"))
(:h3 (who:str "Instructions"))
(:ol
(:li (who:str "Install FreeOTP"))
(:li (who:str "Scan the QR code"))
(:li (who:str "Generate tokens in your phone and input here"))
(:li (who:str "Click on \"Test\" button and verify it succeeds")))
(:p (:b (who:str "IMPORTANT NOTE:")) "FreeOTP is very sensible regarding clock times, and tokens are valid for only 30 seconds, so it is very easy to get out of sync with the server. So you should make sure your phone's clock is uptodate")
(:img :src "/qrcode" :width 200 :height 200)
(:form :action "/login" :method :post
(:input :type "text" :name "token")
(:input :type "submit" :value "Test"))
(funcall function html)))))
(hunchentoot:define-easy-handler (test :uri "/") ()
(call-with-test-page (lambda (html))))
(hunchentoot:define-easy-handler (login :uri "/login") ()
(call-with-test-page (lambda (html)
(who:with-html-output (html html)
(if (= (totp:totp (otp-secret *user*))
(parse-integer (hunchentoot:parameter "token")))
(who:htm (:h1 :style "color:green;"
"Success!!"))
(who:htm (:h1 :style "color:red;"
"Failed")))))))
(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 9095))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment