mirror of
https://github.com/therootcompany/golib.git
synced 2026-03-02 23:57:59 +00:00
ref(auth/csvauth): separate Login, Token, and ServiceAccount files
This commit is contained in:
parent
85c7b78ca6
commit
7d35551fa7
@ -4,19 +4,15 @@ 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"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"iter"
|
|
||||||
"maps"
|
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -302,93 +298,6 @@ func gcmEncrypt(aes128key [16]byte, gcmNonce [12]byte, secret string) ([]byte, e
|
|||||||
return ciphertext, nil
|
return ciphertext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CredentialKeys returns the names that serve as IDs for each of the login credentials
|
|
||||||
func (a *Auth) CredentialKeys() iter.Seq[Name] {
|
|
||||||
a.mux.Lock()
|
|
||||||
defer a.mux.Unlock()
|
|
||||||
return maps.Keys(a.credentials)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Auth) LoadToken(secret string) (Credential, error) {
|
|
||||||
hashID := a.tokenCacheID(secret)
|
|
||||||
|
|
||||||
a.mux.Lock()
|
|
||||||
c, ok := a.tokens[hashID]
|
|
||||||
a.mux.Unlock()
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return Credential{}, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.plain == "" {
|
|
||||||
var err error
|
|
||||||
if c.plain, err = a.maybeDecryptCredential(c); err != nil {
|
|
||||||
return Credential{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Verify("", secret); err != nil {
|
|
||||||
return Credential{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Auth) LoadCredential(name Name) (Credential, error) {
|
|
||||||
a.mux.Lock()
|
|
||||||
c, ok := a.credentials[name]
|
|
||||||
a.mux.Unlock()
|
|
||||||
if !ok {
|
|
||||||
return c, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if c.plain, err = a.maybeDecryptCredential(c); err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Auth) CacheCredential(c Credential) error {
|
|
||||||
a.mux.Lock()
|
|
||||||
defer 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// CredentialKeys returns the names that serve as IDs for each of the login credentials
|
|
||||||
func (a *Auth) ServiceAccountKeys() iter.Seq[Purpose] {
|
|
||||||
a.mux.Lock()
|
|
||||||
defer a.mux.Unlock()
|
|
||||||
return maps.Keys(a.serviceAccounts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Auth) LoadServiceAccount(purpose Purpose) (Credential, error) {
|
|
||||||
a.mux.Lock()
|
|
||||||
c, ok := a.serviceAccounts[purpose]
|
|
||||||
a.mux.Unlock()
|
|
||||||
if !ok {
|
|
||||||
return c, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if c.plain, err = a.maybeDecryptCredential(c); err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Auth) maybeDecryptCredential(c Credential) (secretValue, error) {
|
func (a *Auth) maybeDecryptCredential(c Credential) (secretValue, error) {
|
||||||
switch c.Params[0] {
|
switch c.Params[0] {
|
||||||
case "aes-128-gcm":
|
case "aes-128-gcm":
|
||||||
@ -422,13 +331,6 @@ func (a *Auth) gcmDecrypt(aes128key [16]byte, gcmNonce [12]byte, derived []byte)
|
|||||||
return string(plaintext), nil
|
return string(plaintext), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) CacheServiceAccount(c Credential) error {
|
|
||||||
a.mux.Lock()
|
|
||||||
defer a.mux.Unlock()
|
|
||||||
a.serviceAccounts[c.Purpose] = c
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify checks Basic Auth credentials, i.e. as decoded from Authorization Basic <base64(user:pass)>.
|
// Verify checks Basic Auth credentials, i.e. as decoded from Authorization Basic <base64(user:pass)>.
|
||||||
// It also supports tokens. In short:
|
// It also supports tokens. In short:
|
||||||
// - if <user>:<pass> and 'user' is found, then "login" credentials
|
// - if <user>:<pass> and 'user' is found, then "login" credentials
|
||||||
@ -459,26 +361,6 @@ func (a *Auth) Verify(name, secret string) error {
|
|||||||
return ErrNotFound
|
return ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyToken uses a short, but timing-safe hash to find the token,
|
|
||||||
// and then verifies it with HMAC
|
|
||||||
func (a *Auth) VerifyToken(secret string) error {
|
|
||||||
_, err := a.LoadToken(secret)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Auth) tokenCacheID(secret string) string {
|
|
||||||
key := a.aes128key[:]
|
|
||||||
mac := hmac.New(sha256.New, key)
|
|
||||||
message := []byte(secret)
|
|
||||||
mac.Write(message)
|
|
||||||
// attack collisions are possible, but will still fail to pass HMAC
|
|
||||||
// practical collisions are not possible for the CSV use case
|
|
||||||
nameBytes := mac.Sum(nil)[:6]
|
|
||||||
|
|
||||||
name := base64.RawURLEncoding.EncodeToString(nameBytes)
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify checks Basic Auth credentials
|
// Verify checks Basic Auth credentials
|
||||||
// (name is ignored, as it is assumed to have been used for lookup)
|
// (name is ignored, as it is assumed to have been used for lookup)
|
||||||
func (c Credential) Verify(_, secret string) error {
|
func (c Credential) Verify(_, secret string) error {
|
||||||
|
|||||||
45
auth/csvauth/login.go
Normal file
45
auth/csvauth/login.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package csvauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"iter"
|
||||||
|
"maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CredentialKeys returns the names that serve as IDs for each of the login credentials
|
||||||
|
func (a *Auth) CredentialKeys() iter.Seq[Name] {
|
||||||
|
a.mux.Lock()
|
||||||
|
defer a.mux.Unlock()
|
||||||
|
return maps.Keys(a.credentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) LoadCredential(name Name) (Credential, error) {
|
||||||
|
a.mux.Lock()
|
||||||
|
c, ok := a.credentials[name]
|
||||||
|
a.mux.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return c, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if c.plain, err = a.maybeDecryptCredential(c); err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) CacheCredential(c Credential) error {
|
||||||
|
a.mux.Lock()
|
||||||
|
defer 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
|
||||||
|
}
|
||||||
36
auth/csvauth/service_account.go
Normal file
36
auth/csvauth/service_account.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package csvauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"iter"
|
||||||
|
"maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CredentialKeys returns the names that serve as IDs for each of the login credentials
|
||||||
|
func (a *Auth) ServiceAccountKeys() iter.Seq[Purpose] {
|
||||||
|
a.mux.Lock()
|
||||||
|
defer a.mux.Unlock()
|
||||||
|
return maps.Keys(a.serviceAccounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) LoadServiceAccount(purpose Purpose) (Credential, error) {
|
||||||
|
a.mux.Lock()
|
||||||
|
c, ok := a.serviceAccounts[purpose]
|
||||||
|
a.mux.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return c, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if c.plain, err = a.maybeDecryptCredential(c); err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) CacheServiceAccount(c Credential) error {
|
||||||
|
a.mux.Lock()
|
||||||
|
defer a.mux.Unlock()
|
||||||
|
a.serviceAccounts[c.Purpose] = c
|
||||||
|
return nil
|
||||||
|
}
|
||||||
52
auth/csvauth/token.go
Normal file
52
auth/csvauth/token.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package csvauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Auth) LoadToken(secret string) (Credential, error) {
|
||||||
|
hashID := a.tokenCacheID(secret)
|
||||||
|
|
||||||
|
a.mux.Lock()
|
||||||
|
c, ok := a.tokens[hashID]
|
||||||
|
a.mux.Unlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return Credential{}, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.plain == "" {
|
||||||
|
var err error
|
||||||
|
if c.plain, err = a.maybeDecryptCredential(c); err != nil {
|
||||||
|
return Credential{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Verify("", secret); err != nil {
|
||||||
|
return Credential{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyToken uses a short, but timing-safe hash to find the token,
|
||||||
|
// and then verifies it with HMAC
|
||||||
|
func (a *Auth) VerifyToken(secret string) error {
|
||||||
|
_, err := a.LoadToken(secret)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) tokenCacheID(secret string) string {
|
||||||
|
key := a.aes128key[:]
|
||||||
|
mac := hmac.New(sha256.New, key)
|
||||||
|
message := []byte(secret)
|
||||||
|
mac.Write(message)
|
||||||
|
// attack collisions are possible, but will still fail to pass HMAC
|
||||||
|
// practical collisions are not possible for the CSV use case
|
||||||
|
nameBytes := mac.Sum(nil)[:6]
|
||||||
|
|
||||||
|
name := base64.RawURLEncoding.EncodeToString(nameBytes)
|
||||||
|
return name
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user