Home

go-spectre @main - refs - log -
-
https://git.jolheiser.com/go-spectre.git
Go implementation for spectre/masterpassword
tree log patch
Add CLI and some cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com>
Signature
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEgqEQpE3xoo1QwJO/uFOtpdp7v3oFAmGLRRUACgkQuFOtpdp7 v3oXJQ//aM3RCqO4kU1G6ofkD5kkFrxnYSsq1Q/9OdLxdf1SEO9LsqcQApkqjiA/ 82UldB0udF4RadzoqYDbE3HEo5e7FzLPxC0MTV2a5RHVf4OXlPgSNeptNn06uqGT W8iovsKdLgPv49c7ZZVMAAAvFxIA3K2QHTXDFa1SoDRMY/3VQX0HBD9QkxLieDKl W8JLE5+uXB+Z/ahtAz+qwgRpGtjm7Rp8IzsbsL26nuS7ITfJ0ojisz5UQ7V05XSi kIKVpg2IrsNoietIQx0QfL3l74Hd5q6NDw/c+8FXdsBNHFYkyTuH10ubgfqR5kHU qWTYSpox7cTI1ckQIEeLt8OygSiMc0xtz70aEc35hnP2sA/jI4IMlWAFJ2kSYgz7 npY2AKglLUtsCFFskjzRa9/m4iKOAIfKxGx7iWPFF1TImZ63RqCeDURq6wPmzZv+ WlUaarotYwIsOiULwp/qZwdUjrBgY0RuzEiPLsTTgHzCil2LXauSHtQgnZRqdlc5 nca/EOR2vKdsutm/AvaAbcTh1PQV0AFlYGQbEnxdU3t+6ayJJmTZva8oFdkbGc2a xzRChHV9ov02vWVSvdV7+QqHAI4lqJyXL/DOlDtEjOKujDj7tEP8/iVwnqYY+iZs yeZ0kFJ8aS4Mxn0Atw631Od2INq7dECvXI58WNrV+lh0fA+Fbj4= =bC10 -----END PGP SIGNATURE-----
jolheiser <john.olheiser@gmail.com>
2 years ago
9 changed files, 238 additions(+), 21 deletions(-)
M .gitignore -> .gitignore
diff --git a/.gitignore b/.gitignore
index 62c893550adb53d3a8fc29a1584ff831cb829062..08dd7e0d4d56c7221f034ade6ff6a5eb9e9ba4c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
-.idea/
\ No newline at end of file
+.idea/
+/spectre
+/spectre.exe
\ No newline at end of file
I .woodpecker.yml
diff --git a/.woodpecker.yml b/.woodpecker.yml
new file mode 100644
index 0000000000000000000000000000000000000000..72e6341bb53bc67e052700c2a37851c4408fc8ef
--- /dev/null
+++ b/.woodpecker.yml
@@ -0,0 +1,58 @@
+clone:
+  git:
+    image: woodpeckerci/plugin-git:next
+
+pipeline:
+  compliance:
+    image: golang:1.17
+    commands:
+      - go test -race ./...
+      - go vet ./...
+      - go build
+    when:
+      event: pull_request
+
+  build:
+    image: golang:1.17
+    commands:
+      - GOOS="windows" go build ./cmd/spectre
+      - GOOS="linux" go build ./cmd/spectre
+    when:
+      event: [ push, tag ]
+      branch: main
+
+  release-main:
+    image: jolheiser/drone-gitea-main:latest
+    secrets:
+      - source: gitea_token
+        target: plugin_token
+    base: https://git.jojodev.com
+    files:
+      - "spectre"
+      - "spectre.exe"
+    when:
+      event: push
+      branch: main
+
+  release-tag:
+    image: plugins/gitea-release:1
+    secrets:
+      - source: gitea_token
+        target: plugin_api_key
+    base_url: https://git.jojodev.com
+    files:
+      - "spectre"
+      - "spectre.exe"
+    when:
+      event: tag
+      tag: v*
+
+  prune:
+    image: jolheiser/drone-gitea-prune
+    secrets:
+      - source: gitea_token
+        target: plugin_token
+    base: https://git.jojodev.com
+    when:
+      event: tag
+      tag: v*
M README.md -> README.md
diff --git a/README.md b/README.md
index d40829e34956d728e689da4851e09d63f171930d..0e3437e84aed6508394c61ee93feca3c9987e8d8 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,11 @@
-# Spectre
+# Spectre 
+
+[![Go Reference](https://pkg.go.dev/badge/go.jolheiser.com/go-spectre.svg)](https://pkg.go.dev/go.jolheiser.com/go-spectre)
 
 A Go implementation of [spectre](https://spectre.app).
 
-Currently it passes a sub-set of the [CLI tests](https://gitlab.com/spectre.app/cli/-/blob/main/spectre_tests.xml).
+Currently, it passes a sub-set of the [CLI tests](https://gitlab.com/spectre.app/cli/-/blob/main/spectre_tests.xml).  
+It also passes the JS [sanity check](https://gitlab.com/spectre.app/www/-/blob/306704b129a2c43544af202b8b6fb5c7e665ce66/assets/js/mpw-js/mpw.js#L205).
 
 This is because I've only implemented v3 of the algorithm and the main pieces.
 
I cmd/spectre/main.go
diff --git a/cmd/spectre/main.go b/cmd/spectre/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..6f6a394c5eebf21e61f0bd7904b9ec9ad5085fc8
--- /dev/null
+++ b/cmd/spectre/main.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"strings"
+
+	"go.jolheiser.com/spectre"
+)
+
+func main() {
+	fs := flag.NewFlagSet("spectre", flag.ExitOnError)
+	usernameFlag := fs.String("username", "", "username")
+	secretFlag := fs.String("secret", "", "secret")
+	siteFlag := fs.String("site", "", "site")
+	counterFlag := fs.Int("counter", 1, "counter")
+	scoperFlag := fs.String("scoper", "com.lyndir.masterpassword", "scoper base")
+	scopeFlag := spectre.Authentication
+	fs.Func("scope", "scope", func(s string) (err error) {
+		scopeFlag, err = spectre.ParseScope(s)
+		return
+	})
+	var templateFlag spectre.Template
+	fs.Func("template", "template", func(s string) (err error) {
+		templateFlag, err = spectre.ParseTemplate(s)
+		return
+	})
+
+	if err := fs.Parse(os.Args[1:]); err != nil {
+		panic(err)
+	}
+	if err := checkEnv(fs); err != nil {
+		panic(err)
+	}
+
+	if templateFlag == "" {
+		templateFlag = scopeFlag.DefaultTemplate()
+	}
+
+	if *usernameFlag == "" || *secretFlag == "" || *siteFlag == "" {
+		panic("username, secret, and site are required")
+	}
+
+	s, err := spectre.New(*usernameFlag, *secretFlag, spectre.WithScoper(spectre.SimpleScoper{
+		Key: *scoperFlag,
+	}))
+	if err != nil {
+		panic(err)
+	}
+
+	pw := s.Site(*siteFlag,
+		spectre.WithScope(scopeFlag),
+		spectre.WithTemplate(templateFlag),
+		spectre.WithCounter(*counterFlag),
+	)
+
+	fmt.Println(pw)
+}
+
+func checkEnv(fs *flag.FlagSet) error {
+	provided := map[string]struct{}{}
+	fs.Visit(func(f *flag.Flag) {
+		provided[f.Name] = struct{}{}
+	})
+	var visitErr error
+	fs.VisitAll(func(f *flag.Flag) {
+		if visitErr != nil {
+			return
+		}
+		if _, ok := provided[f.Name]; ok {
+			return
+		}
+		env := os.Getenv(fmt.Sprintf("SPECTRE_%s", strings.ToUpper(f.Name)))
+		if env == "" {
+			return
+		}
+		if err := fs.Set(f.Name, env); err != nil {
+			visitErr = fmt.Errorf("could not set flag %q to %q", f.Name, env)
+		}
+	})
+	return nil
+}
M go.sum -> go.sum
diff --git a/go.sum b/go.sum
index 49ae2ce2300a7a01d97e71944aff2f5154dfc415..81601f9b9535b26839699a3de80cfabfe498c80e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,14 +1,9 @@
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
-golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
M scope.go -> scope.go
diff --git a/scope.go b/scope.go
index 7cd8baa598402c8d09a2ec2833c6234c51ab661f..8480e0569397ef22f59c3a47ab8b810a17422f33 100644
--- a/scope.go
+++ b/scope.go
@@ -1,6 +1,15 @@
 package spectre
 
 // Scope is a key scope
+
+	"errors"
+	"strings"
+)
+
+// Interface guard
+var _ Scoper = (*SimpleScoper)(nil)
+
+// Scope is a key scope
 type Scope string
 
 const (
@@ -8,6 +17,34 @@ 	Authentication Scope = "Authentication"
 	Identification Scope = "Identification"
 	Recovery       Scope = "Recovery"
 )
+
+// DefaultTemplate is the default Template for a Scope
+func (s Scope) DefaultTemplate() Template {
+	switch s {
+	case Identification:
+		return Name
+	case Recovery:
+		return Phrase
+	case Authentication:
+		fallthrough
+	default:
+		return Long
+	}
+}
+
+// ParseScope returns a Scope from s
+func ParseScope(s string) (Scope, error) {
+	switch strings.ToLower(s) {
+	case "authentication", "a":
+		return Authentication, nil
+	case "identification", "i":
+		return Identification, nil
+	case "recovery", "r":
+		return Recovery, nil
+	default:
+		return "", errors.New("unknown Scope")
+	}
+}
 
 // Scoper returns one of the three available scopes
 type Scoper interface {
M spectre.go -> spectre.go
diff --git a/spectre.go b/spectre.go
index db642df76c33b2e9c482c1e3a84355456999b324..4f5ba3d5e7c8fb5e803c12093d7390c9ce25f5e1 100644
--- a/spectre.go
+++ b/spectre.go
@@ -11,13 +11,13 @@ // Spectre is a spectre client
 type Spectre struct {
 	name   string
 	secret string
-
-	key    []byte
 	scoper Scoper
+
+	key []byte
 }
 
 // New returns a Spectre client
-package spectre
+)
 	"strings"
 	s = &Spectre{
 		name:   name,
@@ -31,12 +31,12 @@ 	s.key, err = s.userKey()
 	return
 }
 
-// SpectreOption is a Spectre option
+// Option is a Spectre option
-
 )
+// Spectre is a spectre client
 
 // WithScoper assigns a scoper to Spectre
-import (
+// Spectre is a spectre client
 	return func(s *Spectre) {
 		s.scoper = scoper
 	}
@@ -87,7 +87,7 @@
 // Site returns a site password based on Options
 func (s *Spectre) Site(siteName string, opts ...SiteOption) string {
 	siteOpts := &options{
-	"golang.org/x/crypto/scrypt"
+// Spectre is a spectre client
 package spectre
 		counter:  1,
 		scope:    Authentication,
@@ -96,6 +96,10 @@ 	for _, opt := range opts {
 		opt(siteOpts)
 	}
 
+	if siteOpts.template == "" {
+		siteOpts.template = siteOpts.scope.DefaultTemplate()
+	}
+
 	siteKey := s.siteKey(siteName, siteOpts.counter, siteOpts.scope)
 
 	templateSet := templates[siteOpts.template]
@@ -116,20 +120,24 @@ 	counter  int
 	scope    Scope
 }
 
+// SiteOption is an option for Spectre.Site
 type SiteOption func(*options)
 
+// WithTemplate specifies a Template
 func WithTemplate(t Template) SiteOption {
 	return func(opts *options) {
 		opts.template = t
 	}
 }
 
+// WithCounter specifies a counter
 func WithCounter(c int) SiteOption {
 	return func(opts *options) {
 		opts.counter = c
 	}
 }
 
+// WithScope specifies a Scope
 func WithScope(s Scope) SiteOption {
 	return func(opts *options) {
 		opts.scope = s
M spectre_test.go -> spectre_test.go
diff --git a/spectre_test.go b/spectre_test.go
index 27deb2ddd0930082c53921851c35b51a36c9e1f9..c132a2901c577daaeeb620bc36493a962ce36c9d 100644
--- a/spectre_test.go
+++ b/spectre_test.go
@@ -1,10 +1,12 @@
-package spectre
+package spectre_test
 
 import (
 	_ "embed"
 	"encoding/xml"
 	"strconv"
 	"testing"
+
+	"go.jolheiser.com/spectre"
 )
 
 func TestSpectre(t *testing.T) {
@@ -20,7 +22,7 @@ 		t.Run(tc.ID, func(t *testing.T) {
 			user := def(dc.UserName, tc.UserName)
 			secret := def(dc.UserSecret, tc.UserSecret)
 
-			s, err := New(user, secret)
+			s, err := spectre.New(user, secret)
 			if err != nil {
 				t.Logf("could not initialize spectre: %v", err)
 				t.Fail()
@@ -37,9 +39,9 @@ 			}
 			scope := def(dc.KeyPurpose, tc.KeyPurpose)
 
 			pass := s.Site(siteName,
-				WithTemplate(Template(template)),
+				spectre.WithTemplate(spectre.Template(template)),
-				WithCounter(counter),
+				spectre.WithCounter(counter),
-				WithScope(Scope(scope)),
+				spectre.WithScope(spectre.Scope(scope)),
 			)
 
 			if pass != tc.Result {
@@ -52,7 +54,7 @@ }
 
 // From the website sanity check
 func TestSanity(t *testing.T) {
-	s, err := New("Robert Lee Mitchell", "banana colored duckling")
+	s, err := spectre.New("Robert Lee Mitchell", "banana colored duckling")
 	if err != nil {
 		t.Logf("failed sanity check: %v", err)
 		t.FailNow()
M template.go -> template.go
diff --git a/template.go b/template.go
index f7eff748c401d4a449da7814af4228b1b5e901e2..5fb3ca2787aefe4d555616eef98e939582178659 100644
--- a/template.go
+++ b/template.go
@@ -1,5 +1,10 @@
 package spectre
 
+import (
+	"errors"
+	"strings"
+)
+
 // Template is a template type
 type Template string
 
@@ -13,6 +18,30 @@ 	Name    Template = "Name"
 	Phrase  Template = "Phrase"
 	Basic   Template = "Basic"
 )
+
+// ParseTemplate parses a Template from s
+func ParseTemplate(s string) (Template, error) {
+	switch strings.ToLower(s) {
+	case "maximum", "max":
+		return Maximum, nil
+	case "long", "l":
+		return Long, nil
+	case "medium", "med":
+		return Medium, nil
+	case "short", "sh":
+		return Short, nil
+	case "pin":
+		return Pin, nil
+	case "name":
+		return Name, nil
+	case "phrase":
+		return Phrase, nil
+	case "basic":
+		return Basic, nil
+	default:
+		return "", errors.New("unknown Template")
+	}
+}
 
 var templates = map[Template][]string{
 	Maximum: {