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