Base features done

This commit is contained in:
Matthias Fulz 2022-06-26 23:34:41 +02:00
parent 12f205fae4
commit 08d20361bf
11 changed files with 605 additions and 119 deletions

View File

@ -21,8 +21,11 @@ import (
"strings"
"gitea.olznet.de/OlzNet/qmkenc"
"gitea.olznet.de/OlzNet/slog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/manifoldco/promptui"
)
var cnfSetFlags *pflag.FlagSet
@ -45,25 +48,27 @@ func cfgFormat(cfg *qmkenc.QCfg) string {
// cnfCmd represents the cnf command
var cnfCmd = &cobra.Command{
Use: "cnf [get|set] [flags]",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Short: "Manage configuration for encryption enabled qmk devices",
Long: `Via get the actual configuration can be retrieved. This
can be used for monitoring apps to directly show every aspect of the
configuration for the device.
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Set can be used to change the configuration of the device.
If enabling paranoia mode of a device be aware that this
cannot be undone without loosing your keys.`,
ValidArgs: []string{"get", "set"},
Args: cobra.ExactValidArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "get":
cfg, err := qmkenc.QCmdGetCfg(qdev)
if err != nil {
return err
slog.LOG_ERRORLN(err)
return
}
if subViper.GetBool("format") {
fmt.Println(cfgFormat(cfg))
return nil
return
}
if subViper.GetBool("max_error") {
fmt.Printf("%d\n", cfg.MaxError())
@ -88,35 +93,52 @@ to quickly create a Cobra application.`,
f := cnfSetFlags.Lookup("max_error")
if f != nil && f.Changed {
if err := qmkenc.QCmdSetCfgMaxError(qdev, uint8(subViper.GetInt("max_error"))); err != nil {
return err
slog.LOG_ERRORLN(err)
return
}
}
f = cnfSetFlags.Lookup("paranoia_mode")
if f != nil && f.Changed {
if subViper.GetBool("paranoia_mode") == false {
return fmt.Errorf("Paranoia Mode can't be unset -> YOU'VE BEEN WARNED !!!!")
slog.LOG_ERRORLN("Paranoia Mode can't be unset -> YOU'VE BEEN WARNED !!!!")
return
}
if err := qmkenc.QCmdSetCfgParanoia(qdev); err != nil {
return err
prompt := promptui.Select{
Label: "Paranoia mode cannot be unset. Make sure you HAVE STORED your keys. Are you sure to procceed",
Items: []string{"Yes", "No"},
}
_, result, err := prompt.Run()
if err != nil {
slog.LOG_ERRORFLN("Prompt failed %v", err)
return
}
if result == "Yes" {
slog.LOG_DEBUGLN("Enabling paranoia mode")
if err := qmkenc.QCmdSetCfgParanoia(qdev); err != nil {
slog.LOG_ERRORLN(err)
return
}
}
}
f = cnfSetFlags.Lookup("secure_mode")
if f != nil && f.Changed {
if err := qmkenc.QCmdSetCfgSecure(qdev, subViper.GetBool("secure_mode")); err != nil {
return err
slog.LOG_ERRORLN(err)
return
}
}
f = cnfSetFlags.Lookup("timeout_min")
if f != nil && f.Changed {
if err := qmkenc.QCmdSetCfgTimeout(qdev, uint8(subViper.GetInt("timeout_min"))); err != nil {
return err
slog.LOG_ERRORLN(err)
return
}
}
break
default:
return fmt.Errorf("Invalid command: %s", args[0])
slog.LOG_ERRORFLN("Invalid command: %s", args[0])
return
}
return nil
},
}

View File

@ -23,13 +23,13 @@ import (
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.design/x/clipboard"
"gitea.olznet.de/OlzNet/qmkenc"
"gitea.olznet.de/OlzNet/slog"
)
func readIn() (in []byte, err error) {
func cryptReadIn() (in []byte, err error) {
if subViper.GetBool("cin") {
in = clipboard.Read(clipboard.FmtText)
} else {
@ -68,61 +68,58 @@ func writeOut(out []byte) (c <-chan struct{}, err error) {
// cryptCmd represents the crypt command
var cryptCmd = &cobra.Command{
Use: "crypt [encrypt|decrypt] [flags]",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Short: "Encrypt or decrypt data by using the qmk device",
Long: `The data can be provided via clipboard or stdin and
by using base64 encoding.
The default will use clipboard without base64 encoding.`,
ValidArgs: []string{"encrypt", "decrypt"},
Args: cobra.ExactValidArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
Run: func(cmd *cobra.Command, args []string) {
if subViper.GetBool("cin") || subViper.GetBool("cout") {
if err := clipboard.Init(); err != nil {
return err
slog.LOG_ERRORLN(err)
return
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
in, err := readIn()
in, err := cryptReadIn()
if err != nil {
return err
slog.LOG_ERRORLN(err)
return
}
var out []byte
switch args[0] {
case "encrypt":
if err = qmkenc.QCmdEncrypt(qdev, in); err != nil {
return err
if out, err = qmkenc.QCmdEncrypt(qdev, in); err != nil {
slog.LOG_ERRORLN(err)
return
}
break
case "decrypt":
if err = qmkenc.QCmdDecrypt(qdev, in); err != nil {
return err
if out, err = qmkenc.QCmdDecrypt(qdev, in); err != nil {
slog.LOG_ERRORLN(err)
return
}
break
default:
return fmt.Errorf("Invalid command: %s", args[0])
}
out, err := qmkenc.QCmdGetBuffer(qdev)
if err != nil {
return err
slog.LOG_ERRORLN(fmt.Errorf("Invalid command: %s", args[0]))
return
}
c, err := writeOut(out)
if err != nil {
return err
slog.LOG_ERRORLN(err)
return
}
if c == nil {
return nil
return
}
select {
case <-c:
return nil
case <-time.After(time.Second * time.Duration(viper.GetInt("timeout"))):
return nil
return
case <-time.After(time.Second * time.Duration(subViper.GetInt("timeout"))):
return
}
},
}

View File

@ -19,31 +19,42 @@ import (
"fmt"
"gitea.olznet.de/OlzNet/qmkenc"
"gitea.olznet.de/OlzNet/slog"
"github.com/spf13/cobra"
)
// devCmd represents the dev command
var devCmd = &cobra.Command{
Use: "dev [unlock|lock|initialize|reset] [flags]",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
RunE: func(cmd *cobra.Command, args []string) error {
Use: "dev [unlock|lock|initialize|reset|mode] [flags]",
Short: "Device commands used to manage basic aspects",
Long: `This command is used to lock, unlock, initialize, reset
or retrieving the run mode of the device.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
slog.LOG_ERRORLN("No command")
return
}
var err error
switch args[0] {
case "unlock":
return qmkenc.QCmdUnlock(qdev)
err = qmkenc.QCmdUnlock(qdev)
case "lock":
return qmkenc.QCmdLock(qdev)
err = qmkenc.QCmdLock(qdev)
case "initialize":
return qmkenc.QCmdInitialize(qdev)
err = qmkenc.QCmdInitialize(qdev)
case "reset":
return qmkenc.QCmdReset(qdev)
err = qmkenc.QCmdReset(qdev)
case "mode":
m, sm, err := qmkenc.QCmdGetMode(qdev)
if err == nil {
fmt.Printf("M: %s, SM: %s\n", m, sm)
}
default:
return fmt.Errorf("Invalid command: %s", args[0])
slog.LOG_ERRORFLN("Invalid command: %s", args[0])
return
}
if err != nil {
slog.LOG_ERRORLN(err)
}
},
}

