go-spectre/spectre.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
}
}