Compare commits
36 Commits
5c26e72e8f
...
v1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 5651cc1ab2 | |||
| 96ed6ae1e9 | |||
| d8c3f5ee67 | |||
| 9eeb3571d5 | |||
| 58a17dc5bd | |||
| 4f29260f9b | |||
| df82cf3f44 | |||
| f32986f2db | |||
| 1205a79f19 | |||
| 38126f987a | |||
| fa0de1f624 | |||
| efec0ca07c | |||
| 4361782861 | |||
| 8d0fb81a9d | |||
| 4a1abd7fd7 | |||
| b7d3c6ce86 | |||
| 0a4e531b2f | |||
| a22f4a5670 | |||
| f408834726 | |||
| 09d8cd0e10 | |||
| d448ebe001 | |||
| e165dc107f | |||
| e18953b287 | |||
| 290c52a71f | |||
| e32e054c8c | |||
| 5eec2f2e25 | |||
| 65aa97d6e9 | |||
| ec6d948645 | |||
| a7e79e0727 | |||
| 67f60f2b5d | |||
| 5dc70b70e8 | |||
| 4390afbaca | |||
| 9eac0fc3a2 | |||
| da9f2d28d1 | |||
| ca88c0b86d | |||
| d7c7bddb67 |
39
.gitignore
vendored
39
.gitignore
vendored
@@ -1,32 +1,7 @@
|
||||
# ---> Clojure
|
||||
pom.xml
|
||||
pom.xml.asc
|
||||
*.jar
|
||||
*.class
|
||||
/lib/
|
||||
/classes/
|
||||
/target/
|
||||
/checkouts/
|
||||
.lein-deps-sum
|
||||
.lein-repl-history
|
||||
.lein-plugins/
|
||||
.lein-failures
|
||||
.nrepl-port
|
||||
.cpcache/
|
||||
|
||||
# ---> Leiningen
|
||||
pom.xml
|
||||
pom.xml.asc
|
||||
*.jar
|
||||
*.class
|
||||
/lib/
|
||||
/classes/
|
||||
/target/
|
||||
/checkouts/
|
||||
.lein-deps-sum
|
||||
.lein-repl-history
|
||||
.lein-plugins/
|
||||
.lein-failures
|
||||
.nrepl-port
|
||||
.cpcache/
|
||||
|
||||
/.clj-kondo/
|
||||
/.cpcache/
|
||||
/.lsp/
|
||||
/target/
|
||||
.nrepl-port
|
||||
.calva
|
||||
|
||||
|
||||
18
LICENSE
18
LICENSE
@@ -1,18 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 ruben
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
41
README.md
41
README.md
@@ -13,22 +13,43 @@ You can red more about the algorith here:
|
||||
- TOTP RFC: https://web.archive.org/web/20110711124823/http://tools.ietf.org/html/rfc6238
|
||||
- HOTP RFC: https://www.ietf.org/rfc/rfc4226.txt
|
||||
|
||||
|
||||
## The inside
|
||||
|
||||
This project is done 100% in clojure. It uses `deps.edn` for configuring the project.
|
||||
|
||||
## Implementation timeline
|
||||
## Features
|
||||
|
||||
### v1.0
|
||||
[ ] Functional TOTP generation
|
||||
[ ] Get TOTP from command line
|
||||
[ ] Store configuration in a simple BD (sqlite, for example)
|
||||
- Functional TOTP generation
|
||||
- Get TOTP from command line
|
||||
- Continuous update every 30 seconds
|
||||
|
||||
### v1.1
|
||||
[ ] REST API
|
||||
[ ] User management
|
||||
## Usage
|
||||
You can use the `clojure` command to run the program:
|
||||
```
|
||||
clojure -M:run <params>
|
||||
```
|
||||
|
||||
### v1.2
|
||||
[ ] Simple web connected to REST API
|
||||
If you prefer using the distributed jar:
|
||||
```
|
||||
java -jar clj-topt-1.0.35-standalone.jar <params>
|
||||
```
|
||||
|
||||
You can use the binary (compiled with GraalVM) in linux environments:
|
||||
```
|
||||
totp <params>
|
||||
```
|
||||
|
||||
All three methods are equivalent.
|
||||
|
||||
### Generate a single TOTP
|
||||
You can simple run:
|
||||
```
|
||||
totp generate <secret in BASE32>
|
||||
```
|
||||
|
||||
If want to update coninously the generated TOTP, you cand add the `-s` param:
|
||||
```
|
||||
totp generate <secret in BASE32> -s
|
||||
```
|
||||
|
||||
|
||||
25
build.clj
Normal file
25
build.clj
Normal file
@@ -0,0 +1,25 @@
|
||||
(ns build
|
||||
(:require [clojure.tools.build.api :as b]))
|
||||
|
||||
(def lib 'es.rcorral/clj-topt)
|
||||
(def version (format "1.0.%s" (b/git-count-revs nil)))
|
||||
(def class-dir "target/classes")
|
||||
(def uber-file (format "target/%s-%s-standalone.jar" (name lib) version))
|
||||
|
||||
;; delay to defer side effects (artifact downloads)
|
||||
(def basis (delay (b/create-basis {:project "deps.edn"})))
|
||||
|
||||
(defn clean [_]
|
||||
(b/delete {:path "target"}))
|
||||
|
||||
(defn uber [_]
|
||||
(clean nil)
|
||||
(b/copy-dir {:src-dirs ["src" "resources"]
|
||||
:target-dir class-dir})
|
||||
(b/compile-clj {:basis @basis
|
||||
:ns-compile '[totp.app]
|
||||
:class-dir class-dir})
|
||||
(b/uber {:class-dir class-dir
|
||||
:uber-file uber-file
|
||||
:basis @basis
|
||||
:main 'totp.app}))
|
||||
1
collect-deps.sh
Executable file
1
collect-deps.sh
Executable file
@@ -0,0 +1 @@
|
||||
~/.sdkman/candidates/java/21.0.2-graalce/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image -jar target/clj-topt-1.0.32-standalone.jar g TUGOBTEHPSCMUCYAT6UPELNWGE -c
|
||||
19
deps.edn
Executable file
19
deps.edn
Executable file
@@ -0,0 +1,19 @@
|
||||
{:paths ["src"]
|
||||
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
|
||||
io.github.clojure/tools.build {:mvn/version "0.10.10"}
|
||||
mvxcvi/alphabase {:mvn/version "3.0.185"} ;; https://github.com/greglook/alphabase
|
||||
cli-matic/cli-matic {:mvn/version "0.5.4"} ;; https://github.com/l3nz/cli-matic
|
||||
;; Native image (GraalVM)
|
||||
com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}};; Tutorial: https://shagunagrawal.me/posts/setup-clojure-with-graalvm-for-native-image/
|
||||
|
||||
:aliases {;; Execute the app
|
||||
:run {:main-opts ["-m" "totp.app"]}
|
||||
|
||||
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
||||
:test {:extra-paths ["test"] ;; https://cljdoc.org/d/uberdeps/uberdeps/1.4.0/doc/readme
|
||||
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
|
||||
:main-opts ["-m" "kaocha.runner"]}
|
||||
|
||||
;; Run with clj -T:build function-in-build
|
||||
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||
:ns-default build}}}
|
||||
34
doc/db.plantuml
Normal file
34
doc/db.plantuml
Normal file
@@ -0,0 +1,34 @@
|
||||
@startuml
|
||||
' configuration
|
||||
skinparam linetype ortho
|
||||
|
||||
entity "user" as user {
|
||||
id: number
|
||||
--
|
||||
login: varchar(64)
|
||||
passw: varchar(512)
|
||||
active: shortint
|
||||
desc: varchar(512)
|
||||
config: varchar(512)
|
||||
}
|
||||
|
||||
entity "app" as app {
|
||||
id: number
|
||||
--
|
||||
name: varchar(32)
|
||||
desc: varchar(512)
|
||||
secret: varchar(512)
|
||||
period: int
|
||||
config: varchar(512)
|
||||
}
|
||||
|
||||
entity "user_app" as user_app {
|
||||
user_id: number
|
||||
app_id: number
|
||||
--
|
||||
}
|
||||
|
||||
user ||--o{ user_app
|
||||
app ||--o{ user_app
|
||||
|
||||
@enduml
|
||||
BIN
doc/db.png
Normal file
BIN
doc/db.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
14
native.sh
Executable file
14
native.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
NATIVE=~/.sdkman/candidates/java/21.0.2-graalce/bin/native-image
|
||||
UBERJAR=clj-topt-1.0.32-standalone.jar
|
||||
BIN_FILE=totp
|
||||
|
||||
echo "Creating uberjar"
|
||||
clojure -T:build uber
|
||||
|
||||
echo "Creating native image"
|
||||
|
||||
$NATIVE -jar target/$UBERJAR -o target/$BIN_FILE -H:+ReportExceptionStackTraces --features=clj_easy.graal_build_time.InitClojureClasses --report-unsupported-elements-at-runtime --verbose --no-fallback -H:ReflectionConfigurationFiles=./reflect_config.json
|
||||
|
||||
echo "Executable created on target/$BIN_FILE"
|
||||
52
reflect_config.json
Normal file
52
reflect_config.json
Normal file
@@ -0,0 +1,52 @@
|
||||
[
|
||||
{
|
||||
"name": "com.sun.crypto.provider.HmacSHA1",
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "java.lang.reflect.Method",
|
||||
"methods": [
|
||||
{
|
||||
"name": "canAccess",
|
||||
"parameterTypes": [
|
||||
"java.lang.Object"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "java.util.Arrays",
|
||||
"allDeclaredClasses": true,
|
||||
"allPublicClasses": true,
|
||||
"queryAllPublicMethods": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "copyOfRange",
|
||||
"parameterTypes": [
|
||||
"byte[]",
|
||||
"int",
|
||||
"int"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "java.util.Timer",
|
||||
"queryAllPublicMethods": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "scheduleAtFixedRate",
|
||||
"parameterTypes": [
|
||||
"java.util.TimerTask",
|
||||
"long",
|
||||
"long"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
64
src/totp/app.clj
Normal file
64
src/totp/app.clj
Normal file
@@ -0,0 +1,64 @@
|
||||
(ns totp.app
|
||||
(:require [totp.core :refer :all]
|
||||
[cli-matic.core :refer [run-cmd]]
|
||||
[cli-matic.utils :as U]
|
||||
[clojure.pprint :as pp])
|
||||
(:import [java.util TimerTask Timer])
|
||||
(:gen-class))
|
||||
|
||||
|
||||
(defn- print-confinuous
|
||||
([secret] (print-confinuous secret 30))
|
||||
([secret step]
|
||||
(let [step-millis (* 1000 step)
|
||||
now (System/currentTimeMillis)
|
||||
delay (int (- step-millis (rem now step-millis)))
|
||||
fn-show (fn [s] (println (System/currentTimeMillis) "-> "(get-otp s)))
|
||||
task (proxy [TimerTask] []
|
||||
(run [] (fn-show secret)))]
|
||||
(println "\n <Generating continuosly, press enter to stop>\n")
|
||||
;; (println "Now:" now ", Delay:" delay ", Next execution: " (+ now delay))
|
||||
(fn-show secret)
|
||||
(. (new Timer) (scheduleAtFixedRate task delay step-millis)))
|
||||
(read-line))) ;; Waits for a key press
|
||||
|
||||
(comment
|
||||
(print get-otp "MJXW42LBORXQ====")
|
||||
(print-confinuous "MJXW42LBORXQ====")
|
||||
)
|
||||
|
||||
(defn cmd-generate
|
||||
[& {:keys [secret continuous] :as otps}]
|
||||
;(pp/pprint otps)
|
||||
(if continuous
|
||||
(print-confinuous secret)
|
||||
(println (get-otp secret))
|
||||
))
|
||||
|
||||
|
||||
(def cli-options
|
||||
{:app {:command "totp"
|
||||
:version "1.0"
|
||||
:description ["Generate a TOTP"]}
|
||||
|
||||
:commands [{:command "generate" :short "g"
|
||||
:description "Generate one TOTP with a given secret in BASE32"
|
||||
:examples ["Generate one TOTP and exit:"
|
||||
" totp generate \"MJXW42LBORXQ====\""
|
||||
"Generate one TOTP, update each 30 seconds:"
|
||||
" totp g -c \"MJXW42LBORXQ====\""]
|
||||
:opts [{:option "secret"
|
||||
:short 0
|
||||
:as "Secret codified in BASE32"
|
||||
:type :string
|
||||
:default :present}
|
||||
{:option "continuous"
|
||||
:short "c"
|
||||
:type :with-flag
|
||||
:as "Contiuous mode"
|
||||
:default false}]
|
||||
:runs cmd-generate}]})
|
||||
|
||||
|
||||
(defn -main [& args]
|
||||
(run-cmd args cli-options))
|
||||
88
src/totp/core.clj
Normal file
88
src/totp/core.clj
Normal file
@@ -0,0 +1,88 @@
|
||||
(ns totp.core
|
||||
(:require [alphabase.base32 :as b32])
|
||||
(:import (javax.crypto Mac)
|
||||
(javax.crypto.spec SecretKeySpec)
|
||||
(java.util Base64 Arrays)
|
||||
(java.nio ByteBuffer)))
|
||||
|
||||
(def ^:private byte-array-type (type (.getBytes "")))
|
||||
|
||||
(defn timestamp->steps
|
||||
"Converts from UNIX timestamp in milliseconds to a number os steps of 's' seconds of duration"
|
||||
[time, step-size]
|
||||
(if (or (nil? time) (nil? step-size) (zero? step-size))
|
||||
0
|
||||
(int (quot time (* 1000 step-size)))))
|
||||
|
||||
|
||||
(defn bytes-array?
|
||||
"Return true if x is a byte[]"
|
||||
[x]
|
||||
(= byte-array-type (type x)))
|
||||
|
||||
|
||||
(defmulti hmac-sha1
|
||||
"Generates an HMAC-SHA1. The key and the message can be (both) string or array of bytes, nil otherwise"
|
||||
(fn [key message]
|
||||
(cond
|
||||
(and (string? key) (string? message)) :string
|
||||
(and (bytes-array? key) (bytes-array? message)) :byte
|
||||
:else :nil)))
|
||||
|
||||
;; By default
|
||||
(defmethod hmac-sha1 :nil [_ _]
|
||||
nil)
|
||||
|
||||
;; When key and message are strings
|
||||
(defmethod hmac-sha1 :string [key message]
|
||||
(if (or (empty? key) (empty? message))
|
||||
""
|
||||
(let [mac (doto (Mac/getInstance "HmacSHA1") (.init (SecretKeySpec. (.getBytes key) "HmacSHA1")))
|
||||
hmac-bytes (.doFinal mac (.getBytes message))]
|
||||
;; Return the Base64 encoded HMAC
|
||||
(.encodeToString (Base64/getEncoder) hmac-bytes))))
|
||||
|
||||
;; When key and message are arrays of bytes
|
||||
(defmethod hmac-sha1 :byte [key message]
|
||||
(if (nil? message)
|
||||
(bytes (byte-array 0))
|
||||
(let [mac (doto (Mac/getInstance "HmacSHA1") (.init (SecretKeySpec. key "HmacSHA1")))
|
||||
hmac-bytes (.doFinal mac message)]
|
||||
;; Return the Base64 encoded HMAC
|
||||
(Base64/getEncoder) hmac-bytes)))
|
||||
|
||||
|
||||
(defn long->bytes
|
||||
"Converts a long to an array of 8 bytes"
|
||||
[l]
|
||||
;;Java equivalent: ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(someLong).array();
|
||||
(when (integer? l)
|
||||
(-> (ByteBuffer/allocate (/ Long/SIZE Byte/SIZE))
|
||||
(.putLong l)
|
||||
(.array))))
|
||||
|
||||
|
||||
(defn bytes->int
|
||||
"Converts an array of 4 bytes to an integer"
|
||||
[bytes]
|
||||
;;Java equivalent: ByteBuffer.wrap(data).getInt()
|
||||
(when (some? bytes)
|
||||
(.getInt (ByteBuffer/wrap bytes))))
|
||||
|
||||
|
||||
(defn get-otp
|
||||
"Generate an OTP with the given secret (in base32) for the specified timestep"
|
||||
([secret step]
|
||||
(when (and secret step)
|
||||
(let [k (b32/decode secret)
|
||||
c (long->bytes step)
|
||||
hs (hmac-sha1 k c)
|
||||
offset (bit-and (get hs (dec (count hs))) 0x0f) ;; int offset = hs[hs.length-1] & 0xf;
|
||||
chunk (Arrays/copyOfRange hs offset (+ offset 4)) ;(take 4 (drop offset hs)) ;; byte[] chunk = Arrays.copyOfRange(hs, offset, offset+4)
|
||||
]
|
||||
(format "%06d" (-> chunk
|
||||
(bytes->int)
|
||||
(bit-and 0x7fffffff)
|
||||
(rem 1000000))))))
|
||||
([secret]
|
||||
(get-otp secret (timestamp->steps (System/currentTimeMillis) 30))))
|
||||
106
src/totp/data.clj
Normal file
106
src/totp/data.clj
Normal file
@@ -0,0 +1,106 @@
|
||||
(ns totp.data
|
||||
(:require [clojure.edn :as e]
|
||||
[clojure.string :as str]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.pprint :as pp]))
|
||||
|
||||
(defn join-path
|
||||
"Joins several subpaths using system's path separator (/ un *NIX and \\ in windows)"
|
||||
[& col]
|
||||
(str/join java.io.File/separator col))
|
||||
|
||||
|
||||
(def cfg-path (join-path (System/getProperty "user.home") ".config" "totp"))
|
||||
|
||||
(def cfg-file (join-path cfg-path "apps.edn"))
|
||||
|
||||
(def cfg-header ";; clj-totp configuration file
|
||||
;; This file contents a list of maps with :name and :secret entries
|
||||
;; Secrets must be encoded in BASE32
|
||||
|
||||
")
|
||||
|
||||
|
||||
(defn exists-config
|
||||
"Checks if the config file exists"
|
||||
[]
|
||||
(.exists (io/file cfg-file)))
|
||||
|
||||
|
||||
(defn create-cfg-file
|
||||
"Creates the config file"
|
||||
[]
|
||||
(println "Creating " cfg-file)
|
||||
(io/delete-file cfg-file true)
|
||||
(io/make-parents cfg-file)
|
||||
(spit cfg-file cfg-header)
|
||||
true)
|
||||
|
||||
|
||||
(defn create-cfg?
|
||||
"Create configuration file if not exists. Overridable with allways = true"
|
||||
([] (create-cfg? false))
|
||||
([allways]
|
||||
(if (or allways (not (exists-config)))
|
||||
(create-cfg-file)
|
||||
false)))
|
||||
|
||||
(comment
|
||||
(exists-config)
|
||||
(create-cfg?)
|
||||
)
|
||||
|
||||
|
||||
(defn load-config
|
||||
"Loads configuration from file"
|
||||
[]
|
||||
(e/read-string (slurp cfg-file)))
|
||||
|
||||
|
||||
(defn store-config
|
||||
"Store configuration to file"
|
||||
[cfg]
|
||||
(when cfg
|
||||
(spit cfg-file (str cfg-header (with-out-str
|
||||
(binding [pp/*print-right-margin* 50]
|
||||
(pp/pprint cfg)))))))
|
||||
|
||||
|
||||
(defn delete-app
|
||||
[cfg name]
|
||||
(filter #(not= name (:name %)) cfg))
|
||||
|
||||
|
||||
(defn add-app
|
||||
[cfg name secret]
|
||||
(conj (delete-app cfg name) {:name name :secret secret}))
|
||||
|
||||
|
||||
(defn list-apps
|
||||
[cfg]
|
||||
(map :name cfg))
|
||||
|
||||
|
||||
(defn get-app
|
||||
[cfg name]
|
||||
(first (filter #(= name (:name %)) cfg)))
|
||||
|
||||
|
||||
(comment
|
||||
(exists-config)
|
||||
(create-cfg?)
|
||||
(load-config)
|
||||
|
||||
(with-out-str
|
||||
(binding [pp/*print-right-margin* 50]
|
||||
(pp/pprint [{:name "abc" :secret "def"} {:name "my-app" :secret "abc123"}])))
|
||||
|
||||
(store-config [{:name "abc" :secret "def"} {:name "my-app" :secret "abc123"}])
|
||||
|
||||
(-> nil
|
||||
(add-app "app1" "abc123abc123")
|
||||
(add-app "app2" "abc123abc123")
|
||||
(add-app "app1" "123456789012")
|
||||
(store-config))
|
||||
|
||||
)
|
||||
71
test/totp/core_test.clj
Normal file
71
test/totp/core_test.clj
Normal file
@@ -0,0 +1,71 @@
|
||||
(ns totp.core-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[totp.core :refer :all]
|
||||
[alphabase.base64 :as b64])
|
||||
(:import (java.util Arrays)))
|
||||
|
||||
(deftest timestamp->steps-test
|
||||
(testing "Border cases"
|
||||
(is (zero? (timestamp->steps nil nil)))
|
||||
(is (zero? (timestamp->steps 0 nil)))
|
||||
(is (zero? (timestamp->steps nil 0)))
|
||||
(is (zero? (timestamp->steps 0 0))))
|
||||
(testing "Common usage"
|
||||
(is (= 10 (timestamp->steps 100000 10)))
|
||||
(is (= 10 (timestamp->steps 100001 10)))
|
||||
(is (= 10 (timestamp->steps 100999 10)))
|
||||
(is (= 11 (timestamp->steps 110000 10)))
|
||||
(is (= 2 (timestamp->steps 63000 30)))))
|
||||
|
||||
(deftest bytes-array?-test
|
||||
(testing "All cases"
|
||||
(is (= false (bytes-array? nil)))
|
||||
(is (= false (bytes-array? "")))
|
||||
(is (= false (bytes-array? [0x0])))
|
||||
(is (= true (bytes-array? (.getBytes ""))))
|
||||
(is (= true (bytes-array? (bytes (byte-array [0 0 0 0 0 0 0 0])))))))
|
||||
|
||||
(deftest hmac-sha1-test
|
||||
(testing "border cases"
|
||||
(is (= nil (hmac-sha1 nil nil)))
|
||||
(is (= nil (hmac-sha1 "" nil)))
|
||||
(is (= nil (hmac-sha1 nil "")))
|
||||
(is (= nil (hmac-sha1 (.getBytes "") nil)))
|
||||
(is (= nil (hmac-sha1 nil (.getBytes ""))))
|
||||
(is (= "" (hmac-sha1 "" ""))))
|
||||
(testing "String params"
|
||||
(is (= "63h3K4sN+c3NDEl3EGeA23jq/EY=" (hmac-sha1 "12345" "this is a message")))
|
||||
(is (= "MA+ieo7t7MeQfyZR/X52dB1aXDI=" (hmac-sha1 "12345" "this is a longer message
|
||||
with some lines"))))
|
||||
(testing "byte[] params"
|
||||
(is (Arrays/equals (b64/decode "63h3K4sN+c3NDEl3EGeA23jq/EY=") (hmac-sha1 (.getBytes "12345") (.getBytes "this is a message"))))
|
||||
(is (Arrays/equals (b64/decode "MA+ieo7t7MeQfyZR/X52dB1aXDI=") (hmac-sha1 (.getBytes "12345") (.getBytes "this is a longer message
|
||||
with some lines"))))))
|
||||
|
||||
|
||||
(deftest long->bytes-test
|
||||
(testing "Border cases"
|
||||
(is (nil? (long->bytes nil))))
|
||||
(testing "Common usage"
|
||||
(is (Arrays/equals (bytes (byte-array [0 0 0 0 0 0 0 0])) (long->bytes 0)))
|
||||
(is (Arrays/equals (bytes (byte-array [0 0 0 0 0 0 0x01 0x01])) (long->bytes 257)))))
|
||||
|
||||
|
||||
(deftest bytes->int-test
|
||||
(testing "Border cases"
|
||||
(is (nil? (bytes->int nil))))
|
||||
(testing "Common usage"
|
||||
(is (= 0 (bytes->int (bytes (byte-array [0 0 0 0])))))
|
||||
(is (= 1 (bytes->int (bytes (byte-array [0 0 0 0x01])))))
|
||||
(is (= 257 (bytes->int (bytes (byte-array [0 0 0x01 0x01])))))))
|
||||
|
||||
|
||||
(deftest get-otp-test
|
||||
(testing "Border cases"
|
||||
(is (nil? (get-otp nil nil)))
|
||||
(is (nil? (get-otp "" nil)))
|
||||
(is (nil? (get-otp nil "")))
|
||||
(is (nil? (get-otp nil 1000))))
|
||||
(testing "Common usage"
|
||||
(is (= "837552" (get-otp "MJXW42LBORXQ====" 10000)))
|
||||
(is (= 6 (count (get-otp "MJXW42LBORXQ===="))))))
|
||||
Reference in New Issue
Block a user