Compare commits
28 Commits
3a6fd107c0
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 590f14d101 | |||
| d824b3ef72 | |||
| 5b1c375934 | |||
| 7080ea74fb | |||
| 97d1d6e59b | |||
| 9a858c8993 | |||
| e003288004 | |||
| d2c97bd5e5 | |||
| 1ec2db9583 | |||
| b8cd76b481 | |||
| 7380362280 | |||
| 34a365960a | |||
| a52070dfa6 | |||
| 76510be028 | |||
| a1fec08cc4 | |||
| 556cc85cde | |||
| 2ebac1676f | |||
| b6749bdb29 | |||
| 1b141173cc | |||
| 06174de597 | |||
| e6523e0a7b | |||
| 78da3c37c0 | |||
| 3d305a0d70 | |||
| 6166e930fe | |||
| 5edbfa4ce4 | |||
| 1071d9e5ee | |||
| 017291f784 | |||
| 17a7a09ab0 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
/.clj-kondo/
|
/.clj-kondo/
|
||||||
/.cpcache/
|
/**/.cpcache/
|
||||||
/.lsp/
|
/.lsp/
|
||||||
/target/
|
/target/
|
||||||
.nrepl-port
|
.nrepl-port
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ clj-totp.sh import <alias> "<url>"
|
|||||||
- [x] Native compilation script corrections
|
- [x] Native compilation script corrections
|
||||||
|
|
||||||
### v2
|
### v2
|
||||||
|
- [x] Restructurate as a multiproject
|
||||||
- [ ] REST API
|
- [ ] REST API
|
||||||
- [ ] User management
|
- [ ] User management
|
||||||
- [ ] Robust BD backend (H2, datomic, or similar)
|
- [ ] Robust BD backend (H2, datomic, or similar)
|
||||||
@@ -176,14 +177,14 @@ The first step is to install Java JDK, version 11 or newer (version 21 recommend
|
|||||||
|
|
||||||
To execute manually the main function, simple use the `:run` alias:
|
To execute manually the main function, simple use the `:run` alias:
|
||||||
|
|
||||||
```clojure
|
```bash
|
||||||
clojure -M:run <commands and parameters>
|
clojure -M:run/cli <commands and parameters>
|
||||||
```
|
```
|
||||||
|
|
||||||
To build the uberjar:
|
To build the uberjar:
|
||||||
|
|
||||||
```clojure
|
```bash
|
||||||
clojure -T:build uber
|
clojure -T:build :uber/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
There is a utility script to build a native executable using Graal VM. Please, edit the script and
|
There is a utility script to build a native executable using Graal VM. Please, edit the script and
|
||||||
|
|||||||
121
build.clj
121
build.clj
@@ -1,62 +1,91 @@
|
|||||||
(ns build
|
(ns build
|
||||||
(:refer-clojure :exclude [test])
|
(:refer-clojure :exclude [test])
|
||||||
(:require [clojure.tools.build.api :as b]
|
(:require [clojure.tools.build.api :as b]
|
||||||
[clojure.java.io :as io]))
|
[clojure.java.io :as io]
|
||||||
|
[clojure.pprint :as pp]
|
||||||
|
[clojure.java.basis :as basis]))
|
||||||
|
|
||||||
(def lib-group "es.rcorral")
|
(def lib-group "es.rcorral")
|
||||||
(def artifact-prefix "clj-totp")
|
(def artifact-prefix "clj-totp")
|
||||||
|
(def subprojs-base "projects")
|
||||||
(def curr-version (format "2.0.%s" (b/git-count-revs nil)))
|
(def curr-version (format "2.0.%s" (b/git-count-revs nil)))
|
||||||
|
|
||||||
|
|
||||||
;; Builds artifact's full descriptor for each subproject
|
;; Builds artifact's full descriptor for each subproject
|
||||||
(defn lib [subproj]
|
(defn lib [subproj]
|
||||||
(symbol (str lib-group "/" artifact-prefix "-" subproj )))
|
(symbol (str lib-group "/" artifact-prefix "-" subproj )))
|
||||||
|
|
||||||
|
|
||||||
;; Basis for each subproject, using their own deps.edn
|
;; Basis for each subproject, using their own deps.edn
|
||||||
|
;; Injects :extra-deps from :build as additional dependencies
|
||||||
(defn basis [subproj]
|
(defn basis [subproj]
|
||||||
;(b/create-basis {:project (str subproj "/deps.edn")}))
|
(delay (b/create-basis {:project (str subprojs-base "/" subproj "/deps.edn")
|
||||||
(delay (b/create-basis {:project (str "projects/" subproj "/deps.edn")})))
|
;; Inject extra deps as deps
|
||||||
|
:extra {:deps (get-in (basis/initial-basis) [:aliases :build :extra-deps])}
|
||||||
|
})))
|
||||||
|
|
||||||
|
|
||||||
|
;; Show basis generated for a subproject
|
||||||
|
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
|
||||||
|
(defn show-basis [subproj]
|
||||||
|
(println (with-out-str
|
||||||
|
(pp/pprint
|
||||||
|
@(basis subproj)
|
||||||
|
;(basis/initial-basis)
|
||||||
|
))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(pp/pprint (keys (basis/initial-basis)))
|
||||||
|
(pp/pprint (:deps (basis/initial-basis)))
|
||||||
|
(pp/pprint (:libs (basis/initial-basis)))
|
||||||
|
(pp/pprint (sort (keys (:aliases (basis/initial-basis)))))
|
||||||
|
(get-in (basis/initial-basis) [:aliases :build :extra-deps])
|
||||||
|
)
|
||||||
|
|
||||||
;; Target dir for each subproject
|
;; Target dir for each subproject
|
||||||
(defn target-dir [subproj]
|
(defn target-dir [subproj]
|
||||||
(str "target/" subproj))
|
(str "target/" subproj))
|
||||||
|
|
||||||
|
|
||||||
;; Path for compiled classes
|
;; Path for compiled classes
|
||||||
(defn class-dir [subproj]
|
(defn class-dir [subproj]
|
||||||
(str (target-dir subproj) "/" "classes"))
|
(str (target-dir subproj) "/" "classes"))
|
||||||
|
|
||||||
|
|
||||||
;; Jar file for each subproject. :uber type adds -standalone suffix
|
;; Jar file for each subproject. :uber type adds -standalone suffix
|
||||||
(defn jar-file [subproj version type]
|
(defn jar-file [subproj version type]
|
||||||
(format "target/%s-%s-%s%s.jar" artifact-prefix subproj version
|
(format "target/%s-%s-%s%s.jar" artifact-prefix subproj version
|
||||||
(if (= type :uber) "-standalone" "")))
|
(if (= type :uber) "-standalone" "")))
|
||||||
|
|
||||||
|
|
||||||
;; Clean target dir for subproject
|
;; Clean target dir for subproject
|
||||||
(defn clean [{:keys [subproj]}]
|
(defn clean [{:keys [subproj]}]
|
||||||
(b/delete {:path (target-dir subproj)})
|
(b/delete {:path (target-dir subproj)})
|
||||||
(println "Project" subproj "cleaned"))
|
(println "Project" subproj "cleaned"))
|
||||||
|
|
||||||
|
|
||||||
;; Compile java classes, only if java subdir exists
|
;; Compile java classes, only if java subdir exists
|
||||||
(defn compile-java [subproj]
|
(defn compile-java [subproj]
|
||||||
(let [java-dir (str "projects/" subproj "/java")]
|
(let [java-dir (str subprojs-base "/" subproj "/java")]
|
||||||
(if (.exists (io/file java-dir))
|
(when (.exists (io/file java-dir))
|
||||||
(do
|
(println "Compiling java code for" subproj)
|
||||||
(println "Compiling java code for" subproj)
|
(b/javac {:src-dirs [java-dir]
|
||||||
(b/javac {:src-dirs [java-dir]
|
:class-dir (class-dir subproj)
|
||||||
:class-dir (class-dir subproj)
|
:basis @(basis subproj)
|
||||||
:basis @(basis subproj)
|
:javac-opts ["-source" "11" "--target" "11" "-proc:none"]}))))
|
||||||
:javac-opts ["-source" "11" "--target" "11" "-proc:none"]}))
|
|
||||||
(println "No java code for" subproj ", skipping compilation"))))
|
|
||||||
|
|
||||||
;; Create a jar file
|
;; Create a jar file
|
||||||
(defn jar
|
(defn jar
|
||||||
"Build a simple jar file, with no dependencies included."
|
"Build a simple jar file, with no dependencies included."
|
||||||
[{:keys [subproj version]}]
|
[{:keys [subproj version]
|
||||||
(let [real-version (if version version curr-version)
|
:or {version curr-version}}]
|
||||||
target-dir (target-dir subproj)
|
(let [target-dir (target-dir subproj)
|
||||||
class-dir (class-dir subproj)
|
class-dir (class-dir subproj)
|
||||||
src-dir (str "projects/" subproj "/src")
|
src-dir (str subprojs-base "/" subproj "/src")
|
||||||
resources-dir (str "projects/" subproj "/resources")
|
resources-dir (str subprojs-base "/" subproj "/resources")
|
||||||
basis (basis subproj)
|
basis (basis subproj)
|
||||||
jar-file (jar-file subproj real-version :plain)]
|
jar-file (jar-file subproj version :plain)]
|
||||||
;; Clean only class dir
|
;; Clean only class dir
|
||||||
(b/delete {:path class-dir})
|
(b/delete {:path class-dir})
|
||||||
;; Copy code
|
;; Copy code
|
||||||
@@ -72,19 +101,23 @@
|
|||||||
:basis @basis
|
:basis @basis
|
||||||
:jar-file jar-file
|
:jar-file jar-file
|
||||||
:lib (lib subproj)
|
:lib (lib subproj)
|
||||||
:version real-version})
|
:version version})
|
||||||
(println "Generated jar file:" jar-file)))
|
(println "Generated jar file:" jar-file)))
|
||||||
|
|
||||||
|
|
||||||
;; Create an uber jar, with all dependencies inside
|
;; Create an uber jar, with all dependencies inside
|
||||||
|
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn uber
|
(defn uber
|
||||||
"Build a uberjar with all dependencies included"
|
"Build a uberjar with all dependencies included"
|
||||||
[{:keys [subproj version main-ns]}]
|
[{:keys [subproj version main-ns]
|
||||||
(let [real-version (if version version curr-version)target-dir (target-dir subproj)
|
:or {version curr-version}}]
|
||||||
|
(let [target-dir (target-dir subproj)
|
||||||
basis (basis subproj)
|
basis (basis subproj)
|
||||||
class-dir (class-dir subproj)
|
class-dir (class-dir subproj)
|
||||||
src-dir (str "projects/" subproj "/src")
|
src-dir (str subprojs-base "/" subproj "/src")
|
||||||
resources-dir (str "projects/" subproj "/resources")
|
resources-dir (str subprojs-base "/" subproj "/resources")
|
||||||
uber-file (jar-file subproj real-version :uber)]
|
uber-file (jar-file subproj version :uber)]
|
||||||
|
;(println "Using basis: ")(show-basis subproj)
|
||||||
(b/delete {:path class-dir})
|
(b/delete {:path class-dir})
|
||||||
(b/copy-dir {:src-dirs [src-dir]
|
(b/copy-dir {:src-dirs [src-dir]
|
||||||
:target-dir class-dir})
|
:target-dir class-dir})
|
||||||
@@ -99,3 +132,43 @@
|
|||||||
:main main-ns})
|
:main main-ns})
|
||||||
(println "Generated uberjar executable:" uber-file)))
|
(println "Generated uberjar executable:" uber-file)))
|
||||||
|
|
||||||
|
|
||||||
|
;; Multimethod to get the name of all subdirs in a dir.
|
||||||
|
;; Accepts strings or files
|
||||||
|
(defmulti get-subdirs type)
|
||||||
|
|
||||||
|
(defmethod get-subdirs
|
||||||
|
java.lang.String [dir]
|
||||||
|
(get-subdirs (io/file dir)))
|
||||||
|
|
||||||
|
(defmethod get-subdirs
|
||||||
|
java.io.File [dir]
|
||||||
|
(if (.isDirectory dir)
|
||||||
|
(filter #(.isDirectory %) (.listFiles dir))
|
||||||
|
(println "Directory" subprojs-base "doesn't exists!")))
|
||||||
|
|
||||||
|
|
||||||
|
;; Get the name of all subdir in a given directory
|
||||||
|
(defn get-subdir-names
|
||||||
|
"Get a list projects in the 'projects' directory"
|
||||||
|
[dir-name]
|
||||||
|
(map #(.getName %) (get-subdirs dir-name)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(get-subdirs "projects")
|
||||||
|
(get-subdirs (io/file "projects"))
|
||||||
|
(get-subdir-names "projects")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; Generate jar files for all projects
|
||||||
|
(defn jar-all
|
||||||
|
"Build jar files for all projects"
|
||||||
|
[& {:keys [version]
|
||||||
|
:or {version curr-version}}]
|
||||||
|
(dorun (map #(jar {:subproj % :version version}) (get-subdir-names subprojs-base))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(jar-all )
|
||||||
|
|
||||||
|
)
|
||||||
@@ -1 +1,11 @@
|
|||||||
~/.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
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
JAVA_CMD=~/.sdkman/candidates/java/21.0.2-graalce/bin/java
|
||||||
|
UBERJAR=$(realpath --relative-to=. target/clj-totp-cli-*-standalone.jar)
|
||||||
|
|
||||||
|
echo "Using uberjar $UBERJAR"
|
||||||
|
|
||||||
|
$JAVA_CMD -agentlib:native-image-agent=config-output-dir=target/native-image\
|
||||||
|
-jar $UBERJAR import "deleteme" "otpauth-migration://offline?data=CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA"
|
||||||
|
|
||||||
|
java -jar $UBERJAR delete "deleteme"
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/env sh
|
|
||||||
|
|
||||||
protoc --java_out java/protoc/ resources/proto/otpauth-migration.proto
|
|
||||||
#javac -cp resources/protobuf-java-3.25.8.jar -d target/classes/proto src/OtpauthMigration.java
|
|
||||||
100
deps.edn
100
deps.edn
@@ -1,58 +1,74 @@
|
|||||||
{:paths ["src" "resources" "target/classes"]
|
{:paths ["src" "resources" "target/classes"]
|
||||||
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
|
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
|
||||||
;; Native image (GraalVM)
|
;; Native image (GraalVM). Tutorial: https://shagunagrawal.me/posts/setup-clojure-with-graalvm-for-native-image/
|
||||||
com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}}
|
com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}
|
||||||
|
;; Local subprojects
|
||||||
|
clj-totp/core {:local/root "projects/core"}
|
||||||
|
clj-totp/cli {:local/root "projects/cli"}
|
||||||
|
clj-totp/web {:local/root "projects/web"}
|
||||||
|
clj-totp/gui {:local/root "projects/gui"}
|
||||||
|
}
|
||||||
|
|
||||||
:aliases {;; Execute the app. Tutorial: https://shagunagrawal.me/posts/setup-clojure-with-graalvm-for-native-image/
|
:aliases {;; Execute the app.
|
||||||
:run {:main-opts ["-m" "totp.app"]}
|
:run {:main-opts ["-m" "totp.app"]}
|
||||||
|
;:run {:exec-fn totp.app/-main}
|
||||||
|
|
||||||
|
;; Execute the app (prepared for more subprojects)
|
||||||
|
:run/cli {:main-opts ["-m" "totp.app"]}
|
||||||
|
|
||||||
|
:run/gui {:main-opts ["-m" "totp.gui"]}
|
||||||
|
|
||||||
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
||||||
:test {:extra-paths ["test"]
|
;; Check test.edn for kaocha runner's config
|
||||||
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
|
:test {:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}
|
||||||
|
lambdaisland/kaocha-cloverage {:mvn/version "1.1.89"}}
|
||||||
:main-opts ["-m" "kaocha.runner"]}
|
:main-opts ["-m" "kaocha.runner"]}
|
||||||
|
|
||||||
;; Run with clj -T:build function-in-build
|
;; Run with clj -T:build function-in-build
|
||||||
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
|
;; Used by all compilations
|
||||||
|
:extra-deps {clj-totp/core {:local/root "projects/core"}}
|
||||||
:ns-default build}
|
:ns-default build}
|
||||||
|
|
||||||
|
;; Aliases for easy building
|
||||||
;; COMMON ALIASES FOR ALL PROJECTS
|
:build/core {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
:root/run-x {:exec-fn -main}
|
|
||||||
:root/extra-paths [:totp.core/extra-paths
|
|
||||||
:totp.cli/extra-paths
|
|
||||||
:totp.web/extra-paths]
|
|
||||||
|
|
||||||
:root/all {:extra-paths ["src" "resources"
|
|
||||||
:root/extra-paths]}
|
|
||||||
|
|
||||||
:root/test {:extra-paths ["test"]
|
|
||||||
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
|
|
||||||
:main-opts ["-m" "kaocha.runner"]}
|
|
||||||
|
|
||||||
:boot/build {:extra-paths ["build"]
|
|
||||||
:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
|
||||||
:ns-default build
|
:ns-default build
|
||||||
;:exec-fn ci
|
:exec-fn jar
|
||||||
;:exec-args {:app-alias :com.example.core}
|
:exec-args {:subproj "core"}}
|
||||||
}
|
|
||||||
|
|
||||||
:totp.core/extra-paths ["projects/core/src"
|
:build/cli {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}
|
||||||
"projects/core/test"
|
clj-totp/core {:local/root "projects/core"}}
|
||||||
"projects/core/java"
|
:ns-default build
|
||||||
"projects/core/resources"]
|
:exec-fn jar
|
||||||
:totp.core {:ns-default totp.core
|
:exec-args {:subproj "cli"}}
|
||||||
:main-opts ["-m" "totp.core"]
|
|
||||||
:extra-deps {projects/core {:local/root "projects/core"}}
|
|
||||||
:exec-args {:dirs ["projects/core"]}}
|
|
||||||
|
|
||||||
|
:build/web {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
:totp.cli/extra-paths ["projects/app/src"]
|
:replace-deps {clj-totp/core {:local/root "projects/core"}}
|
||||||
:totp.cli {:ns-default totp.cli
|
:ns-default build
|
||||||
:main-opts ["-m" "totp.cli"]
|
:exec-fn jar
|
||||||
:extra-deps {;; does not use parts/grugstack {:local/root "parts"}
|
:exec-args {:subproj "web"}}
|
||||||
projects/cli {:local/root "projects/cli"}}
|
|
||||||
:exec-args {:dirs ["projects/cli"]}}
|
|
||||||
|
|
||||||
:totp.web/extra-paths ["projects/web/src"]
|
:build/gui {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
}}
|
:replace-deps {clj-totp/core {:local/root "projects/core"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn jar
|
||||||
|
:exec-args {:subproj "gui"}}
|
||||||
|
|
||||||
|
;; Build the three libraries
|
||||||
|
:build/all {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
|
:replace-deps {clj-totp/core {:local/root "projects/core"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn jar-all}
|
||||||
|
|
||||||
|
;; Build uber jar for CLI app
|
||||||
|
:uber/cli {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn uber
|
||||||
|
:exec-args {:subproj "cli" :main-ns "totp.app"}}
|
||||||
|
|
||||||
|
;; Build uber jar for CLI app
|
||||||
|
:uber/gui {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn uber
|
||||||
|
:exec-args {:subproj "gui" :main-ns "totp.app"}}}}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ NATIVE=~/.sdkman/candidates/java/21.0.2-graalce/bin/native-image
|
|||||||
BIN_FILE=totp
|
BIN_FILE=totp
|
||||||
|
|
||||||
echo "Creating uberjar"
|
echo "Creating uberjar"
|
||||||
clojure -T:build uber
|
#clojure -T:uber/cli
|
||||||
UBERJAR=$(realpath --relative-to=target target/clj-totp-*-standalone.jar)
|
UBERJAR=$(realpath --relative-to=target target/clj-totp-cli-*-standalone.jar)
|
||||||
|
|
||||||
|
echo "Using uberjar $UBERJAR"
|
||||||
|
|
||||||
echo "Creating native image"
|
echo "Creating native image"
|
||||||
$NATIVE -jar target/$UBERJAR -o target/$BIN_FILE\
|
$NATIVE -jar target/$UBERJAR -o target/$BIN_FILE\
|
||||||
@@ -17,10 +19,13 @@ $NATIVE -jar target/$UBERJAR -o target/$BIN_FILE\
|
|||||||
--strict-image-heap\
|
--strict-image-heap\
|
||||||
-march=native\
|
-march=native\
|
||||||
-R:MaxHeapSize=10m\
|
-R:MaxHeapSize=10m\
|
||||||
|
--trace-object-instantiation=java.lang.Thread\
|
||||||
--initialize-at-build-time=org.fusesource.jansi.Ansi\
|
--initialize-at-build-time=org.fusesource.jansi.Ansi\
|
||||||
--initialize-at-build-time='org.fusesource.jansi.Ansi$Color'\
|
--initialize-at-build-time='org.fusesource.jansi.Ansi$Color'\
|
||||||
--initialize-at-build-time='org.fusesource.jansi.Ansi$Attribute'\
|
--initialize-at-build-time='org.fusesource.jansi.Ansi$Attribute'\
|
||||||
'--initialize-at-build-time=org.fusesource.jansi.Ansi$1'
|
--initialize-at-build-time='org.fusesource.jansi.Ansi$1'\
|
||||||
|
--initialize-at-run-time=cljc_long.constants__init
|
||||||
|
|
||||||
|
|
||||||
echo "Executable created on target/$BIN_FILE"
|
echo "Executable created on target/$BIN_FILE"
|
||||||
cp target/$BIN_FILE ~/bin
|
cp target/$BIN_FILE ~/bin
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
{:paths ["src" "resources" "target/classes"]
|
{:paths ["src" "resources" "target/classes"]
|
||||||
:deps {clj-totp/core {:local/root "projects/core"}
|
:deps {;clj-totp/core {:local/root "../core"}
|
||||||
org.clojure/clojure {:mvn/version "1.12.1"}
|
org.clojure/clojure {:mvn/version "1.12.1"}
|
||||||
io.github.clojure/tools.build {:mvn/version "0.10.10"}
|
|
||||||
cli-matic/cli-matic {:mvn/version "0.5.4"} ;; https://github.com/l3nz/cli-matic
|
cli-matic/cli-matic {:mvn/version "0.5.4"} ;; https://github.com/l3nz/cli-matic
|
||||||
;; Progress bar
|
;; Progress bar
|
||||||
com.github.pmonks/spinner {:mvn/version "2.0.284"}}
|
com.github.pmonks/spinner {:mvn/version "2.0.284"}
|
||||||
|
com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}}
|
||||||
|
|
||||||
:aliases {;; Execute the app
|
:aliases {;; Execute the app
|
||||||
:run {:main-opts ["-m" "totp.app"]}
|
;:run {:main-opts ["-m" "totp.app"]}
|
||||||
|
|
||||||
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
||||||
:test {:extra-paths ["test"]
|
:test {:extra-paths ["test"]
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
-m
|
|
||||||
kaocha.runner
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
{:paths ["src" "resources" "target/classes"]
|
{:paths ["src" "resources" "target/classes"]
|
||||||
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
|
: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
|
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
|
|
||||||
;; Protobuf for java
|
;; Protobuf for java
|
||||||
com.google.protobuf/protobuf-java {:mvn/version "3.25.8"}
|
com.google.protobuf/protobuf-java {:mvn/version "3.25.8"}
|
||||||
|
;; Dynamic protobuf
|
||||||
|
com.github.s-expresso/clojobuf {:mvn/version "0.2.1"}
|
||||||
}
|
}
|
||||||
|
|
||||||
:aliases {;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
:aliases {;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -99,3 +99,29 @@
|
|||||||
(rem (int (m/pow 10 digits))))))))
|
(rem (int (m/pow 10 digits))))))))
|
||||||
([secret]
|
([secret]
|
||||||
(get-otp secret "sha1" 6 30)))
|
(get-otp secret "sha1" 6 30)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn calculate-offset-millis
|
||||||
|
[period]
|
||||||
|
(let [step-millis (* 1000 period)
|
||||||
|
now (System/currentTimeMillis)]
|
||||||
|
(int (rem now step-millis))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn calculate-delay-millis
|
||||||
|
[period]
|
||||||
|
(let [step-millis (* 1000 period)]
|
||||||
|
(- step-millis (calculate-offset-millis period))))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(let [now (System/currentTimeMillis)
|
||||||
|
off (calculate-offset-millis 30)
|
||||||
|
delay (calculate-delay-millis 30)
|
||||||
|
]
|
||||||
|
(println "Now: " (int (/ now 1000)) "secs" now "millis")
|
||||||
|
(println "Offset:" (int (/ off 1000)) "secs" off "millis")
|
||||||
|
(println "Delay: " (int (/ delay 1000)) "secs" delay "millis")
|
||||||
|
(println "Total: " (+ (int (/ off 1000)) (int (/ delay 1000))) "secs" (+ delay off) "millis")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
(ns totp.data
|
(ns totp.data
|
||||||
(:require [clojure.edn :as e]
|
(:require [totp.otp-proto :as proto]
|
||||||
|
[clojure.edn :as e]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.pprint :as pp]
|
[clojure.pprint :as pp]
|
||||||
[alphabase.base64 :as b64]
|
[alphabase.base64 :as b64]
|
||||||
[alphabase.base32 :as b32])
|
[alphabase.base32 :as b32]))
|
||||||
(:import [protoc OtpauthMigration$MigrationPayload]))
|
|
||||||
|
|
||||||
(defn join-path
|
(defn join-path
|
||||||
"Joins several subpaths using system's path separator (/ un *NIX and \\ in windows)"
|
"Joins several subpaths using system's path separator (/ un *NIX and \\ in windows)"
|
||||||
@@ -89,11 +89,16 @@
|
|||||||
|
|
||||||
(defn list-apps
|
(defn list-apps
|
||||||
[cfg]
|
[cfg]
|
||||||
(map :name
|
(->> cfg
|
||||||
(filter #(contains? % :name) cfg)))
|
(filter #(contains? % :name))
|
||||||
|
(map :name)))
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(list-apps (load-config)))
|
(list-apps (load-config))
|
||||||
|
(with-config #_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(list-apps cfg))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
(defn get-app
|
(defn get-app
|
||||||
[cfg name]
|
[cfg name]
|
||||||
@@ -202,28 +207,26 @@
|
|||||||
(when (some? url)
|
(when (some? url)
|
||||||
(let [b64-data (second (str/split url #"=" -1))
|
(let [b64-data (second (str/split url #"=" -1))
|
||||||
data-b (b64/decode b64-data)
|
data-b (b64/decode b64-data)
|
||||||
parsed (OtpauthMigration$MigrationPayload/parseFrom data-b)
|
payload (proto/parse-data data-b)
|
||||||
payload (bean (.getOtpParameters parsed 0))
|
;{:keys [name secret name issuer digits algorithm type]} payload
|
||||||
;{:keys [name secret name issuer digitsValue algorithmValue typeValue]} payload
|
secret (:secret payload)
|
||||||
secret-b (:secret payload)
|
|
||||||
secret (b32/encode (.toByteArray secret-b))
|
|
||||||
user (:name payload)
|
user (:name payload)
|
||||||
issuer (:issuer payload)
|
issuer (:issuer payload)
|
||||||
algorithm (case (:algorithmValuei payload) 2 "sha256" 3 "sha512" "sha1")
|
algorithm (:algorithm payload)
|
||||||
digits (case (:digitsValue payload) 2 8 6)
|
digits (:digits payload)
|
||||||
valid-type (= 2 (:typeValue payload))
|
valid-type (= 2 (:type payload))
|
||||||
]
|
]
|
||||||
(println "name:" name "user:" user "issuer:" issuer "digitsValue:" digits "algorithm:" algorithm "valid type?" valid-type)
|
;(println "name:" name "user:" user "issuer:" issuer "digits:" digits "algorithm:" algorithm "valid type?" valid-type)
|
||||||
(if valid-type
|
(if valid-type
|
||||||
(create-app name secret user issuer algorithm digits 30)
|
(create-app name secret user issuer algorithm digits 30)
|
||||||
(println "Invalid OTP type" (:typeValue payload)))
|
(println "Invalid OTP type" (:type payload)))
|
||||||
)))
|
)))
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(url-export->app "test"
|
(url-export->app "test"
|
||||||
"otpauth-migration://offline?data=CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA"
|
"otpauth-migration://offline?data=CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA"
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
385
projects/core/src/totp/otp_import.clj
Normal file
385
projects/core/src/totp/otp_import.clj
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
(ns totp.otp-import
|
||||||
|
(:require [alphabase.bytes :as b]
|
||||||
|
[alphabase.base16 :as hex]
|
||||||
|
[alphabase.base64 :as b64]
|
||||||
|
[alphabase.base2 :as b2]
|
||||||
|
[clojure.math :as m]
|
||||||
|
[clojure.string :as s]))
|
||||||
|
|
||||||
|
;; Original description of the export protocol uses Google's Protocol Buffers
|
||||||
|
;; https://protobuf.dev/
|
||||||
|
|
||||||
|
(def WIRE_TYPES {:varint 0 ;; Integeres of variable length encoding
|
||||||
|
:i64 1 ;; Fixed 64 bits numbers (integer or decimal)
|
||||||
|
:len 2 ;; Block of bytes with a predefined length
|
||||||
|
:sgroup 3 ;; Group end (deprecated)
|
||||||
|
:egroup 4 ;; Group start (deprecated)
|
||||||
|
:i32 5 ;; Fixed 32 bits number (integer or decimal)
|
||||||
|
})
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(let [encoded "CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA"
|
||||||
|
decoded (b64/decode encoded)]
|
||||||
|
(print (hex/encode decoded))
|
||||||
|
(print (b/to-string decoded))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn to-fancy-bin
|
||||||
|
"Return a string with bits from number in a fancy manner"
|
||||||
|
[x]
|
||||||
|
(s/join " "
|
||||||
|
(map s/join
|
||||||
|
(partition 8 (b2/encode (byte-array (map #(b/to-byte %) x)))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn to-fancy-hex
|
||||||
|
"Return a string with hex values from number in a fancy manner"
|
||||||
|
([x]
|
||||||
|
(to-fancy-hex x 2))
|
||||||
|
([x group-size]
|
||||||
|
(s/join " "
|
||||||
|
(map s/join
|
||||||
|
(partition group-size (hex/encode (byte-array (map #(b/to-byte %) x))))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn len-bits
|
||||||
|
"How may blocks of n bits are needed to encode this number?
|
||||||
|
|
||||||
|
We use the following formula, for calculating the number of digits required
|
||||||
|
to encode the number n in the base b:
|
||||||
|
|
||||||
|
digits = ceil ( log_n ( x + 1 ) )
|
||||||
|
"
|
||||||
|
[n x]
|
||||||
|
(case x
|
||||||
|
nil 0 ;; nill is encoded with zero bytes
|
||||||
|
0 1 ;; One block to zero
|
||||||
|
9223372036854775807 (len-bits n (dec x)) ;; Beware the overflow!! it's best to lose some precision
|
||||||
|
(when (and (>= x 0) (some? n) (> n 0))
|
||||||
|
(long (m/ceil (/ (m/log (inc x)) (m/log (m/pow 2 n))))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(len-bits 8 513)
|
||||||
|
(len-bits 8 Long/MAX_VALUE)
|
||||||
|
(len-bits 8 (dec Long/MAX_VALUE)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn len-bytes
|
||||||
|
"How may bytes are needed to encode this number?"
|
||||||
|
[x]
|
||||||
|
(len-bits 8 x))
|
||||||
|
|
||||||
|
|
||||||
|
(defn integer>bytes
|
||||||
|
"Converts an integer to a byte array"
|
||||||
|
[x]
|
||||||
|
(when x (let [len (len-bytes x)
|
||||||
|
hex-len (* 2 len)]
|
||||||
|
(hex/decode (String/format (str "%0" hex-len "x") (into-array [x]))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(integer>bytes 513)
|
||||||
|
10r3
|
||||||
|
3r10)
|
||||||
|
|
||||||
|
|
||||||
|
(defn euclidean-quot
|
||||||
|
"Euclidean division for integers
|
||||||
|
|
||||||
|
More info: https://en.wikipedia.org/wiki/Euclidean_division
|
||||||
|
"
|
||||||
|
[a b]
|
||||||
|
(when (zero? b)
|
||||||
|
(throw (IllegalArgumentException. "You can't divide by zero!")))
|
||||||
|
(let [b-abs (Math/abs b)
|
||||||
|
r (mod a b-abs)] ;; 0 <= r < |b|
|
||||||
|
(quot (- a r) b) ;; adjusts quotient with the positive remainder
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
(defn euclidean-rem
|
||||||
|
"Modulus for euclidean division for integers
|
||||||
|
|
||||||
|
More info: https://en.wikipedia.org/wiki/Euclidean_division
|
||||||
|
"
|
||||||
|
[a b]
|
||||||
|
(when (zero? b)
|
||||||
|
(throw (IllegalArgumentException. "You can't divide by zero!")))
|
||||||
|
(let [b-abs (Math/abs b)]
|
||||||
|
(mod a b-abs);; 0 <= r < |b|
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
(defn euclid-div
|
||||||
|
"Euclidean division. Returns quotient and remainder as a map. The remainder is
|
||||||
|
allways a positive number.
|
||||||
|
|
||||||
|
More info: https://en.wikipedia.org/wiki/Euclidean_division"
|
||||||
|
[a b]
|
||||||
|
(when (zero? b)
|
||||||
|
(throw (IllegalArgumentException. "You can't divide by zero!")))
|
||||||
|
(let [b-abs (Math/abs b)
|
||||||
|
r (mod a b-abs) ;; 0 <= r < |b|
|
||||||
|
q (quot (- a r) b)] ;; adjusts quotient with the positive remainder
|
||||||
|
{:q q :r r}))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(euclid-div 7 3)
|
||||||
|
(euclid-div -7 3)
|
||||||
|
(euclid-div 7 -3)
|
||||||
|
(euclid-div -7 -3))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn decimal->base
|
||||||
|
"Converts a decimal number to an arbitrary base. Each digit is encoded as an
|
||||||
|
integer with value between 0 and base-1.
|
||||||
|
|
||||||
|
For example, 10 in base 4 will be encoded as:
|
||||||
|
[2 0 2]
|
||||||
|
, 127 in base 126 will be:
|
||||||
|
[1 1]
|
||||||
|
and so on.
|
||||||
|
|
||||||
|
We will use the sucessive division method: do a integer division of the number
|
||||||
|
to the base until the quotient is zero, and take the reminders backwards. For
|
||||||
|
example, if we want to converto 127 to base 5:
|
||||||
|
|
||||||
|
127 / 5 = 25 rem 2
|
||||||
|
25 / 5 = 5 rem 0
|
||||||
|
5 / 5 = 1 rem 0
|
||||||
|
1 / 5 = 0 rem 1
|
||||||
|
|
||||||
|
so, 127 in base 5 is 1002.
|
||||||
|
|
||||||
|
The maximum digits this method can return is 65. Max size for a long is base 2
|
||||||
|
is 64, so if you obtain a result with 65 digits is very probable that the
|
||||||
|
conversion has failed.
|
||||||
|
|
||||||
|
If the number is negative and the base is possitive, we return the conversion
|
||||||
|
of the absolute value. You must take care of the sign in your implementation.
|
||||||
|
|
||||||
|
There are negative bases as -2 that can encode possitive and negative numbers.
|
||||||
|
"
|
||||||
|
([base n]
|
||||||
|
(decimal->base base n true))
|
||||||
|
([base n reverse?]
|
||||||
|
(if (or (nil? n) (nil? base) (zero? n) (zero? base))
|
||||||
|
[0] ;; Allways [0] for base zero or number zero
|
||||||
|
(loop [acc []
|
||||||
|
x (if (and (< n 0) (> base 0)) (abs n) n)] ;; If n < 0 and b > 0 => |n|, else n
|
||||||
|
(let [q (euclidean-quot x base)
|
||||||
|
r (euclidean-rem x base)]
|
||||||
|
;;(printf "Acc: %s Calculating: %s/%s -> %s rem %s%n" acc x base q r)
|
||||||
|
(if (or (zero? x) (> (count acc) (inc Long/SIZE))) ;; max digits is 65
|
||||||
|
(vec (if reverse? (reverse acc) acc)) ;; When x is zero, we have finished
|
||||||
|
(recur
|
||||||
|
(conj acc r) ;; Accumulate the remainder
|
||||||
|
q ;; Pass the quotient to the next step
|
||||||
|
)))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(decimal->base 2 8)
|
||||||
|
2r1000
|
||||||
|
(decimal->base 2 3)
|
||||||
|
|
||||||
|
(letfn [(step [x b name q r]
|
||||||
|
(printf "%s -> %d/%d = %d rem %d%n" name x b q r))
|
||||||
|
(examples [x b exp-q exp-r]
|
||||||
|
(step x b "EXPECTED " exp-q exp-r)
|
||||||
|
(step x b "IEEE " (int (m/IEEE-remainder x b)) (rem x b))
|
||||||
|
(step x b "With rem " (quot x b) (rem x b))
|
||||||
|
(step x b "With mod " (quot x b) (mod x b))
|
||||||
|
(step x b "floor " (m/floor-div x b) (m/floor-mod x b))
|
||||||
|
(step x b "Manual 1 " (int (clojure.math/round (double (/ x b)))) (rem x b))
|
||||||
|
(step x b "Manual 2 " (int (clojure.math/rint (double (/ x b)))) (rem x b))
|
||||||
|
(step x b "Unchecked " (unchecked-divide-int x b) (unchecked-remainder-int x b))
|
||||||
|
(step x b "Euclidean " (euclidean-quot x b) (euclidean-rem x b)))]
|
||||||
|
(examples -3 -2 2 1)
|
||||||
|
(println)
|
||||||
|
(examples 2 -2 -1 0)
|
||||||
|
(println)
|
||||||
|
(examples -1 -2 1 1)
|
||||||
|
(println)
|
||||||
|
(examples 1 -2 0 1)
|
||||||
|
(println)
|
||||||
|
(println)
|
||||||
|
(examples 7 3 2 1)
|
||||||
|
(println)
|
||||||
|
(examples -7 3 -3 2)
|
||||||
|
(println)
|
||||||
|
(examples 7 -3 -2 1)
|
||||||
|
(println)
|
||||||
|
(examples -7 -3 3 2)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn base->decimal
|
||||||
|
"Converts from an array with values in an arbitrary base into decimal values"
|
||||||
|
[base n]
|
||||||
|
(if (or (nil? n) (nil? base) (zero? (count n)))
|
||||||
|
0
|
||||||
|
(long (reduce-kv
|
||||||
|
(fn [acc k v]
|
||||||
|
(+ acc (* v (m/pow base k))))
|
||||||
|
0 (vec (reverse n))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(reduce-kv #(+ %1 (* %3 (m/pow 2 %2))) 0 [1 0 0 0]) ;; backwards!
|
||||||
|
(base->decimal 2 [1 0 0 0]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn int->varint
|
||||||
|
"Converts a integer value to a varint, that is encoded in 7 bits, where the first
|
||||||
|
bit is used to indicate if there are more bytes.
|
||||||
|
|
||||||
|
For example, the number 255 is usually encoded in a byte as follows:
|
||||||
|
11111111
|
||||||
|
but in a varint it will turn to:
|
||||||
|
10000001 01111111
|
||||||
|
|
||||||
|
First byte has the MSB set to 1, because there is another byte after it. The
|
||||||
|
second byte is the last one, so the MSB is set to 0.
|
||||||
|
|
||||||
|
The result is a byte array.
|
||||||
|
"
|
||||||
|
[x]
|
||||||
|
(let [b128 (decimal->base 128 x false)]
|
||||||
|
(byte-array (conj
|
||||||
|
(vec (map #(bit-or 2r10000000 %) (butlast b128)))
|
||||||
|
(bit-and 2r01111111 (peek b128))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn varint->int
|
||||||
|
"Converts a varint to an integer. Each byte in varint uses the MSB as a continuation
|
||||||
|
bit: if it's value is 1, there are more bites, if it's 0 it's the last block.
|
||||||
|
|
||||||
|
For example, this varint with two bytes:
|
||||||
|
10000001 01111111
|
||||||
|
|
||||||
|
first byte's MSB has value 1, so there is another byte. The second one has a zero
|
||||||
|
in the MSB, so it's the last block. To calculate the final value, you must ignore
|
||||||
|
the MSB and concatenate both bytes:
|
||||||
|
110010110 00000001 -> original value
|
||||||
|
0010110 0000001 -> delete MSB
|
||||||
|
0000001 0010110 -> reverse bytes (little-endian to big endian)
|
||||||
|
00000010010110 -> concatenate
|
||||||
|
00000000 10010110 -> fill the bytes
|
||||||
|
"
|
||||||
|
[x]
|
||||||
|
(base->decimal 128 (reverse (map #(bit-and 2r01111111 %) x))))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; 150
|
||||||
|
(to-fancy-bin [-106, 1])
|
||||||
|
(to-fancy-bin (map #(bit-and 2r01111111 %) [-106, 1]))
|
||||||
|
(to-fancy-bin (reverse (map #(bit-and 2r01111111 %) [-106, 1])))
|
||||||
|
(varint->int [-106, 1])
|
||||||
|
|
||||||
|
(varint->int [-106, 1])
|
||||||
|
(varint->int [-105, 1])
|
||||||
|
|
||||||
|
;; Long/MAX_VALUE
|
||||||
|
(to-fancy-bin (map #(bit-and 2r01111111 %) [-1, -1, -1, -1, -1, -1, -1, -1, 128]))
|
||||||
|
(varint->int [-2, -1, -1, -1, -1, -1, -1, -1, 127]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn more-blocks?
|
||||||
|
"True if the MSB bit is 1"
|
||||||
|
[b]
|
||||||
|
(when b
|
||||||
|
(< 0 (bit-and 2r10000000 b))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(bit-and 2r10000000 2r10000001)
|
||||||
|
(bit-and 2r10000000 2r00000001)
|
||||||
|
( more-blocks? 2r10000001)
|
||||||
|
(more-blocks? 2r00000001)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn extract-varint-blocks
|
||||||
|
"Group varints in a byte array"
|
||||||
|
[bytes]
|
||||||
|
(loop [acc [] ;; Groups of varints
|
||||||
|
group [] ;; Current varint
|
||||||
|
r bytes] ;; Current tested byte
|
||||||
|
(if (empty? r) ;; Final condition: no more bytes to test
|
||||||
|
(if ((complement empty?) group)
|
||||||
|
(conj acc group) ;; Return acc with the last group added if not empty
|
||||||
|
acc) ;; if empty, return acc
|
||||||
|
(if (more-blocks? (first r))
|
||||||
|
;; If more blocks remains:
|
||||||
|
(recur acc
|
||||||
|
(conj group (first r)) ;; add current byte to current group
|
||||||
|
(next r))
|
||||||
|
;; If it's last in varint:
|
||||||
|
(recur (->> (first r)
|
||||||
|
(conj group) ;; add byte to group
|
||||||
|
(conj acc)) ;; add group to accumulator
|
||||||
|
[] ;; and start a new empty group
|
||||||
|
(next r))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(extract-varint-blocks [2r11111111 2r01111111, 2r10000001 2r10101001 2r00000001, 2r00000111])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn extract-varints
|
||||||
|
"Group varints in an byte array and convert them to decimal"
|
||||||
|
[bytes]
|
||||||
|
(map varint->int (extract-varint-blocks bytes)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(extract-varints [2r11111111 2r01111111, 2r10000001 2r10101001 2r00000001, 2r00000111])
|
||||||
|
|
||||||
|
(varint->int [2r11111111 2r01111111])
|
||||||
|
(varint->int [2r10000001 2r10101001 2r00000001])
|
||||||
|
(varint->int [2r00000111])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn bytes->len-type
|
||||||
|
"Prepend byte array with length in varint format"
|
||||||
|
[bytes]
|
||||||
|
(concat (int->varint (count bytes))
|
||||||
|
bytes))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(to-fancy-hex (bytes->len-type (.getBytes "testing")))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn pack-bytes-as-tlv
|
||||||
|
"Pack the value as TLV.
|
||||||
|
|
||||||
|
Type can one of those 6 IDs:
|
||||||
|
ID Name Used For
|
||||||
|
0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum
|
||||||
|
1 I64 fixed64, sfixed64, double
|
||||||
|
2 LEN string, bytes, embedded messages, packed repeated fields
|
||||||
|
3 SGROUP group start (deprecated)
|
||||||
|
4 EGROUP group end (deprecated)
|
||||||
|
5 I32 fixed32, sfixed32, float
|
||||||
|
"
|
||||||
|
([{:keys [field type value]}]
|
||||||
|
(pack-bytes-as-tlv field type value))
|
||||||
|
([field-number type value]
|
||||||
|
(let [field-displaced (bit-shift-left field-number 3)
|
||||||
|
tag (int->varint (bit-or field-displaced type))]
|
||||||
|
(concat tag value))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn unpack-tlv-bytes
|
||||||
|
"Returns a map with 3 pairs:
|
||||||
|
- :field is the field number
|
||||||
|
- :type is one of the values of WIRE_TYPES
|
||||||
|
- :value is the byte array with the payload"
|
||||||
|
[packed]
|
||||||
|
(let [first-byte (bit-and 2r01111111 (first packed)) ;; bye bye, MSB
|
||||||
|
type (bit-and 2r00000111 first-byte)
|
||||||
|
field-number (bit-shift-right first-byte 3)]
|
||||||
|
{:field field-number :type type :value (rest packed)}))
|
||||||
|
|
||||||
|
|
||||||
45
projects/core/src/totp/otp_proto.clj
Normal file
45
projects/core/src/totp/otp_proto.clj
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
(ns totp.otp-proto
|
||||||
|
(:require [clojobuf.core :refer [protoc decode]]
|
||||||
|
[alphabase.base64 :as b64]
|
||||||
|
[alphabase.base32 :as b32]))
|
||||||
|
|
||||||
|
|
||||||
|
;; Where lookup for proto files
|
||||||
|
(def registry (protoc ["resourcse/proto/"
|
||||||
|
"projects/core/resources/proto/"]
|
||||||
|
["otpauth-migration.proto"]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn parse-data
|
||||||
|
[binary-data]
|
||||||
|
(let [decoded (decode registry :MigrationPayload binary-data)
|
||||||
|
msg (get-in decoded [:otp_parameters 0])]
|
||||||
|
;(println "Decoded:" msg)
|
||||||
|
(when msg
|
||||||
|
{:secret (b32/encode (:secret msg))
|
||||||
|
:name (:name msg)
|
||||||
|
:issuer (:issuer msg)
|
||||||
|
:algorithm (case (:algorithm msg)
|
||||||
|
:ALGORITHM_SHA256 "sha256"
|
||||||
|
:ALGORITHM_SHA512 "sha512"
|
||||||
|
:ALGORITHM_MD5 "md5"
|
||||||
|
"sha1") ;; sha1 by default
|
||||||
|
:digits (case (:digits msg)
|
||||||
|
:DIGIT_COUNT_EIGHT 8
|
||||||
|
6) ;; 6 digits by default
|
||||||
|
:type (case (:type msg)
|
||||||
|
:OTP_TYPE_UNSPECIFIED 0
|
||||||
|
:OTP_TYPE_HOTP 1
|
||||||
|
:OTP_TYPE_TOTP 2) ;; Only TOTP is supported
|
||||||
|
})))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(let [b64-data "CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA"
|
||||||
|
bin-data (b64/decode b64-data)
|
||||||
|
decoded (decode registry :MigrationPayload bin-data)]
|
||||||
|
;(get-in decoded [:otp_parameters 0])
|
||||||
|
(parse-data bin-data)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,101 +0,0 @@
|
|||||||
(ns totp.core
|
|
||||||
(:require [alphabase.base32 :as b32]
|
|
||||||
[clojure.math :as m])
|
|
||||||
(: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)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn get-alg
|
|
||||||
[alg]
|
|
||||||
(case alg
|
|
||||||
"sha1" "HmacSHA1"
|
|
||||||
"sha256" "HmacSHA256"
|
|
||||||
"sha512" "HmacSHA512"
|
|
||||||
""))
|
|
||||||
|
|
||||||
|
|
||||||
(defmulti hmac
|
|
||||||
"Generates an HMAC. Algorithms supported: sha1, sha256, sha512.
|
|
||||||
The key and the message can be (both) string or array of bytes, nil otherwise"
|
|
||||||
(fn [algorithm key message]
|
|
||||||
(cond
|
|
||||||
(and (string? key) (string? message) (some? (get-alg algorithm))) :string
|
|
||||||
(and (bytes-array? key) (bytes-array? message) (some? (get-alg algorithm))) :byte
|
|
||||||
:else :nil)))
|
|
||||||
|
|
||||||
;; By default
|
|
||||||
(defmethod hmac :nil [_ _ _]
|
|
||||||
nil)
|
|
||||||
|
|
||||||
;; When key and message are strings
|
|
||||||
(defmethod hmac :string [algorithm key message]
|
|
||||||
(if (or (empty? key) (empty? message))
|
|
||||||
""
|
|
||||||
(let [mac (doto (Mac/getInstance (get-alg algorithm)) (.init (SecretKeySpec. (.getBytes key) (get-alg algorithm))))
|
|
||||||
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 :byte [algorithm key message]
|
|
||||||
(if (nil? message)
|
|
||||||
(bytes (byte-array 0))
|
|
||||||
(let [mac (doto (Mac/getInstance (get-alg algorithm)) (.init (SecretKeySpec. key (get-alg algorithm))))
|
|
||||||
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 algorithm digits period] ;;algorithm digits period
|
|
||||||
(when (and secret period)
|
|
||||||
(let [step (timestamp->steps (System/currentTimeMillis) period)
|
|
||||||
k (b32/decode secret)
|
|
||||||
c (long->bytes step)
|
|
||||||
hs (hmac algorithm 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 (str "%0" digits "d")
|
|
||||||
(-> chunk
|
|
||||||
(bytes->int)
|
|
||||||
(bit-and 0x7fffffff)
|
|
||||||
(rem (int (m/pow 10 digits))))))))
|
|
||||||
([secret]
|
|
||||||
(get-otp secret "sha1" 6 30)))
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user