-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c4563e9
Showing
14 changed files
with
547 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
version: 2 | ||
updates: | ||
- package-ecosystem: docker | ||
directory: "/" | ||
schedule: | ||
interval: daily | ||
time: "21:00" | ||
open-pull-requests-limit: 10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
on: | ||
push: | ||
tags: | ||
- v* | ||
|
||
jobs: | ||
releases-matrix: | ||
name: Release Go Binary | ||
runs-on: ubuntu-latest | ||
permissions: | ||
contents: write | ||
strategy: | ||
max-parallel: 1 | ||
matrix: | ||
include: | ||
- goarch: amd64 | ||
goos: darwin | ||
- goarch: arm64 | ||
goos: darwin | ||
- goarch: amd64 | ||
goos: linux | ||
- goarch: arm64 | ||
goos: linux | ||
- goarch: amd64 | ||
goos: windows | ||
steps: | ||
- name: Show environment | ||
run: export | ||
- uses: actions/checkout@v3 | ||
- uses: ncipollo/release-action@v1 | ||
with: | ||
allowUpdates: true | ||
token: ${{ secrets.GITHUB_TOKEN }} | ||
- uses: wangyoucao577/go-release-action@master | ||
with: | ||
github_token: ${{ secrets.GITHUB_TOKEN }} | ||
goos: ${{ matrix.goos }} | ||
goarch: ${{ matrix.goarch }} | ||
build_command: "make" | ||
binary_name: "2fa" | ||
extra_files: 2fa | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
2fa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
build: | ||
go build -o 2fa main.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
|
||
# 2fa | ||
|
||
2fa is a two-factor authentication command line tool. | ||
|
||
## Install | ||
|
||
```bash | ||
go get github.com/jimyag/2fa@latest | ||
``` | ||
|
||
or download the binary from <https://github.com/jimyag/2fa/releases> | ||
|
||
## Usage | ||
|
||
### Add | ||
|
||
Add a 2fa key | ||
|
||
`2fa add <name> <key>` | ||
|
||
```bash | ||
2fa add jimyag 4LDRN6EUDSF3RNV7 | ||
``` | ||
|
||
### Get | ||
|
||
Get a 2fa key and copy to clipboard | ||
|
||
`2fa get <name> [--copy/-c]` | ||
|
||
```bash | ||
2fa get jimyag | ||
2fa get jimyag --copy | ||
``` | ||
|
||
### List | ||
|
||
List all 2fa keys and display in a table | ||
|
||
`2fa list` | ||
|
||
```bash | ||
2fa list | ||
+--------+--------+------------+-----------+ | ||
| NAME | TOTP | LIFETIME/S | NEXT TOTP | | ||
+--------+--------+------------+-----------+ | ||
| jimyag | 056907 | 2 | 134552 | | ||
+--------+--------+------------+-----------+ | ||
|
||
``` | ||
|
||
### Delete | ||
|
||
Delete a 2fa key | ||
|
||
`2fa del <name>` | ||
|
||
```bash | ||
2fa del jimyag | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package cmd | ||
|
||
import ( | ||
"crypto/aes" | ||
"crypto/cipher" | ||
"crypto/rand" | ||
"encoding/base64" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"log" | ||
"os" | ||
"strings" | ||
) | ||
|
||
const secretKey = "018fcf3d2bf3745ca38d0360d6816e68" | ||
|
||
type Key struct { | ||
Key string `json:"key"` | ||
} | ||
type TwoFactor struct { | ||
Keys map[string]Key `json:"keys"` | ||
cfgFile string `json:"-"` | ||
} | ||
|
||
func New() (*TwoFactor, error) { | ||
tf := &TwoFactor{ | ||
Keys: map[string]Key{ | ||
"": { | ||
Key: secretKey, | ||
}, | ||
}, | ||
} | ||
homeDir, err := os.UserHomeDir() | ||
if err != nil { | ||
return nil, fmt.Errorf("could not get home directory: %w", err) | ||
} | ||
|
||
configDir := fmt.Sprintf("%s/.config/2fa", homeDir) | ||
if err = os.MkdirAll(configDir, 0755); err != nil { | ||
return nil, fmt.Errorf("could not create config directory %s : %w", configDir, err) | ||
} | ||
|
||
tf.cfgFile = fmt.Sprintf("%s/.2fa.json", configDir) | ||
_, err = os.Stat(tf.cfgFile) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
if err = tf.Write(); err != nil { | ||
return nil, fmt.Errorf("could not write config file %s : %w", tf.cfgFile, err) | ||
} | ||
} else { | ||
return nil, fmt.Errorf("could not stat config file %s : %w", tf.cfgFile, err) | ||
} | ||
} | ||
err = tf.Load() | ||
return tf, err | ||
|
||
} | ||
|
||
func (tf *TwoFactor) Add(name, key string) error { | ||
key = strings.ToUpper(key) | ||
tf.Keys[name] = Key{key} | ||
return tf.Write() | ||
} | ||
|
||
func (tf *TwoFactor) List() map[string]Key { | ||
list := make(map[string]Key) | ||
for k, v := range tf.Keys { | ||
list[k] = v | ||
} | ||
return list | ||
} | ||
|
||
func (tf *TwoFactor) Get(name string) string { | ||
if key, ok := tf.Keys[name]; ok { | ||
return key.Key | ||
} | ||
return "" | ||
} | ||
|
||
func (tf *TwoFactor) Remove(name string) error { | ||
delete(tf.Keys, name) | ||
return tf.Write() | ||
} | ||
|
||
func (tf *TwoFactor) Write() error { | ||
file, err := os.OpenFile(tf.cfgFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) | ||
if err != nil { | ||
log.Fatalln("could not open config file", err) | ||
return err | ||
} | ||
defer file.Close() | ||
plaintext, err := json.Marshal(tf.Keys) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
cipherText, err := tf.EncryptMessage(plaintext) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = file.WriteString(cipherText) | ||
return err | ||
} | ||
|
||
func (tf *TwoFactor) Load() error { | ||
file, err := os.Open(tf.cfgFile) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
cipherText, err := io.ReadAll(file) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
data, err := tf.DecryptMessage(cipherText) | ||
if err != nil { | ||
fmt.Println(err) | ||
return json.Unmarshal(cipherText, &tf.Keys) | ||
} | ||
return json.Unmarshal([]byte(data), &tf.Keys) | ||
} | ||
|
||
func (tf *TwoFactor) EncryptMessage(message []byte) (string, error) { | ||
block, err := aes.NewCipher([]byte(secretKey)) | ||
if err != nil { | ||
return "", fmt.Errorf("could not create new cipher: %v", err) | ||
} | ||
|
||
cipherText := make([]byte, aes.BlockSize+len(message)) | ||
iv := cipherText[:aes.BlockSize] | ||
if _, err = io.ReadFull(rand.Reader, iv); err != nil { | ||
return "", fmt.Errorf("could not encrypt: %v", err) | ||
} | ||
|
||
stream := cipher.NewCFBEncrypter(block, iv) | ||
stream.XORKeyStream(cipherText[aes.BlockSize:], message) | ||
|
||
return base64.StdEncoding.EncodeToString(cipherText), nil | ||
} | ||
|
||
func (tf *TwoFactor) DecryptMessage(message []byte) (string, error) { | ||
cipherText, err := base64.StdEncoding.DecodeString(string(message)) | ||
if err != nil { | ||
return "", fmt.Errorf("could not base64 decode: %v", err) | ||
} | ||
|
||
block, err := aes.NewCipher([]byte(secretKey)) | ||
if err != nil { | ||
return "", fmt.Errorf("could not create new cipher: %v", err) | ||
} | ||
|
||
if len(cipherText) < aes.BlockSize { | ||
return "", fmt.Errorf("invalid ciphertext block size") | ||
} | ||
|
||
iv := cipherText[:aes.BlockSize] | ||
cipherText = cipherText[aes.BlockSize:] | ||
|
||
stream := cipher.NewCFBDecrypter(block, iv) | ||
stream.XORKeyStream(cipherText, cipherText) | ||
|
||
return string(cipherText), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package cmd | ||
|
||
import ( | ||
"log" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
var addCmd = &cobra.Command{ | ||
Use: "add", | ||
Short: "2fa add <name> <key>", | ||
Run: addRun, | ||
} | ||
|
||
func addRun(cmd *cobra.Command, args []string) { | ||
if len(args) != 2 { | ||
_ = cmd.Help() | ||
return | ||
} | ||
if err := tfa.Add(args[0], args[1]); err != nil { | ||
log.Fatalln("could not add key", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package cmd | ||
|
||
import ( | ||
"log" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
var delCmd = &cobra.Command{ | ||
Use: "del", | ||
Short: "2fa del <name>", | ||
Run: delRun, | ||
} | ||
|
||
func delRun(cmd *cobra.Command, args []string) { | ||
if len(args) != 1 { | ||
_ = cmd.Help() | ||
return | ||
} | ||
if err := tfa.Remove(args[0]); err != nil { | ||
log.Fatalln("could not remove key", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"time" | ||
|
||
"github.com/atotto/clipboard" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var getCmd = &cobra.Command{ | ||
Use: "get", | ||
Short: "2fa get <name>", | ||
Run: getRun, | ||
} | ||
|
||
var copyToClipboard bool | ||
|
||
func init() { | ||
getCmd.Flags().BoolVarP(©ToClipboard, "copy", "c", false, "copy to clipboard") | ||
} | ||
|
||
func getRun(cmd *cobra.Command, args []string) { | ||
if len(args) != 1 { | ||
_ = cmd.Help() | ||
return | ||
} | ||
key := tfa.Get(args[0]) | ||
if key == "" { | ||
log.Fatalln("could not find key") | ||
} | ||
|
||
code, err := GenTOTP(key, time.Now(), 6, 30) | ||
if err != nil { | ||
log.Fatalln("could not generate code", err) | ||
} | ||
if copyToClipboard { | ||
if err = clipboard.WriteAll(code); err != nil { | ||
log.Fatalln("could not copy to clipboard", err) | ||
} | ||
} else { | ||
fmt.Println(code) | ||
|
||
} | ||
|
||
} |
Oops, something went wrong.