package main import ( "C" "crypto/hmac" "crypto/sha1" "crypto/sha256" "crypto/sha512" "github.com/creachadair/otp" "hash" "log" "os" "path/filepath" "runtime" "strconv" "strings" "time" "unsafe" ) var token string var homeDir string var configs map[string]otp.Config func getSha1() func() hash.Hash { return sha1.New } //export loadConfigs func loadConfigs() { osVar := runtime.GOOS if osVar == "windows" { homeDir = os.Getenv("USERPROFILE") } else { homeDir = os.Getenv("HOME") } configs = make(map[string]otp.Config) files, err := os.ReadDir(homeDir + "/.totp") if err != nil { log.Fatal(err) } for _, fileEntry := range files { fileBytes, err := os.ReadFile(homeDir + "/.totp/" + fileEntry.Name()) if err != nil { log.Fatal(err) } fileStr := string(fileBytes) lineSplt := strings.Split(fileStr, "\n") var key string var hashVar func() hash.Hash var timeStep func() uint64 var counter uint64 var digits int var format func(hash []byte, length int) string for _, line := range lineSplt { kv := strings.Split(line, ":") if len(kv) < 2 { continue } kv[1] = strings.TrimSpace(kv[1]) switch strings.ToLower(kv[0]) { case "key": key = kv[1] case "hash": { switch kv[1] { case "sha1": hashVar = getSha1() case "sha256": hashVar = func() hash.Hash { return sha256.New() } case "sha512": hashVar = func() hash.Hash { return sha512.New() } case "hmac": { keyBytes, err := otp.ParseKey(key) if err != nil { log.Fatal(err) } hashVar = func() hash.Hash { return hmac.New(getSha1(), keyBytes) } // Probably not proper implementation but who uses HMAC anyway } } } case "timestep": { ts, err := strconv.Atoi(kv[1]) if err != nil { log.Fatal(err) } timeStep = otp.TimeWindow(ts) } case "counter": { cNum, err := strconv.ParseUint(kv[1], 10, 0) if err != nil { log.Fatal(err) } if cNum < 6 || cNum > 10 { log.Fatal("Counter not in range 6-10") } counter = cNum } case "digits": { dNum, err := strconv.Atoi(kv[1]) if err != nil { log.Fatal(err) } if dNum < 1 || dNum > 10 { log.Fatal("Digits is not in range 1-10") } digits = dNum } case "format": { } //TODO implement } } config := otp.Config{ Hash: hashVar, TimeStep: timeStep, Counter: counter, Digits: digits, Format: format, } err = config.ParseKey(key) if err != nil { log.Fatal(err) } fileName := fileEntry.Name() ext := filepath.Ext(fileName) configs[strings.TrimSuffix(fileName, ext)] = config } } //export getConfigNames func getConfigNames() (**C.char, C.int) { configsLen := len(configs) cArray := C.malloc(C.size_t(configsLen) * C.size_t(unsafe.Sizeof(uintptr(0)))) ar := unsafe.Slice((**C.char)(cArray), configsLen) i := 0 for name := range configs { ar[i] = C.CString(name) i++ } return (**C.char)(cArray), C.int(configsLen) } //export getConfig func getConfig(configName string) (*C.char, C.int) { files, err := os.ReadDir(homeDir + "/.totp") if err != nil { log.Fatal(err) } for _, fileEntry := range files { if strings.HasPrefix(fileEntry.Name(), configName) { fileBytes, err := os.ReadFile(homeDir + "/.totp/" + fileEntry.Name()) if err != nil { log.Fatal(err) } fileStr := string(fileBytes) cStr := C.CString(fileStr) runtime.KeepAlive(fileStr) return cStr, (C.int)(len(fileStr)) } } return nil, -1 } //export getCode func getCode(configName string) *C.char { code := configs[configName].TOTP() cCode := C.CString(code) runtime.KeepAlive(code) return cCode } //export getTimeRemaining func getTimeRemaining(n int) uint64 { return uint64(n) - (uint64(time.Now().Unix()) % uint64(n)) } //export getTimeRemainingMS func getTimeRemainingMS(n int) uint64 { return (uint64(n) * 1000) - (uint64(time.Now().UnixNano()/1e6) % (uint64(n) * 1000)) } func main() {}