143 lines
2.8 KiB
Go
143 lines
2.8 KiB
Go
package spectre
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"golang.org/x/crypto/scrypt"
|
|
"strings"
|
|
)
|
|
|
|
// Spectre is a spectre client
|
|
type Spectre struct {
|
|
name string
|
|
secret string
|
|
scoper Scoper
|
|
|
|
key []byte
|
|
}
|
|
|
|
// New returns a Spectre client
|
|
func New(name, secret string, opts ...Option) (s *Spectre, err error) {
|
|
s = &Spectre{
|
|
name: name,
|
|
secret: secret,
|
|
scoper: DefaultScoper,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(s)
|
|
}
|
|
s.key, err = s.userKey()
|
|
return
|
|
}
|
|
|
|
// Option is a Spectre option
|
|
type Option func(*Spectre)
|
|
|
|
// WithScoper assigns a scoper to Spectre
|
|
func WithScoper(scoper Scoper) Option {
|
|
return func(s *Spectre) {
|
|
s.scoper = scoper
|
|
}
|
|
}
|
|
|
|
func (s *Spectre) userKey() ([]byte, error) {
|
|
nameBytes := []byte(s.name)
|
|
secretBytes := []byte(s.secret)
|
|
keyScope := []byte(s.scoper.Scope(Authentication))
|
|
|
|
nameBytesLen := len(nameBytes)
|
|
keySalt := append(keyScope,
|
|
byte(nameBytesLen>>24),
|
|
byte(nameBytesLen>>16),
|
|
byte(nameBytesLen>>8),
|
|
byte(nameBytesLen),
|
|
)
|
|
keySalt = append(keySalt, nameBytes...)
|
|
|
|
return scrypt.Key(secretBytes, keySalt, 32768, 8, 2, 64)
|
|
}
|
|
|
|
func (s *Spectre) siteKey(name string, counter int, scope Scope) []byte {
|
|
nameBytes := []byte(name)
|
|
scopeBytes := []byte(s.scoper.Scope(scope))
|
|
|
|
nameBytesLen := len(nameBytes)
|
|
keySalt := append(scopeBytes,
|
|
byte(nameBytesLen>>24),
|
|
byte(nameBytesLen>>16),
|
|
byte(nameBytesLen>>8),
|
|
byte(nameBytesLen),
|
|
)
|
|
keySalt = append(keySalt, nameBytes...)
|
|
keySalt = append(keySalt,
|
|
byte(counter>>24),
|
|
byte(counter>>16),
|
|
byte(counter>>8),
|
|
byte(counter),
|
|
)
|
|
|
|
sign := hmac.New(sha256.New, s.key)
|
|
sign.Write(keySalt)
|
|
|
|
return sign.Sum(nil)
|
|
}
|
|
|
|
// Site returns a site password based on Options
|
|
func (s *Spectre) Site(siteName string, opts ...SiteOption) string {
|
|
siteOpts := &options{
|
|
template: "",
|
|
counter: 1,
|
|
scope: Authentication,
|
|
}
|
|
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]
|
|
template := templateSet[int(siteKey[0])%len(templateSet)]
|
|
|
|
var out strings.Builder
|
|
for idx, b := range template {
|
|
chars := characters[string(b)]
|
|
char := chars[int(siteKey[idx+1])%len(chars)]
|
|
out.WriteByte(char)
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
type options struct {
|
|
template Template
|
|
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
|
|
}
|
|
}
|