Initial commit.
commit
cb7e5027ed
|
@ -0,0 +1,27 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
*.exe
|
||||||
|
/pixelqr
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
*.test
|
|
@ -0,0 +1,52 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/eliukblau/pixterm/ansimage"
|
||||||
|
"github.com/qpliu/qrencode-go/qrencode"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConvertGridToANSImage(grid *qrencode.BitGrid) (s string, err error) {
|
||||||
|
// HACK - ansimage has an image offset bug which hides the first two rows
|
||||||
|
img, err := ansimage.New(2+2+((grid.Height()+1)/2)*2, 2+grid.Width())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// white borders
|
||||||
|
for x := 0; x < 1+grid.Width()+1; x++ {
|
||||||
|
err = img.SetAt(2, x, 255, 255, 255)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = img.SetAt(2+grid.Height()+1, x, 255, 255, 255)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for y := 1; y < grid.Height()+3; y++ {
|
||||||
|
err = img.SetAt(y, 0, 255, 255, 255)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = img.SetAt(y, grid.Width()+1, 255, 255, 255)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for y := 0; y < grid.Height(); y++ {
|
||||||
|
for x := 0; x < grid.Width(); x++ {
|
||||||
|
val := uint8(0)
|
||||||
|
if !grid.Get(x, y) {
|
||||||
|
val = 255
|
||||||
|
}
|
||||||
|
// HACK - ansimage has an image offset bug which hides the first two rows
|
||||||
|
err = img.SetAt(y+3, x+1, val, val, val)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = img.Render()
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base32"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
|
||||||
|
"github.com/qpliu/qrencode-go/qrencode"
|
||||||
|
|
||||||
|
"git.icedream.tech/icedream/pixelqr/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Algorithm_SHA1 = "sha1"
|
||||||
|
Algorithm_SHA256 = "sha256"
|
||||||
|
Algorithm_SHA512 = "sha512"
|
||||||
|
Algorithm_Default = Algorithm_SHA1
|
||||||
|
|
||||||
|
ECLevel_L = "l"
|
||||||
|
ECLevel_M = "m"
|
||||||
|
ECLevel_Q = "q"
|
||||||
|
ECLevel_H = "h"
|
||||||
|
|
||||||
|
Type_TOTP = "totp"
|
||||||
|
Type_HOTP = "hotp"
|
||||||
|
|
||||||
|
Digits_Default = 6
|
||||||
|
|
||||||
|
Period_Default = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ecLevelMapping = map[string]qrencode.ECLevel{
|
||||||
|
ECLevel_L: qrencode.ECLevelL,
|
||||||
|
ECLevel_M: qrencode.ECLevelM,
|
||||||
|
ECLevel_Q: qrencode.ECLevelQ,
|
||||||
|
ECLevel_H: qrencode.ECLevelH,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
app = kingpin.New("pixelqr", "Generates QR code from any input and displays it in your ANSI true-color compatible terminal.")
|
||||||
|
|
||||||
|
flagECLevel = app.Flag("ec", "Sets the error correction level for the generated code.").
|
||||||
|
Default(ECLevel_L).
|
||||||
|
Enum(ECLevel_L, ECLevel_M, ECLevel_Q, ECLevel_H)
|
||||||
|
|
||||||
|
cmdText = app.Command("text", "Generate QR code from plain text file or stdin.")
|
||||||
|
cmdTextArgInput = cmdText.Arg("input", "The input file to encode.").
|
||||||
|
ExistingFile()
|
||||||
|
|
||||||
|
cmdOtp = app.Command("otp", "Generate QR code for ")
|
||||||
|
cmdOtpFlagType = cmdOtp.Flag("type", "The type of the key.").
|
||||||
|
Default(Type_TOTP).
|
||||||
|
Enum(Type_TOTP, Type_HOTP)
|
||||||
|
cmdOtpFlagLabel = cmdOtp.Flag("label", "The name for the OTP key.").
|
||||||
|
String()
|
||||||
|
cmdOtpFlagIssuer = cmdOtp.Flag("issuer", "Indicates the provider or service this account is associated with.").
|
||||||
|
Default("pixelqr").String()
|
||||||
|
cmdOtpFlagAlgorithm = cmdOtp.Flag("algorithm", "Determines the key generation algorithm. Not supported by all authenticator apps.").
|
||||||
|
Default(fmt.Sprintf("%s", Algorithm_Default)).
|
||||||
|
Enum(Algorithm_SHA1, Algorithm_SHA256, Algorithm_SHA512)
|
||||||
|
cmdOtpFlagDigits = cmdOtp.Flag("digits", "Determines how long of a one-time passcode to display to the user.").
|
||||||
|
Default(fmt.Sprintf("%d", Digits_Default)).
|
||||||
|
Uint8()
|
||||||
|
cmdOtpFlagCounter = cmdOtp.Flag("counter", "Required if type is hotp, sets the initial counter value for code generation.").
|
||||||
|
Int()
|
||||||
|
cmdOtpFlagPeriod = cmdOtp.Flag("period", "Only if type is totp, defines a period that a TOTP code will be valid for. Not supported by all authenticator apps.").
|
||||||
|
Default(Period_Default.String()).
|
||||||
|
Duration()
|
||||||
|
cmdOtpFlagBase32Encoded = cmdOtp.Flag("base32encoded", "Set this to true if the secret master key you pass in has already been base32-encoded.").
|
||||||
|
Bool()
|
||||||
|
cmdOtpArgSecret = cmdOtp.Arg("secret", "The secret master key.").
|
||||||
|
Required().String()
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCommandNotFound = errors.New("command not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Fatal(err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
commandName, err := (app.Parse(os.Args[1:]))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch commandName {
|
||||||
|
case cmdText.FullCommand(): // plain text -> qr
|
||||||
|
var inputFile *os.File
|
||||||
|
if cmdTextArgInput == nil || *cmdTextArgInput == "-" || *cmdTextArgInput == "" {
|
||||||
|
inputFile = os.Stdin
|
||||||
|
} else {
|
||||||
|
inputFile, err = os.Open(*cmdTextArgInput)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer inputFile.Close()
|
||||||
|
input, err := ioutil.ReadAll(inputFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
generateCliQR(string(input))
|
||||||
|
|
||||||
|
case cmdOtp.FullCommand(): // otp -> qr
|
||||||
|
// https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
||||||
|
// otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
|
||||||
|
query := url.Values{}
|
||||||
|
query.Set("issuer", *cmdOtpFlagIssuer)
|
||||||
|
if !*cmdOtpFlagBase32Encoded {
|
||||||
|
// base32-encode secret before generating url
|
||||||
|
query.Set("secret", base32.StdEncoding.EncodeToString([]byte(*cmdOtpArgSecret)))
|
||||||
|
} else {
|
||||||
|
// secret already base32-encoded
|
||||||
|
query.Set("secret", *cmdOtpArgSecret)
|
||||||
|
}
|
||||||
|
if cmdOtpFlagAlgorithm != nil && *cmdOtpFlagAlgorithm != Algorithm_Default {
|
||||||
|
query.Set("algorithm", *cmdOtpFlagAlgorithm)
|
||||||
|
}
|
||||||
|
if cmdOtpFlagDigits != nil && *cmdOtpFlagDigits != Digits_Default {
|
||||||
|
query.Set("digits", fmt.Sprintf("%d", *cmdOtpFlagDigits))
|
||||||
|
}
|
||||||
|
/*if cmdOtpFlagLabel != nil && len(*cmdOtpFlagLabel) > 0 {
|
||||||
|
query.Set("label"] = *cmdOtpFlagLabel
|
||||||
|
}*/
|
||||||
|
if cmdOtpFlagPeriod != nil && *cmdOtpFlagPeriod != Period_Default {
|
||||||
|
periodSeconds := int(*cmdOtpFlagPeriod / time.Second)
|
||||||
|
query.Set("period", fmt.Sprintf("%d", periodSeconds))
|
||||||
|
}
|
||||||
|
url := &url.URL{
|
||||||
|
Scheme: "otpauth",
|
||||||
|
Host: *cmdOtpFlagType,
|
||||||
|
Path: *cmdOtpFlagLabel,
|
||||||
|
RawQuery: query.Encode(),
|
||||||
|
}
|
||||||
|
log.Println("Generated URL:", url.String())
|
||||||
|
generateCliQR(url.String())
|
||||||
|
|
||||||
|
default: // other
|
||||||
|
err = ErrCommandNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCliQR(input string) {
|
||||||
|
grid, err := qrencode.Encode(input, ecLevelMapping[*flagECLevel])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := internal.ConvertGridToANSImage(grid)
|
||||||
|
|
||||||
|
fmt.Println(s)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
Loading…
Reference in New Issue