mirror of
https://github.com/therootcompany/golib.git
synced 2026-03-02 23:57:59 +00:00
feat(auth/csvauth): add token support,make secrets non-printing
This commit is contained in:
parent
dd48b2420b
commit
85d42550bf
@ -33,6 +33,7 @@ func showHelp() {
|
|||||||
fmt.Fprintf(os.Stderr, `csvauth - create, update, and verify users, passwords, and tokens
|
fmt.Fprintf(os.Stderr, `csvauth - create, update, and verify users, passwords, and tokens
|
||||||
|
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
|
csvauth store --token 'my-new-token'
|
||||||
csvauth store --ask-password 'my-new-user'
|
csvauth store --ask-password 'my-new-user'
|
||||||
csvauth verify 'my-new-user'
|
csvauth verify 'my-new-user'
|
||||||
|
|
||||||
@ -255,12 +256,13 @@ func handleInit(keyenv, keypath, csvpath string) error {
|
|||||||
|
|
||||||
func handleSet(args []string, aesKey []byte, csvFile csvauth.NamedReadCloser) {
|
func handleSet(args []string, aesKey []byte, csvFile csvauth.NamedReadCloser) {
|
||||||
storeFlags := flag.NewFlagSet("csvauth-store", flag.ContinueOnError)
|
storeFlags := flag.NewFlagSet("csvauth-store", flag.ContinueOnError)
|
||||||
purpose := storeFlags.String("purpose", "login", "'login' for users, or a service account name, such as 'basecamp_api_key'")
|
purpose := storeFlags.String("purpose", "login", "'login' for users, 'token' for tokens, or a service account name, such as 'basecamp_api_key'")
|
||||||
roleList := storeFlags.String("roles", "", "a comma- or space-separated list of roles (defined by you), such as 'triage audit'")
|
roleList := storeFlags.String("roles", "", "a comma- or space-separated list of roles (defined by you), such as 'triage audit'")
|
||||||
extra := storeFlags.String("extra", "", "free form data to retrieve with the user (hint: JSON might be nice)")
|
extra := storeFlags.String("extra", "", "free form data to retrieve with the user (hint: JSON might be nice)")
|
||||||
algorithm := storeFlags.String("algorithm", "", "Hash algorithm: aes, plain, pbkdf2[,iters[,size[,hash]]], or bcrypt[,cost]")
|
algorithm := storeFlags.String("algorithm", "", "Hash algorithm: aes, plain, pbkdf2[,iters[,size[,hash]]], or bcrypt[,cost]")
|
||||||
askPassword := storeFlags.Bool("ask-password", false, "Read password from stdin")
|
askPassword := storeFlags.Bool("ask-password", false, "Read password or token from stdin")
|
||||||
passwordFile := storeFlags.String("password-file", "", "Read password from file")
|
useToken := storeFlags.Bool("token", false, "generate token")
|
||||||
|
passwordFile := storeFlags.String("password-file", "", "Read password or token from file")
|
||||||
// storeFlags.StringVar(&tsvPath, "tsv", tsvPath, "Credentials file to use")
|
// storeFlags.StringVar(&tsvPath, "tsv", tsvPath, "Credentials file to use")
|
||||||
if err := storeFlags.Parse(args); err != nil {
|
if err := storeFlags.Parse(args); err != nil {
|
||||||
if err == flag.ErrHelp {
|
if err == flag.ErrHelp {
|
||||||
@ -276,20 +278,38 @@ func handleSet(args []string, aesKey []byte, csvFile csvauth.NamedReadCloser) {
|
|||||||
|
|
||||||
name := storeFlags.Arg(0)
|
name := storeFlags.Arg(0)
|
||||||
switch name {
|
switch name {
|
||||||
case "id", "name", "purpose":
|
case "", "id", "name", "purpose":
|
||||||
|
if *useToken {
|
||||||
|
fmt.Fprintf(os.Stderr, "invalid token name %q\n", name)
|
||||||
|
} else {
|
||||||
fmt.Fprintf(os.Stderr, "invalid username %q\n", name)
|
fmt.Fprintf(os.Stderr, "invalid username %q\n", name)
|
||||||
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *useToken {
|
||||||
|
if *purpose != csvauth.PurposeDefault && *purpose != csvauth.PurposeToken {
|
||||||
|
fmt.Fprintf(os.Stderr, "token purpose must be 'token', not %q\n", *purpose)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
*purpose = csvauth.PurposeToken
|
||||||
|
}
|
||||||
|
|
||||||
if len(*algorithm) == 0 {
|
if len(*algorithm) == 0 {
|
||||||
if *purpose == "login" {
|
switch *purpose {
|
||||||
|
case csvauth.PurposeDefault:
|
||||||
*algorithm = "pbkdf2"
|
*algorithm = "pbkdf2"
|
||||||
} else {
|
case csvauth.PurposeToken:
|
||||||
|
fallthrough
|
||||||
// *algorithm = "plain"
|
// *algorithm = "plain"
|
||||||
|
default:
|
||||||
*algorithm = "aes-128-gcm"
|
*algorithm = "aes-128-gcm"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if *purpose != "login" {
|
switch *purpose {
|
||||||
|
case csvauth.PurposeDefault, csvauth.PurposeToken:
|
||||||
|
// no change
|
||||||
|
default:
|
||||||
*askPassword = true
|
*askPassword = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,7 +359,7 @@ func handleSet(args []string, aesKey []byte, csvFile csvauth.NamedReadCloser) {
|
|||||||
_ = csvFile.Close()
|
_ = csvFile.Close()
|
||||||
|
|
||||||
var exists bool
|
var exists bool
|
||||||
if len(*purpose) > 0 && *purpose != "login" {
|
if len(*purpose) > 0 && *purpose != csvauth.PurposeDefault && *purpose != csvauth.PurposeToken {
|
||||||
if _, err := auth.LoadServiceAccount(*purpose); err != nil {
|
if _, err := auth.LoadServiceAccount(*purpose); err != nil {
|
||||||
if !errors.Is(err, csvauth.ErrNotFound) {
|
if !errors.Is(err, csvauth.ErrNotFound) {
|
||||||
fmt.Fprintf(os.Stderr, "could not load %s: %v\n", *purpose, err)
|
fmt.Fprintf(os.Stderr, "could not load %s: %v\n", *purpose, err)
|
||||||
@ -382,9 +402,10 @@ func handleSet(args []string, aesKey []byte, csvFile csvauth.NamedReadCloser) {
|
|||||||
|
|
||||||
func handleCheck(args []string, aesKey []byte, csvFile csvauth.NamedReadCloser) {
|
func handleCheck(args []string, aesKey []byte, csvFile csvauth.NamedReadCloser) {
|
||||||
checkFlags := flag.NewFlagSet("csvauth-check", flag.ContinueOnError)
|
checkFlags := flag.NewFlagSet("csvauth-check", flag.ContinueOnError)
|
||||||
purpose := checkFlags.String("purpose", "login", "'login' for users, or a service account name, such as 'basecamp_api_key'")
|
purpose := checkFlags.String("purpose", "login", "'login' for users, 'token' for tokens, or a service account name, such as 'basecamp_api_key'")
|
||||||
_ = checkFlags.Bool("ask-password", true, "Read password from stdin")
|
_ = checkFlags.Bool("ask-password", true, "Read password or token from stdin")
|
||||||
passwordFile := checkFlags.String("password-file", "", "Read password from file")
|
useToken := checkFlags.Bool("token", false, "generate token")
|
||||||
|
passwordFile := checkFlags.String("password-file", "", "Read password or token from file")
|
||||||
// storeFlags.StringVar(&tsvPath, "tsv", tsvPath, "Credentials file to use")
|
// storeFlags.StringVar(&tsvPath, "tsv", tsvPath, "Credentials file to use")
|
||||||
if err := checkFlags.Parse(args); err != nil {
|
if err := checkFlags.Parse(args); err != nil {
|
||||||
if err == flag.ErrHelp {
|
if err == flag.ErrHelp {
|
||||||
@ -400,10 +421,24 @@ func handleCheck(args []string, aesKey []byte, csvFile csvauth.NamedReadCloser)
|
|||||||
|
|
||||||
name := checkFlags.Arg(0)
|
name := checkFlags.Arg(0)
|
||||||
switch name {
|
switch name {
|
||||||
case "id", "name", "purpose":
|
case "", "id", "name", "purpose":
|
||||||
|
if !*useToken {
|
||||||
fmt.Fprintf(os.Stderr, "invalid username %q\n", name)
|
fmt.Fprintf(os.Stderr, "invalid username %q\n", name)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
if name != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "invalid token name %q\n", name)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *useToken {
|
||||||
|
if *purpose != csvauth.PurposeDefault && *purpose != csvauth.PurposeToken {
|
||||||
|
fmt.Fprintf(os.Stderr, "token purpose must be 'token', not %q\n", *purpose)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
*purpose = csvauth.PurposeToken
|
||||||
|
}
|
||||||
|
|
||||||
var pass string
|
var pass string
|
||||||
if len(*passwordFile) > 0 {
|
if len(*passwordFile) > 0 {
|
||||||
@ -434,7 +469,7 @@ func handleCheck(args []string, aesKey []byte, csvFile csvauth.NamedReadCloser)
|
|||||||
|
|
||||||
var v csvauth.BasicAuthVerifier
|
var v csvauth.BasicAuthVerifier
|
||||||
var err error
|
var err error
|
||||||
if *purpose != "login" {
|
if *purpose != csvauth.PurposeDefault && *purpose != csvauth.PurposeToken {
|
||||||
v, err = auth.LoadServiceAccount(*purpose)
|
v, err = auth.LoadServiceAccount(*purpose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "couldn't load %s: %v", *purpose, err)
|
fmt.Fprintf(os.Stderr, "couldn't load %s: %v", *purpose, err)
|
||||||
@ -444,7 +479,13 @@ func handleCheck(args []string, aesKey []byte, csvFile csvauth.NamedReadCloser)
|
|||||||
v = auth
|
v = auth
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := v.Verify(name, pass); err != nil {
|
if *purpose == csvauth.PurposeToken {
|
||||||
|
if err := auth.VerifyToken(pass); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "token not verified: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if err := v.Verify(name, pass); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "user '%s' not found or incorrect secret\n", name)
|
fmt.Fprintf(os.Stderr, "user '%s' not found or incorrect secret\n", name)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -14,7 +14,13 @@ type BasicAuthVerifier interface {
|
|||||||
Verify(string, string) error
|
Verify(string, string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultPurpose = "login"
|
const (
|
||||||
|
// deprecated, misspelling of PurposeDefault
|
||||||
|
DefaultPurpose = "login"
|
||||||
|
PurposeDefault = "login"
|
||||||
|
PurposeToken = "token"
|
||||||
|
hashIDSep = "~"
|
||||||
|
)
|
||||||
|
|
||||||
type Purpose = string
|
type Purpose = string
|
||||||
type Name = string
|
type Name = string
|
||||||
@ -43,6 +49,7 @@ type Credential struct {
|
|||||||
Derived []byte
|
Derived []byte
|
||||||
Roles []string
|
Roles []string
|
||||||
Extra string
|
Extra string
|
||||||
|
hashID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Credential) Secret() string {
|
func (c Credential) Secret() string {
|
||||||
@ -64,13 +71,20 @@ func FromRecord(record []string) (Credential, error) {
|
|||||||
|
|
||||||
func FromFields(purpose, name, paramList, saltBase64, derived, roleList, extra string) (Credential, error) {
|
func FromFields(purpose, name, paramList, saltBase64, derived, roleList, extra string) (Credential, error) {
|
||||||
var credential Credential
|
var credential Credential
|
||||||
credential.Name = name
|
|
||||||
|
|
||||||
if len(purpose) == 0 {
|
if len(purpose) == 0 {
|
||||||
purpose = DefaultPurpose
|
purpose = PurposeDefault
|
||||||
}
|
}
|
||||||
credential.Purpose = purpose
|
credential.Purpose = purpose
|
||||||
|
|
||||||
|
if credential.Purpose == PurposeToken {
|
||||||
|
name, hashID, _ := splitLast(name, hashIDSep)
|
||||||
|
credential.Name = name
|
||||||
|
// this can only be verified if plain or aes
|
||||||
|
credential.hashID = hashID
|
||||||
|
} else {
|
||||||
|
credential.Name = name
|
||||||
|
}
|
||||||
|
|
||||||
var roles []string
|
var roles []string
|
||||||
if len(roleList) > 0 {
|
if len(roleList) > 0 {
|
||||||
roleList = strings.ReplaceAll(roleList, ",", " ")
|
roleList = strings.ReplaceAll(roleList, ",", " ")
|
||||||
@ -156,6 +170,19 @@ func FromFields(purpose, name, paramList, saltBase64, derived, roleList, extra s
|
|||||||
return credential, nil
|
return credential, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitLast(s, sep string) (before, after string, found bool) {
|
||||||
|
if sep == "" {
|
||||||
|
return s, "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := strings.LastIndex(s, sep)
|
||||||
|
if idx == -1 {
|
||||||
|
return s, "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s[:idx], s[idx+len(sep):], true
|
||||||
|
}
|
||||||
|
|
||||||
func (c Credential) ToRecord() []string {
|
func (c Credential) ToRecord() []string {
|
||||||
var paramList, salt, derived string
|
var paramList, salt, derived string
|
||||||
|
|
||||||
@ -178,9 +205,13 @@ func (c Credential) ToRecord() []string {
|
|||||||
|
|
||||||
purpose := c.Purpose
|
purpose := c.Purpose
|
||||||
if len(purpose) == 0 {
|
if len(purpose) == 0 {
|
||||||
purpose = DefaultPurpose
|
purpose = PurposeDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
record := []string{purpose, c.Name, paramList, salt, derived, strings.Join(c.Roles, " "), c.Extra}
|
name := c.Name
|
||||||
|
if c.hashID != "" {
|
||||||
|
name += hashIDSep + c.hashID
|
||||||
|
}
|
||||||
|
record := []string{purpose, name, paramList, salt, derived, strings.Join(c.Roles, " "), c.Extra}
|
||||||
return record
|
return record
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,8 @@ service1 acme aes-128-gcm 2z92DVgMF9Hn-GBy i37kF34cwa64j3tmnrvlJ5ZSekWD-w token
|
|||||||
service2 acme plain token2 token2
|
service2 acme plain token2 token2
|
||||||
service3 user3 pbkdf2 1000 16 SHA-256 DYdA9iz1EN81bESTXcSgUg IzkeBCxRVmqybOBeAntfdA token3
|
service3 user3 pbkdf2 1000 16 SHA-256 DYdA9iz1EN81bESTXcSgUg IzkeBCxRVmqybOBeAntfdA token3
|
||||||
service4 user4 bcrypt $2a$12$HueMNxFGYIYtNNTySFW/Lu4vAMqpdcchBnJrW.VdYgP9xPQdITipu token4
|
service4 user4 bcrypt $2a$12$HueMNxFGYIYtNNTySFW/Lu4vAMqpdcchBnJrW.VdYgP9xPQdITipu token4
|
||||||
|
token api~vkdAIZ2O aes-128-gcm kRHkOnbVdKfxwWNo wC7edBEuz5htfvaagBfRUOHc60g api1
|
||||||
|
token api~b5ZF2sRQ aes-128-gcm GowoB3l3eDiE4bPI rtXBX9QbdrochVlr1SG8UWGllao api2
|
||||||
login user1 pbkdf2 1000 16 SHA-256 R-NgfDcY1A6L5a4jO89TNw -Pe9o-NwYvF6M4tlCwhm_g pass1
|
login user1 pbkdf2 1000 16 SHA-256 R-NgfDcY1A6L5a4jO89TNw -Pe9o-NwYvF6M4tlCwhm_g pass1
|
||||||
login user2 bcrypt $2a$12$pad8UgUphO43PioF1JlSHOblRPdaX.ikTqjA8D1EfrcBiNGI9WQ/y pass2
|
login user2 bcrypt $2a$12$pad8UgUphO43PioF1JlSHOblRPdaX.ikTqjA8D1EfrcBiNGI9WQ/y pass2
|
||||||
login user3 aes-128-gcm YC0xno0-W9pWR6rK D9CZFCtGGJecLpCv2Fk1I-wcXmN3 pass3
|
login user3 aes-128-gcm YC0xno0-W9pWR6rK D9CZFCtGGJecLpCv2Fk1I-wcXmN3 pass3
|
||||||
|
|||||||
|
@ -4,10 +4,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"crypto/hmac"
|
||||||
"crypto/pbkdf2"
|
"crypto/pbkdf2"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -27,6 +29,7 @@ import (
|
|||||||
var ErrNotFound = errors.New("not found")
|
var ErrNotFound = errors.New("not found")
|
||||||
var ErrUnauthorized = errors.New("unauthorized")
|
var ErrUnauthorized = errors.New("unauthorized")
|
||||||
var ErrUnknownAlgorithm = errors.New("unknown algorithm")
|
var ErrUnknownAlgorithm = errors.New("unknown algorithm")
|
||||||
|
var ErrLockedCredential = errors.New("credential is locked")
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultIters = 1000 // original 2000 recommendation
|
defaultIters = 1000 // original 2000 recommendation
|
||||||
@ -64,6 +67,7 @@ func NewNamedReadCloser(r io.ReadCloser, name string) NamedReadCloser {
|
|||||||
type Auth struct {
|
type Auth struct {
|
||||||
aes128key [16]byte
|
aes128key [16]byte
|
||||||
credentials map[Name]Credential
|
credentials map[Name]Credential
|
||||||
|
tokens map[string]Credential
|
||||||
serviceAccounts map[Purpose]Credential
|
serviceAccounts map[Purpose]Credential
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
}
|
}
|
||||||
@ -76,6 +80,7 @@ func New(aes128key []byte) *Auth {
|
|||||||
return &Auth{
|
return &Auth{
|
||||||
aes128key: aes128Arr,
|
aes128key: aes128Arr,
|
||||||
credentials: map[Name]Credential{},
|
credentials: map[Name]Credential{},
|
||||||
|
tokens: map[string]Credential{},
|
||||||
serviceAccounts: map[Purpose]Credential{},
|
serviceAccounts: map[Purpose]Credential{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,12 +120,24 @@ func (a *Auth) LoadCSV(f NamedReadCloser, comma rune) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(credential.Purpose) == 0 || credential.Purpose == DefaultPurpose {
|
switch credential.Purpose {
|
||||||
if _, ok := a.credentials[credential.Name]; ok {
|
case "", PurposeDefault, PurposeToken:
|
||||||
|
name := credential.Name
|
||||||
|
if credential.Purpose == PurposeToken {
|
||||||
|
name += hashIDSep + credential.hashID
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := a.credentials[name]; ok {
|
||||||
fmt.Fprintf(os.Stderr, "overwriting cache of previous value for %s: %s\n", credential.Purpose, credential.Name)
|
fmt.Fprintf(os.Stderr, "overwriting cache of previous value for %s: %s\n", credential.Purpose, credential.Name)
|
||||||
}
|
}
|
||||||
a.credentials[credential.Name] = credential
|
a.credentials[name] = credential
|
||||||
} else {
|
if credential.Purpose == PurposeToken {
|
||||||
|
if _, ok := a.tokens[credential.hashID]; ok {
|
||||||
|
fmt.Fprintf(os.Stderr, "overwriting cache of previous value for %s: %s\n", credential.Purpose, credential.Name)
|
||||||
|
}
|
||||||
|
a.tokens[credential.hashID] = credential
|
||||||
|
}
|
||||||
|
default:
|
||||||
if _, ok := a.serviceAccounts[credential.Purpose]; ok {
|
if _, ok := a.serviceAccounts[credential.Purpose]; ok {
|
||||||
fmt.Fprintf(os.Stderr, "overwriting cache of previous value for %s: %s\n", credential.Purpose, credential.Name)
|
fmt.Fprintf(os.Stderr, "overwriting cache of previous value for %s: %s\n", credential.Purpose, credential.Name)
|
||||||
}
|
}
|
||||||
@ -144,6 +161,10 @@ func (a *Auth) NewCredential(purpose, name, secret string, params []string, role
|
|||||||
Extra: extra,
|
Extra: extra,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if purpose == PurposeToken {
|
||||||
|
c.hashID = a.tokenCacheID(secret)
|
||||||
|
}
|
||||||
|
|
||||||
switch c.Params[0] {
|
switch c.Params[0] {
|
||||||
case "plain":
|
case "plain":
|
||||||
if len(params) != 1 {
|
if len(params) != 1 {
|
||||||
@ -304,8 +325,17 @@ func (a *Auth) LoadCredential(name Name) (Credential, error) {
|
|||||||
|
|
||||||
func (a *Auth) CacheCredential(c Credential) error {
|
func (a *Auth) CacheCredential(c Credential) error {
|
||||||
a.mux.Lock()
|
a.mux.Lock()
|
||||||
a.credentials[c.Name] = c
|
defer a.mux.Unlock()
|
||||||
a.mux.Unlock()
|
|
||||||
|
name := c.Name
|
||||||
|
if c.Purpose == PurposeToken {
|
||||||
|
name += hashIDSep + c.hashID
|
||||||
|
}
|
||||||
|
a.credentials[name] = c
|
||||||
|
|
||||||
|
if c.Purpose == PurposeToken {
|
||||||
|
a.tokens[c.hashID] = c
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,12 +413,48 @@ func (a *Auth) Verify(name, secret string) error {
|
|||||||
return c.Verify(name, secret)
|
return c.Verify(name, secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Auth) VerifyToken(secret string) error {
|
||||||
|
hashID := a.tokenCacheID(secret)
|
||||||
|
|
||||||
|
a.mux.Lock()
|
||||||
|
c, ok := a.tokens[hashID]
|
||||||
|
a.mux.Unlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.plain == "" {
|
||||||
|
var err error
|
||||||
|
if c.plain, err = a.maybeDecryptCredential(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.Verify(hashID, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) tokenCacheID(secret string) string {
|
||||||
|
key := a.aes128key[:]
|
||||||
|
mac := hmac.New(sha256.New, key)
|
||||||
|
message := []byte(secret)
|
||||||
|
mac.Write(message)
|
||||||
|
nameBytes := mac.Sum(nil)[:6]
|
||||||
|
|
||||||
|
name := base64.RawURLEncoding.EncodeToString(nameBytes)
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
// Verify checks Basic Auth credentials
|
// Verify checks Basic Auth credentials
|
||||||
func (c Credential) Verify(name, secret string) error {
|
// (name is ignored, as it is assumed to have been used for lookup)
|
||||||
|
func (c Credential) Verify(_, secret string) error {
|
||||||
known := c.Derived
|
known := c.Derived
|
||||||
var derived []byte
|
var derived []byte
|
||||||
switch c.Params[0] {
|
switch c.Params[0] {
|
||||||
case "aes-128-gcm":
|
case "aes-128-gcm":
|
||||||
|
// we hash because encrypted comparisons are NOT timing safe
|
||||||
|
if c.plain == "" {
|
||||||
|
return ErrLockedCredential
|
||||||
|
}
|
||||||
knownHash := sha256.Sum256([]byte(c.plain))
|
knownHash := sha256.Sum256([]byte(c.plain))
|
||||||
known = knownHash[:]
|
known = knownHash[:]
|
||||||
|
|
||||||
@ -421,6 +487,7 @@ func (c Credential) Verify(name, secret string) error {
|
|||||||
return ErrUnknownAlgorithm
|
return ErrUnknownAlgorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// all values MUST be hashed before comparing, for timing safety
|
||||||
if bytes.Equal(known, derived) {
|
if bytes.Equal(known, derived) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user