Giter VIP home page Giter VIP logo

one-time's Introduction

One Time Password (TOTP and HOTP) library for Clojure.

Clojure CI Clojars Project CljDoc

A Clojure library for generating one time passwords (HOTP & TOTP) as per RFC 4226 and RFC 6238. One time passwords are used by a lot of websites for multi factor / two factor authentication. You can find a list of such websites here.

This library has been tested to be compatible with :

Project Maturity

One-Time is a feature complete and fairly stable library, given the small surface area of it's intent. Bugfixes and dependency updates will be made as required.

Installation

Leiningen:

Add the following to your :dependencies in project.clj.

[one-time "0.8.0"]

Please visit the package listing at Clojars for more options.

Documentation

The frequently used functions (with industry-standard defaults) are present in the one-time.core namespace. However, they build on functions present in other namespaces which can always be overridden or extended as needed. Documentation on functions in individual namespaces (for the latest release) is available at https://suvash.github.io/one-time/

Secret Key Generation

Secret key generation is the first step in being able to use TOTP/HOTP. A function is provided by one-time.core namespace that generates random secret keys compatible with Google Authenticator, Authy and Lastpass Authenticator.

(require '[one-time.core :as ot])

(ot/generate-secret-key)
;= "Z3YMI77OFLQBPNT6"

Time based One-time passwords (TOTP)

TOTP is based on HOTP with a timestamp replacing the incrementing counter. one-time.core namespace provides two functions for working with TOTP, one for getting the TOTP token at a certain time and a predicate function for verifying.

You're most likely to use the predicate function for verifying tokens that you receive from the user (Google Authenticator/Authy/Lastpass Authenticator)

(require '[one-time.core :as ot])

;; Generate the key first
(def secret-key (ot/generate-secret-key))

;; At current time
(ot/get-totp-token secret-key)
;= 885510 - this will be different on your machine as it's based on time

;; Verify at current time and after 30 seconds
;; arguments are token-received, secret-key
(def current-token (ot/get-totp-token secret-key))
(ot/is-valid-totp-token? current-token secret-key)
;= true

;; Wait 30 secs
(Thread/sleep 30000)

;; Verify after 30 secs
;; arguments are token-received, secret-key
(ot/is-valid-totp-token? current-token secret-key)
;= false

;; Verify for the last time step offset, eg. support clock drifts
;; arguments are token-received, secret-key, time-step-offset
(ot/is-valid-totp-token? current-token secret-key {:time-step-offset -1})
;= true

The functions above also accept additional map by which various aspects of TOTP token generation is affected. One can configure them to accept a specific time (instead of current time), a different time step (default 30 secs), a time step offset to support clock drifts (default current time step), and a different HMAC SHA function (instead of HMAC-SHA-1). Feel free to look more into one-time.core and one-time.totp namespace to configure these.

HMAC based One-time passwords (HOTP)

HOTP is also better understood as counter based OTP. one-time.core namespace provides two functions for working with HOTP, one for getting the HOTP token at a certain counter and a predicate function for verifying.

You're most likely to use the predicate function for verifying tokens that you receive from the user. HOTP is not supported by Google Authenticator, Authy or Lastpass Authenticator. (They all support TOTP.)

(require '[one-time.core :as ot])

;; Generate the key first, in this example i'll pick one
(def secret-key "HZSRH7AWHI6JV427")

;; At counter 1
(ot/get-hotp-token secret-key 1)
;= 817667

;; At counter 478
(ot/get-hotp-token secret-key 478)
;= 793369

;; Verify at counter 1567
;; arguments are token-received, secret-key, counter
(ot/is-valid-hotp-token? 446789 secret-key 1567)
;= false

;; Verify at counter 23456
;; arguments are token-received, secret-key, counter
(ot/is-valid-hotp-token? 13085 secret-key 23456)
;= true

TOTP/HOTP URI generation

one-time.uri namespace provides additional functions for generating TOTP and HOTP URIs which can be further embed in QR codes to present to the user. A map is used to configure the URI generation functions.

(require '[one-time.uri  :as oturi])

;; Generate the key first, in this example i'll pick one
(def secret-key "NBCVJLJHRKQEYLAD")

;; TOTP URI generation function accepts a map with following keys (non-optional) to generate the URI
;; :label  = Company.com ---- Name of the secret issuing firm, usually the company name
;; :user   = [email protected] - Name of the user intended to use the secret, usually the email address
;; :secret = secret key " --- Secret Key needed to be used
;;
;;
(oturi/totp-uri {:label "Company.com" :user "[email protected]" :secret secret-key})
;= "otpauth://totp/Company.com:user%40email.com?secret=NBCVJLJHRKQEYLAD&issuer=Company.com"

;; HOTP URI generation function accepts a map with following keys (non-optional) to generate the URI
;; :label  = Company.com ---- Name of the secret issuing firm, usually the company name
;; :user   = [email protected] - Name of the user intended to use the secret, usually the email address
;; :secret = secret key " --- Secret Key needed to be used
;; :counter = 123456 -------- Counter need to be used for HOTP
;;
(oturi/hotp-uri {:label "Company.com" :user "[email protected]" :secret secret-key :counter 123456})
;= "otpauth://hotp/Company.com:user%40email.com?secret=NBCVJLJHRKQEYLAD&issuer=Company.com&counter=123456"

The URIs generated above can then be embedded into QR codes that can be displayed to user.

Working Example

Scan the following barcode with your phone, using Google Authenticator, Authy or Lastpass Authenticator.

QR Code for TOTP

Run the following in a REPL and compare the values on the REPL and your device. Make sure you don't have any clock drifts etc., especially if you're running it inside a VM/container.

(require '[one-time.core :as ot])

;; Use the same secret key as the bar code
(def secret-key "TA4WPSAGBMGXLFVI")

;; Compare these values to the one you get on your device at the same time
(ot/get-totp-token secret-key)

QR Code Image Generation

This library wraps over https://github.com/kenglxn/QRGen, to generate QR code images locally. The generated image can be read as a java.io.File or over a java.io.ByteArrayoutputstream. Optionally, image type (BMP,JPG,PNG,GIF) and image size can be provided.

(require '[one-time.qrgen :as qrgen])

;; Generate the key first, in this example i'll pick one
(def secret-key "HTWU5NFLBMWY2MQS")

;; TOTP QRcode image file generation, default image size(125px square) and type(JPG)
(def qrcode-file (qrgen/totp-file {:image-type :BMP :label "company.org" :user "[email protected]" :secret secret}))

;; TOTP QRcode image stream generation, in GIF
(def qrcode-stream (qrgen/totp-stream {:image-type :GIF :label "company.org" :user "[email protected]" :secret secret}))

;; HOTP QRcode image file generation, 300px square in PNG
(def qrcode-file (qrgen/hotp-file {:image-type :PNG :image-size 300 :label "company.org" :user "[email protected]" :secret secret :counter 123}))

;; HOTP QRcode image stream generation
(def qrcode-stream (qrgen/hotp-stream {:label "company.org" :user "[email protected]" :secret secret :counter 123}))

Supported Clojure Versions

One-Time has been tested to work against Clojure 1.6 and up. The most recent release is always recommended.

Development

If you already have Leiningen on your machine, you should just be able to run lein all test as you would do on other leiningen projects.

As prefered by the author, you can also use the provided Makefile to run the tests. In that case, you'll need the following on your machine

  • GNU Make ( Version 4.0 and up )
  • Docker Engine ( Version 17.06.1 and hopefully upwards )
  • Docker Compose ( Version 1.16.1 and hopefully upwards )
# Get help
$ make help

# Run tests
$ make test

# Stop and cleanup docker instances etc.
# make stop-clean

Then create a branch and make your changes on it. Once you are done with your changes and all tests pass, submit a pull request on GitHub.

Changelog

Please check the CHANGELOG.md graph for now.

Contributors

Please check the contribution graph graph for now.

References

These resources were invaluable towards developing this library.

License

Copyright © 2019 Suvash Thapaliya

Distributed under the Eclipse Public License.

one-time's People

Contributors

loudnl avatar lvh avatar megakorre avatar pyons avatar suvash avatar tampix avatar trevorbernard avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

one-time's Issues

Document the notion of time-steps

Hi Suvash,

first of all thanks for the library! Kudos for the great work.

I'm wondering what the :time-step option does.

Actually, I got a rough idea:

one-time.totp> (counter-since-epoch (Date.) 40)
38697200
one-time.totp> (counter-since-epoch (Date.) 20)
77394401
one-time.totp> (counter-since-epoch (Date.) 30)
51596267

So, it divides the time in chunks.

But I don't know the purpose of this? What would be a use case for setting a :time-step value other than 30? And what's the current effect of the default value of 30?

Thanks - Victor

Error building classpath. Could not find artifact com.github.kenglxn.qrgen:javase:jar:2.6.0 in central

Hello,

I added one-time/one-time {:mvn/version "0.8.0"} to my deps.edn and tried to start my project. It was able to find a bunch of jars but choked on one:

$ clojure -M:dev:cms:nrepl                                                                                                                                                      
Downloading: one-time/one-time/0.8.0/one-time-0.8.0.pom from clojars                                                                                                                                               
Downloading: com/google/zxing/javase/3.5.0/javase-3.5.0.pom from central                                                                                                                                           
Downloading: org/apache/xmlgraphics/batik-svggen/1.15/batik-svggen-1.15.pom from central                                                                                                                           
Downloading: org/apache/xmlgraphics/batik-dom/1.15/batik-dom-1.15.pom from central                                                                                                                                 
Downloading: com/google/zxing/zxing-parent/3.5.0/zxing-parent-3.5.0.pom from central                                                                                                                               
Downloading: org/apache/xmlgraphics/batik/1.15/batik-1.15.pom from central                                                                                                                                         
Downloading: org/apache/xmlgraphics/batik-css/1.15/batik-css-1.15.pom from central                                                                                                                                 
Downloading: org/apache/xmlgraphics/batik-util/1.15/batik-util-1.15.pom from central                                                                                                                               
Downloading: org/apache/xmlgraphics/batik-shared-resources/1.15/batik-shared-resources-1.15.pom from central                                                                                                       
Downloading: org/apache/xmlgraphics/batik-ext/1.15/batik-ext-1.15.pom from central                                                                                                                                 
Downloading: xml-apis/xml-apis/1.4.01/xml-apis-1.4.01.pom from central                                   
Downloading: org/apache/xmlgraphics/batik-xml/1.15/batik-xml-1.15.pom from central                                                                                                                                 
Downloading: xml-apis/xml-apis-ext/1.3.04/xml-apis-ext-1.3.04.pom from central                                                                                                                                     
Downloading: org/apache/xmlgraphics/batik-awt-util/1.15/batik-awt-util-1.15.pom from central                                                                                                                       
Downloading: com/beust/jcommander/1.82/jcommander-1.82.pom from central                                  
Downloading: com/github/jai-imageio/jai-imageio-core/1.4.0/jai-imageio-core-1.4.0.pom from central                                                                                                                 
Downloading: com/google/zxing/core/3.5.0/core-3.5.0.pom from central                                     
Downloading: org/apache/xmlgraphics/xmlgraphics-commons/2.7/xmlgraphics-commons-2.7.pom from central
Downloading: org/apache/xmlgraphics/batik-constants/1.15/batik-constants-1.15.pom from central
Downloading: org/apache/xmlgraphics/batik-i18n/1.15/batik-i18n-1.15.pom from central
Downloading: org/apache/apache/7/apache-7.pom from central
Downloading: commons-logging/commons-logging/1.0.4/commons-logging-1.0.4.pom from central
Downloading: org/apache/xmlgraphics/batik-dom/1.15/batik-dom-1.15.jar from central
Downloading: org/apache/xmlgraphics/batik-xml/1.15/batik-xml-1.15.jar from central
Downloading: org/apache/xmlgraphics/batik-svggen/1.15/batik-svggen-1.15.jar from central
Downloading: org/apache/xmlgraphics/xmlgraphics-commons/2.7/xmlgraphics-commons-2.7.jar from central
Downloading: org/apache/xmlgraphics/batik-css/1.15/batik-css-1.15.jar from central
Downloading: xml-apis/xml-apis-ext/1.3.04/xml-apis-ext-1.3.04.jar from central
Downloading: org/apache/xmlgraphics/batik-constants/1.15/batik-constants-1.15.jar from central
Downloading: com/github/jai-imageio/jai-imageio-core/1.4.0/jai-imageio-core-1.4.0.jar from central
Downloading: commons-logging/commons-logging/1.0.4/commons-logging-1.0.4.jar from central
Downloading: xml-apis/xml-apis/1.4.01/xml-apis-1.4.01.jar from central
Downloading: org/apache/xmlgraphics/batik-shared-resources/1.15/batik-shared-resources-1.15.jar from central
Downloading: org/apache/xmlgraphics/batik-util/1.15/batik-util-1.15.jar from central
Downloading: org/apache/xmlgraphics/batik-awt-util/1.15/batik-awt-util-1.15.jar from central
Downloading: org/apache/xmlgraphics/batik-i18n/1.15/batik-i18n-1.15.jar from central
Downloading: com/google/zxing/core/3.5.0/core-3.5.0.jar from central
Downloading: com/google/zxing/javase/3.5.0/javase-3.5.0.jar from central
Downloading: com/beust/jcommander/1.82/jcommander-1.82.jar from central
Downloading: org/apache/xmlgraphics/batik-ext/1.15/batik-ext-1.15.jar from central
Downloading: one-time/one-time/0.8.0/one-time-0.8.0.jar from clojars
Error building classpath. Could not find artifact com.github.kenglxn.qrgen:javase:jar:2.6.0 in central (https://repo1.maven.org/maven2/)

I found this issue which appears to describe the same problem. However, javase and jitpack are new to me, so I don't quite understand the fix.

[feature request] TOTP support for time-step offset

Hello and thanks for this great library.

In order to accommodate end users who might have clock drift issues (especially common with dedicated hardware MFA device), https://tools.ietf.org/html/rfc6238#section-6 recommends to handle time-steps around the current date.

Currently, the library doesn't offer any OOTB way to handle those drifts.

It can be circumvented in a few ways though :

(ns foo.core
  (:require [one-time.core :as ot])
  (:import java.util.Date))

(def drift-window-limit 1)
(def time-step 30)

;; Validate using date offsets. It works but it's a bit convoluted imo
(comment
  (defn- plus-seconds
    [date seconds]
    (-> date .toInstant (.plusSeconds seconds) Date/from))

  (defn- get-dates-in-window
    [date time-step window]
    (->> (range (- window) (inc window))
         (map #(plus-seconds (* time-step %)))))

  (let [secret-key (ot/generate-secret-key)
        date       (plus-seconds (Date.) (- time-step))
        token      (ot/get-totp-token secret-key {:date date
                                                  :time-step time-step})]
    (->> (get-dates-in-window (Date.) time-step drift-window-limit)
         (some #(ot/is-valid-totp-token? token secret-key {:date %
                                                           :time-step time-step}))))
    ;; => true
    )

;; We could validate using get-hotp-token too, be that would mean
;; reimplementing one-time.totp/counter-since-epoch, which is less
;; than ideal

One way to introduce this feature would be by adding a :time-step-offset in the option map passed to get-totp-token and is-valid-totp-token?. This parameter would then be used to adjust the counter feeded to get-hotp-token by adding this parameter to the result of counter-since-epoch.

That way, it would be pretty trivial for developers to implement :

  • Clock drift synchronization (persist an offset and feed it to the library)
  • Error window (feed -1, 0 and 1 as offsets to the library to accomodate for small clock drifts / slow typing from the end user)
  • Combination of both of the above

Let me know if you're ok with the idea.

get-token should probably return strings, not ints

get-token doesn't always return codes that look like what the user is supposed to type in, sometimes i get 5 or 4 digit codes

(get-token "OV2K2K5SOWKN2RCZ" 1) ;; => 3799

so, I have to do some work to present this to the user (authenticator apps will pad with leading 0s)

also i have to do work to take what my form gives me, and turn that into an int.

there is possibly another problem, clojure does treat numbers with leading 0s differently, i haven't run into this being an issue, but i imagine it could be for some people/situations.

(str 00003123) ;; => "1619"
(str 3123) ;; => "3123"

I think it would be simpler to keep these tokens as strings. below is a proposed change to the get-token function
only the last line has been altered.

(defn get-token
  "Return a HOTP token (HMAC-Based One-Time Password Algorithm)
   based on a secret and a counter, as specified in
   https://tools.ietf.org/html/rfc4226"
  ([secret counter]
   ;; Use HMAC-SHA-1 as default when not provided
   (get-token secret counter :hmac-sha-1))
  ([secret counter hmac-sha-type]
   (let [digest (hmac-sha-digest secret counter hmac-sha-type)
         offset (bit-and (digest 19) 0xf)
         code (bit-or (bit-shift-left (bit-and (digest offset) 0x7f) 24)
                (bit-shift-left (bit-and (digest (+ offset 1)) 0xff) 16)
                (bit-shift-left (bit-and (digest (+ offset 2)) 0xff) 8)
                (bit-and (digest (+ offset 3)) 0xff))]
     (->>
       (rem code 1000000)
       (format "%06d")))))

dependant repos need to be added to deps.edn file

i have to add this to my deps.edn or clojure throws nonsense errors

 :mvn/repos {
             "jitpack" {:url "https://jitpack.io"}
             "sonatype" {:url "https://oss.sonatype.org/content/repositories/releases"
                         :snapshots false
                         :releases {:checksum :fail :update :always}}
             "sonatype-snapshots" {:url "https://oss.sonatype.org/content/repositories/snapshots"
                                   :snapshots true
                                   :releases {:checksum :fail :update :always}}}

i have also tried converting the project.clj to deps.edn in my fork, but i still need to add these to my project that uses this library. maybe this is normal and needs to be added to the docs, or maybe there is a way to eliminate the dependant repos

Please update dependency versions OSV:GHSA-2H63-QP69-FWVW

According to Grype the dependencies batik-util and batik-xml that one-time uses are vulnerable to Server-side request forgery (SSRF). Please provide an update.

OSV:GHSA-2H63-QP69-FWVW

 ✔ Vulnerability DB        [no update available]
 ✔ Indexed target/package.jar
 ✔ Cataloged packages      [112 packages]
 ✔ Scanned image           [2 vulnerabilities]
NAME        INSTALLED  FIXED-IN  TYPE          VULNERABILITY        SEVERITY
batik-util  1.10       1.14      java-archive  GHSA-2h63-qp69-fwvw  High
batik-xml   1.10       1.14      java-archive  GHSA-2h63-qp69-fwvw  High

Dependency tree:

[one-time "0.7.0" :exclusions [[commons-codec]]]
   [com.github.kenglxn.qrgen/javase "2.6.0"]
     [com.github.kenglxn.qrgen/core "2.6.0"]
     [com.google.zxing/javase "3.3.0"]
       [com.beust/jcommander "1.48"]
       [com.github.jai-imageio/jai-imageio-core "1.3.1"]
       [com.google.zxing/core "3.3.0"]
     [org.apache.xmlgraphics/batik-dom "1.10"]
       [org.apache.xmlgraphics/batik-css "1.10"]
       [org.apache.xmlgraphics/batik-ext "1.10"]
       [org.apache.xmlgraphics/batik-xml "1.10"]
       [xalan "2.7.2"]
         [xalan/serializer "2.7.2"]
       [xml-apis/xml-apis-ext "1.3.04"]
       [xml-apis "1.3.04"]
     [org.apache.xmlgraphics/batik-svggen "1.10"]
       [org.apache.xmlgraphics/batik-awt-util "1.10"]
         [org.apache.xmlgraphics/xmlgraphics-commons "2.2"]
       [org.apache.xmlgraphics/batik-util "1.10"]
         [org.apache.xmlgraphics/batik-constants "1.10"]
         [org.apache.xmlgraphics/batik-i18n "1.10"]
   [ring/ring-codec "1.1.2"]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.