126
cmd/keepassxcproxy.go Normal file
View File

@ -0,0 +1,126 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"encoding/base64"
keepassxc_browser "gitea.olznet.de/OlzNet/golang-keepassxc-browser"
"gitea.olznet.de/OlzNet/qmkenc"
"gitea.olznet.de/OlzNet/slog"
"github.com/spf13/cobra"
)
type reqM struct {
}
func (r *reqM) ModifyReq(c *keepassxc_browser.ConnMsg) error {
slog.LOG_DEBUGF("Req Msg: %v\n", c)
return nil
}
func (r *reqM) ModifyRes(c *keepassxc_browser.ConnMsg) error {
slog.LOG_DEBUGF("Res Msg: %v\n", c)
switch c.ActionName {
case "get-logins":
resi := c.GetData().(*keepassxc_browser.MsgGetLogins)
for i, e := range resi.Entries {
slog.LOG_DEBUGF("e: %v\n", e)
newStringFields := []map[string]string{}
for _, f := range e.StringFields {
v, ok := f["KPH: qmkenc"]
if ok {
slog.LOG_DEBUGF("OK: %s\n", v)
slog.LOG_DEBUGF("OK e: %s\n", e)
switch v {
case "login":
if out, err := qmkenc.QCmdDecrypt(qdev, []byte(e.Login)); err != nil {
resi.Entries[i].Login = ""
} else {
resi.Entries[i].Login = string(out)
}
break
case "password":
slog.LOG_DEBUGF("OK pass e: %s\n", e)
slog.LOG_DEBUGF("Old pass: %v\n", e.Password)
slog.LOG_DEBUGF("Old login: %v\n", e.Login)
pass, err := base64.StdEncoding.DecodeString(e.Password)
if err != nil {
resi.Entries[i].Password = ""
break
}
if out, err := qmkenc.QCmdDecrypt(qdev, []byte(pass)); err != nil {
slog.LOG_DEBUGLN(err)
resi.Entries[i].Password = ""
} else {
slog.LOG_DEBUGF("Old pass: %s\n", e.Password)
slog.LOG_DEBUGF("New pass: %s\n", out)
resi.Entries[i].Password = string(out)
}
break
}
} else {
newStringFields = append(newStringFields, f)
}
}
resi.Entries[i].StringFields = newStringFields
}
}
return nil
}
// keepassxcproxyCmd represents the keepassxcproxy command
var keepassxcproxyCmd = &cobra.Command{
Use: "keepassxcproxy",
Short: "Proxy to interact with keepassxc via man in the middle",
Long: `This proxy will decrypt entries that are stored encrypted
and base64 encoded in login or password fields of keepassxc.
It will check for additional attribute "KPH: qemkenc" and pass the values
which can be either "password" or "login" for the password or the username
(login field) and decrypt them before providing to the requesting client.`,
Run: func(cmd *cobra.Command, args []string) {
slog.LOG_ERRORF("Starting proxy\n")
kpProxy, err := keepassxc_browser.NewKpXcMitm(new(reqM))
if err != nil {
slog.LOG_DEBUGF("error: %s\n", err)
return
}
slog.LOG_DEBUGF("Creating server\n")
server := keepassxc_browser.NewServer(kpProxy, &keepassxc_browser.StdinoutConnection{})
slog.LOG_DEBUGF("Starting run\n")
if err = server.Run(); err != nil {
slog.LOG_DEBUGF("error: %s\n", err)
return
}
slog.LOG_DEBUGF("Proxy end\n")
},
}
func init() {
rootCmd.AddCommand(keepassxcproxyCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// keepassxcproxyCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// keepassxcproxyCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View File

@ -16,36 +16,105 @@ limitations under the License.
package cmd
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"os"
"gitea.olznet.de/OlzNet/eqmk/eqmkcommon"
"gitea.olznet.de/OlzNet/qmkenc"
"gitea.olznet.de/OlzNet/slog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.design/x/clipboard"
)
var keysSetFlags *pflag.FlagSet
var keysGetFlags *pflag.FlagSet
func keysReadIn() (in []byte, err error) {
if subViper.GetBool("cin") {
in = clipboard.Read(clipboard.FmtText)
} else {
fmt.Println("Enter the key as hex encoded string without spaces (ae. adeeff1a...):")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
err = scanner.Err()
if err != nil {
return nil, err
}
in = scanner.Bytes()
slog.LOG_DEBUGF("input: %s\n", in)
}
if _in, err := hex.DecodeString(string(in)); err != nil {
return nil, err
} else {
in = _in
}
if len(in) != 32 {
slog.LOG_DEBUGF("decoded: %v\n", in)
return nil, fmt.Errorf("Incorrect key len. Got: %d, expected 32", len(in))
}
return in, err
}
// keysCmd represents the keys command
var keysCmd = &cobra.Command{
Use: "keys [get|set] [flags]",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Short: "Managing cryptographic keys of the qmk device",
Long: `Retrieving the keys via get to store them in a safe place.
You can use human readable format to easily write them to a paper.
Also it is possible to set the keys (only if paranoia mode is not activated)
either by using clipboard or entering them interactively.`,
ValidArgs: []string{"get", "set"},
Args: cobra.ExactValidArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "get":
keys, err := qmkenc.QCmdGetKeys(qdev)
var keys []byte
var err error
keys, err = qmkenc.QCmdGetKeys(qdev)
if err != nil {
return err
slog.LOG_ERRORLN(err)
return
}
if subViper.GetBool("verify") {
var qenc []byte
var qdec []byte
verificationString := []byte("Im checking if the received key is correct")
enc, err := eqmkcommon.AesCBCEncrypt(verificationString, keys)
if err != nil {
slog.LOG_ERRORLN(err)
return
}
slog.LOG_DEBUGF("Encrypted: len=%d %v\n", len(enc), enc)
if qenc, err = qmkenc.QCmdEncrypt(qdev, verificationString); err != nil {
slog.LOG_ERRORLN(err)
return
}
slog.LOG_DEBUGF("QEncrypted: len=%d %v\n", len(qenc), qenc)
dec, err := eqmkcommon.AesCBCDecrypt(qenc, keys)
if err != nil {
slog.LOG_ERRORLN(err)
return
}
slog.LOG_DEBUGF("Decrypted: %s\n", dec)
if qdec, err = qmkenc.QCmdDecrypt(qdev, enc); err != nil {
slog.LOG_ERRORLN(err)
return
}
slog.LOG_DEBUGF("QDecrypted: %s\n", qdec)
if bytes.Compare(qdec, dec) != 0 {
slog.LOG_ERRORLN("Key verification failed")
return
}
}
slog.LOG_DEBUGF("keys: %v\n", keys)
if subViper.GetBool("human_readable") {
for i := 0; i < len(keys); {
if subViper.GetBool("hex_mode") {
@ -77,11 +146,26 @@ to quickly create a Cobra application.`,
}
break
case "set":
if subViper.GetBool("cin") {
if err := clipboard.Init(); err != nil {
slog.LOG_ERRORLN(err)
return
}
}
in, err := keysReadIn()
if err != nil {
slog.LOG_ERRORLN(err)
return
}
if err = qmkenc.QCmdSetKeys(qdev, in); err != nil {
slog.LOG_ERRORLN(err)
return
}
break
default:
return fmt.Errorf("Invalid command: %s", args[0])
slog.LOG_ERRORFLN("Invalid command: %s", args[0])
return
}
return nil
},
}
@ -90,10 +174,12 @@ func init() {
rootCmd.AddCommand(keysCmd)
keysSetFlags = pflag.NewFlagSet("set", pflag.ExitOnError)
keysSetFlags.BoolP("cin", "c", true, "Read input from clipboard. If not set input will be read interactively")
keysGetFlags = pflag.NewFlagSet("get", pflag.ExitOnError)
keysGetFlags.BoolP("human_readable", "r", false, "Print the keys in a human readable format")
keysGetFlags.BoolP("hex_mode", "x", true, "Print the keys in hex values")
keysGetFlags.BoolP("verify", "v", true, "Verify the received key")
var cmd string
reached := false
@ -114,9 +200,11 @@ func init() {
keysCmd.Flags().AddFlagSet(keysGetFlags)
subViper.BindPFlag("human_readable", keysCmd.Flags().Lookup("human_readable"))
subViper.BindPFlag("hex_mode", keysCmd.Flags().Lookup("hex_mode"))
subViper.BindPFlag("verify", keysCmd.Flags().Lookup("verify"))
break
case "set":
keysCmd.Flags().AddFlagSet(keysSetFlags)
subViper.BindPFlag("cin", keysCmd.Flags().Lookup("cin"))
break
}
}

View File

@ -19,9 +19,14 @@ import (
"errors"
"fmt"
"os"
"os/signal"
"path"
"strings"
"syscall"
"time"
"gitea.olznet.de/OlzNet/slog"
"github.com/TheCreeper/go-notify"
"github.com/spf13/cobra"
homedir "github.com/mitchellh/go-homedir"
@ -31,7 +36,8 @@ import (
)
var cfgFile string
var stdLogger *slog.StdoutLogger
var stdLogger slog.SLogLogI
var fileLogger slog.SLogLogI
var qdev *qmkenc.QEncDevice
var subViper *viper.Viper
@ -41,45 +47,118 @@ func initSubViper() {
}
}
func initSLog() {
func initSLog() error {
slog.CLEAR_LOGGERS()
slog.SET_LEVEL(slog.GetSLogLevel(viper.GetString("loglevel")))
if viper.GetBool("logenable") {
stdLogger = slog.NewStdoutLogger().(*slog.StdoutLogger)
if viper.GetBool("logstdout") {
stdLogger = slog.NewStdoutLogger()
slog.ADD_LOGGER(stdLogger)
}
if viper.GetString("logfile") != "" {
fileLogger, err := slog.NewFileLogger(viper.GetString("logfile"))
if err != nil {
return err
}
slog.ADD_LOGGER(fileLogger)
}
if viper.GetBool("logverbose") {
slog.SET_VERBOSE(true)
}
if viper.GetString("loglevel") == slog.SLogLevelToString(slog.DEBUG) {
slog.LOG_WARNLN("!!! DEBUG ENABLED !!!")
slog.LOG_WARNLN("!!! There could be sensitive information logged !!!")
slog.LOG_WARNLN("!!! NEVER use this together with productive keys !!!")
}
return nil
}
func initInfoType() {
switch viper.GetString("info_type") {
case "notify":
ntf := notify.NewNotification("eqmk request", "")
var err error
qmkenc.QEncInfoHandler = func(msg string) {
ntf.Body = msg
ntf.AppIcon = "dialog-question"
if ntf.ReplacesID, err = ntf.Show(); err != nil {
slog.LOG_ERRORLN(err)
}
}
qmkenc.QEncEndHandler = func() {
notify.CloseNotification(ntf.ReplacesID)
}
qmkenc.QEncErrHandler = func(err error) {
ntf.Body = err.Error()
ntf.AppIcon = "dialog-error"
if ntf.ReplacesID, err = ntf.Show(); err != nil {
slog.LOG_ERRORLN(err)
}
}
break
}
}
func lock() error {
for {
if _, err := os.Stat(viper.GetString("lockfile")); err == nil {
slog.LOG_DEBUGLN("Waiting for lock")
time.Sleep(time.Second * 1)
continue
} else if os.IsNotExist(err) {
file, err := os.Create(viper.GetString("lockfile"))
if err != nil {
return err
}
file.Close()
return nil
}
}
}
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "eqmk",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Short: "command line client to interact with encryption enabled qmk device",
Long: `This tool is used to manage and interact with encryption enabled qmk devices.
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
It can retrieve information about the configuration and the running mode for the device.
Further it can decrypt, encrypt data and also change configuration of the device.
There is also a keepassxc proxy included that can interact directly with keepassxc
to decrypt encrypted logins and passwords transparent for browsers.`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if err := lock(); err != nil {
slog.LOG_ERRORLN(err)
os.Exit(-1)
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGKILL)
go func() {
<-sigChan
slog.LOG_DEBUGLN("Removing lockfile")
os.Remove(viper.GetString("lockfile"))
}()
slog.LOG_DEBUGLN("Root Pre run started")
initInfoType()
actDev := viper.GetString("device.path")
if actDev == "" {
return fmt.Errorf("No device specified")
slog.LOG_ERRORLN("No device specified")
os.Exit(-1)
}
if viper.GetInt("device.blocksize") == 0 {
qdevs, err := qmkenc.QEncScanDevices()
if err != nil {
slog.LOG_ERRORLN(err)
return err
os.Exit(-1)
}
for _, qd := range qdevs {
@ -91,27 +170,28 @@ to quickly create a Cobra application.`,
if qdev == nil {
err = fmt.Errorf("Device: %s not found\n", actDev)
slog.LOG_ERRORF("%s\n", err)
return err
}
} else {
qdev = qmkenc.QEncNewDevice(actDev, uint8(viper.GetInt("device.blocksize")), viper.GetInt("device.timeout"), viper.GetInt("device.retry_timeout"), viper.GetInt("device.retry_wait"))
if err := qdev.Open(); err != nil {
slog.LOG_ERRORLN(err)
return err
os.Exit(-1)
}
}
qdev = qmkenc.QEncNewDevice(actDev, uint8(viper.GetInt("device.blocksize")), viper.GetInt("device.timeout"), viper.GetInt("device.retry_timeout"), viper.GetInt("device.retry_wait"))
if err := qdev.Open(); err != nil {
slog.LOG_ERRORLN(err)
os.Exit(-1)
}
slog.LOG_DEBUGLN("Open")
return nil
time.Sleep(2 * time.Second)
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) (err error) {
PersistentPostRun: func(cmd *cobra.Command, args []string) {
slog.LOG_DEBUGLN("Finished")
if qdev != nil {
err = qdev.Close()
err := qdev.Close()
if err != nil {
slog.LOG_ERRORLN(err)
}
}
slog.LOG_DEBUGLN("Removing lockfile")
os.Remove(viper.GetString("lockfile"))
slog.FINISH()
return err
},
}
@ -127,30 +207,50 @@ func Execute() {
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.eqmk/eqmk.conf)")
rootCmd.PersistentFlags().StringP("level", "", "INFO", "Log level [ERROR, WARN, INFO, DEBUG]")
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.eqmk/eqmk.toml)")
rootCmd.PersistentFlags().StringP("loglevel", "", "ERROR", "Log level [ERROR, WARN, INFO, DEBUG]")
rootCmd.PersistentFlags().BoolP("verbose", "", false, "Verbose logging")
rootCmd.PersistentFlags().BoolP("log", "", false, "Enable logging")
rootCmd.PersistentFlags().BoolP("logstdout", "", false, "Enable logging to stdout")
rootCmd.PersistentFlags().StringP("logfile", "", "", "Enable logging to file")
rootCmd.PersistentFlags().StringP("lockfile", "", "/tmp/eqmk.lock", "Lock file to prevent multiple accessing device")
rootCmd.PersistentFlags().StringP("device", "d", "", "qmk device path")
rootCmd.PersistentFlags().IntP("blocksize", "", 0, "qmk device blocksize")
rootCmd.PersistentFlags().IntP("timeout", "", 5, "qmk device retry timeout")
rootCmd.PersistentFlags().IntP("device_timeout", "", 5, "qmk device retry timeout")
rootCmd.PersistentFlags().IntP("retry_timeout", "", 10, "qmk device retry timeout")
rootCmd.PersistentFlags().IntP("retry_wait", "", 1, "qmk device retry wait time")
viper.BindPFlag("loglevel", rootCmd.PersistentFlags().Lookup("level"))
rootCmd.PersistentFlags().StringP("info_type", "", "notify", "type for request messages ([notfiy|none])")
viper.BindPFlag("loglevel", rootCmd.PersistentFlags().Lookup("loglevel"))
viper.BindPFlag("logverbose", rootCmd.PersistentFlags().Lookup("verbose"))
viper.BindPFlag("device.path", rootCmd.PersistentFlags().Lookup("device"))
viper.BindPFlag("device.blocksize", rootCmd.PersistentFlags().Lookup("blocksize"))
viper.BindPFlag("device.timeout", rootCmd.PersistentFlags().Lookup("timeout"))
viper.BindPFlag("device.timeout", rootCmd.PersistentFlags().Lookup("device_timeout"))
viper.BindPFlag("device.retry_timeout", rootCmd.PersistentFlags().Lookup("retry_timeout"))
viper.BindPFlag("device.retry_wait", rootCmd.PersistentFlags().Lookup("retry_wait"))
viper.BindPFlag("logenable", rootCmd.PersistentFlags().Lookup("log"))
viper.BindPFlag("logstdout", rootCmd.PersistentFlags().Lookup("logstdout"))
viper.BindPFlag("logfile", rootCmd.PersistentFlags().Lookup("logfile"))
viper.BindPFlag("lockfile", rootCmd.PersistentFlags().Lookup("lockfile"))
viper.SetDefault("loglevel", "INFO")
viper.BindPFlag("info_type", rootCmd.PersistentFlags().Lookup("info_type"))
viper.SetDefault("loglevel", "ERROR")
viper.SetDefault("logverbose", false)
viper.SetDefault("logenable", false)
viper.SetDefault("logstdout", false)
viper.SetDefault("info_type", "notify")
// crappy native-messaging-hosts ...
if (len(os.Args) == 2 && strings.HasPrefix(os.Args[1], "chrome-extension")) || // chrome
(len(os.Args) == 3 && strings.HasPrefix(os.Args[2], "keepassxc-browser@keepassxc.org")) { // firefox
os.Args = []string{os.Args[0], "keepassxcproxy"}
}
if len(os.Args) >= 2 && os.Args[1] == "keepassxcproxy" {
// we have to override logging as we cannot write to stdout
viper.Set("logstdout", false)
}
}
// initConfig reads in config file and ENV variables if set.
@ -185,6 +285,9 @@ func initConfig() {
if err := viper.ReadInConfig(); err != nil {
panic(err)
}
initSLog()
if err := initSLog(); err != nil {
fmt.Println(err)
os.Exit(1)
}
initSubViper()
}

View File

@ -27,13 +27,9 @@ import (
// scanCmd represents the scan command
var scanCmd = &cobra.Command{
Use: "scan",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Short: "Scan for encryption enabled qmk devices",
Long: `It will scan all usb hid devices and check
if they are enabled for encryption.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return nil
},

60
eqmkcommon/crypt.go Normal file
View File

@ -0,0 +1,60 @@
package eqmkcommon
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)
func AesCBCEncrypt(data, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blocksize := block.BlockSize()
pdata, err := pkcs7Pad(data, blocksize)
if err != nil {
return nil, err
}
encdata := make([]byte, blocksize+len(pdata))
iv := encdata[:blocksize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(encdata[blocksize:], pdata)
return encdata, nil
}
func AesCBCDecrypt(data, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blocksize := block.BlockSize()
if len(data) < blocksize {
return nil, fmt.Errorf("Cipher text too short")
}
iv := data[:blocksize]
data = data[blocksize:]
if len(data)%blocksize != 0 {
return nil, fmt.Errorf("Cipher text is not a multiple of block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(data, data)
data, err = pkcs7Unpad(data, blocksize)
if err != nil {
return nil, err
}
return data, nil
}

63
eqmkcommon/pkcs7.go Normal file
View File

@ -0,0 +1,63 @@
package eqmkcommon
import (
"bytes"
"errors"
)
// PKCS7 padding.
// PKCS7 errors.
var (
// ErrInvalidBlockSize indicates hash blocksize <= 0.
ErrInvalidBlockSize = errors.New("invalid blocksize")
// ErrInvalidPKCS7Data indicates bad input to PKCS7 pad or unpad.
ErrInvalidPKCS7Data = errors.New("invalid PKCS7 data (empty or not padded)")
// ErrInvalidPKCS7Padding indicates PKCS7 unpad fails to bad input.
ErrInvalidPKCS7Padding = errors.New("invalid padding on input")
)
// pkcs7Pad right-pads the given byte slice with 1 to n bytes, where
// n is the block size. The size of the result is x times n, where x
// is at least 1.
func pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
if blocksize <= 0 {
return nil, ErrInvalidBlockSize
}
if b == nil || len(b) == 0 {
return nil, ErrInvalidPKCS7Data
}
n := blocksize - (len(b) % blocksize)
pb := make([]byte, len(b)+n)
copy(pb, b)
copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
return pb, nil
}
// pkcs7Unpad validates and unpads data from the given bytes slice.
// The returned value will be 1 to n bytes smaller depending on the
// amount of padding, where n is the block size.
func pkcs7Unpad(b []byte, blocksize int) ([]byte, error) {
if blocksize <= 0 {
return nil, ErrInvalidBlockSize
}
if b == nil || len(b) == 0 {
return nil, ErrInvalidPKCS7Data
}
if len(b)%blocksize != 0 {
return nil, ErrInvalidPKCS7Padding
}
c := b[len(b)-1]
n := int(c)
if n == 0 || n > len(b) {
return nil, ErrInvalidPKCS7Padding
}
for i := 0; i < n; i++ {
if b[len(b)-n+i] != c {
return nil, ErrInvalidPKCS7Padding
}
}
return b[:len(b)-n], nil
}

12
go.mod
View File

@ -1,22 +1,33 @@
module gitea.olznet.de/OlzNet/eqmk
replace gitea.olznet.de/OlzNet/slog => ../slog
replace gitea.olznet.de/OlzNet/qmkenc => ../qmkenc
replace gitea.olznet.de/OlzNet/golang-keepassxc-browser => ../golang-keepassxc-browser
go 1.18
require (
gitea.olznet.de/OlzNet/golang-keepassxc-browser v0.0.0-00010101000000-000000000000
gitea.olznet.de/OlzNet/qmkenc v0.0.0-00010101000000-000000000000
gitea.olznet.de/OlzNet/slog v1.2.4
github.com/TheCreeper/go-notify v0.2.0
github.com/manifoldco/promptui v0.9.0
github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.11.0
golang.design/x/clipboard v0.6.2
)
require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/godbus/dbus/v5 v5.0.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jamesruan/sodium v1.0.14 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
@ -24,7 +35,6 @@ require (
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/sstallion/go-hid v0.0.0-20211019232252-c64377bfa49e // indirect
github.com/subosito/gotenv v1.2.0 // indirect
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect

14
go.sum
View File

@ -36,13 +36,16 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.olznet.de/OlzNet/slog v1.2.4 h1:22/+57/2J7EnFQAwT6xRENgvPeI2RwSmOWuXwkujpf4=
gitea.olznet.de/OlzNet/slog v1.2.4/go.mod h1:xHB0ZnXIXFdnISKpWvDnrBSl4dGB3rzQji36n2sbJXg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/TheCreeper/go-notify v0.2.0 h1:akzlSD8IWx+uOZqGNwS0FYsThYuw11JkXsYiQXY5kgo=
github.com/TheCreeper/go-notify v0.2.0/go.mod h1:paZnY8fMbaOyZLQWJitGWAMrO5ot3Ow7id47cyEL1KA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
@ -63,6 +66,8 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -125,6 +130,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jamesruan/sodium v1.0.14 h1:JfOHobip/lUWouxHV3PwYwu3gsLewPrDrZXO3HuBzUU=
github.com/jamesruan/sodium v1.0.14/go.mod h1:GK2+LACf7kuVQ9k7Irk0MB2B65j5rVqkz+9ylGIggZk=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -136,6 +143,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
@ -287,6 +296,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=