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 } }