Compare commits
26 Commits
017291f784
...
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 |
@@ -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
|
|
||||||
20
deps.edn
20
deps.edn
@@ -6,6 +6,7 @@
|
|||||||
clj-totp/core {:local/root "projects/core"}
|
clj-totp/core {:local/root "projects/core"}
|
||||||
clj-totp/cli {:local/root "projects/cli"}
|
clj-totp/cli {:local/root "projects/cli"}
|
||||||
clj-totp/web {:local/root "projects/web"}
|
clj-totp/web {:local/root "projects/web"}
|
||||||
|
clj-totp/gui {:local/root "projects/gui"}
|
||||||
}
|
}
|
||||||
|
|
||||||
:aliases {;; Execute the app.
|
:aliases {;; Execute the app.
|
||||||
@@ -14,7 +15,8 @@
|
|||||||
|
|
||||||
;; Execute the app (prepared for more subprojects)
|
;; Execute the app (prepared for more subprojects)
|
||||||
:run/cli {:main-opts ["-m" "totp.app"]}
|
:run/cli {:main-opts ["-m" "totp.app"]}
|
||||||
;:run {:exec-fn totp.app/-main}
|
|
||||||
|
: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
|
||||||
;; Check test.edn for kaocha runner's config
|
;; Check test.edn for kaocha runner's config
|
||||||
@@ -45,7 +47,13 @@
|
|||||||
:ns-default build
|
:ns-default build
|
||||||
:exec-fn jar
|
:exec-fn jar
|
||||||
:exec-args {:subproj "web"}}
|
:exec-args {:subproj "web"}}
|
||||||
|
|
||||||
|
: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 the three libraries
|
||||||
:build/all {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
:build/all {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
:replace-deps {clj-totp/core {:local/root "projects/core"}}
|
:replace-deps {clj-totp/core {:local/root "projects/core"}}
|
||||||
@@ -56,5 +64,11 @@
|
|||||||
:uber/cli {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
:uber/cli {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
:ns-default build
|
:ns-default build
|
||||||
:exec-fn uber
|
:exec-fn uber
|
||||||
:exec-args {:subproj "cli" :main-ns "totp.app"}}}}
|
: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
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
org.clojure/clojure {:mvn/version "1.12.1"}
|
org.clojure/clojure {:mvn/version "1.12.1"}
|
||||||
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"]}
|
||||||
|
|||||||
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
|
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
mvxcvi/alphabase {:mvn/version "3.0.185"} ;; https://github.com/greglook/alphabase
|
mvxcvi/alphabase {:mvn/version "3.0.185"} ;; https://github.com/greglook/alphabase
|
||||||
;; 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.
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