Home

go-spectre @7661556e93beda3525190fc2767bb91fed3a284d - refs - log -
-
https://git.jolheiser.com/go-spectre.git
Go implementation for spectre/masterpassword
go-spectre / spectre.go
- raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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

	key    []byte
	scoper Scoper
}

// New returns a Spectre client
func New(name, secret string, scoper Scoper) (s *Spectre, err error) {
	s = &Spectre{
		name:   name,
		secret: secret,
		scoper: scoper,
	}
	s.key, err = s.userKey()
	return
}

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: Long,
		counter:  1,
		scope:    Authentication,
	}
	for _, opt := range opts {
		opt(siteOpts)
	}

	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
}

type SiteOption func(*options)

func WithTemplate(t Template) SiteOption {
	return func(opts *options) {
		opts.template = t
	}
}

func WithCounter(c int) SiteOption {
	return func(opts *options) {
		opts.counter = c
	}
}

func WithScope(s Scope) SiteOption {
	return func(opts *options) {
		opts.scope = s
	}
}