199 lines
4.9 KiB
Go
199 lines
4.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package salt
|
|
|
|
import (
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"hash"
|
|
|
|
"github.com/hashicorp/errwrap"
|
|
uuid "github.com/hashicorp/go-uuid"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
const (
|
|
// DefaultLocation is the path in the view we store our key salt
|
|
// if no other path is provided.
|
|
DefaultLocation = "salt"
|
|
)
|
|
|
|
// Salt is used to manage a persistent salt key which is used to
|
|
// hash values. This allows keys to be generated and recovered
|
|
// using the global salt. Primarily, this allows paths in the storage
|
|
// backend to be obfuscated if they may contain sensitive information.
|
|
type Salt struct {
|
|
config *Config
|
|
salt string
|
|
generated bool
|
|
}
|
|
|
|
type HashFunc func([]byte) []byte
|
|
|
|
// Config is used to parameterize the Salt
|
|
type Config struct {
|
|
// Location is the path in the storage backend for the
|
|
// salt. Uses DefaultLocation if not specified.
|
|
Location string
|
|
|
|
// HashFunc is the hashing function to use for salting.
|
|
// Defaults to SHA1 if not provided.
|
|
HashFunc HashFunc
|
|
|
|
// HMAC allows specification of a hash function to use for
|
|
// the HMAC helpers
|
|
HMAC func() hash.Hash
|
|
|
|
// String prepended to HMAC strings for identification.
|
|
// Required if using HMAC
|
|
HMACType string
|
|
}
|
|
|
|
// NewSalt creates a new salt based on the configuration
|
|
func NewSalt(ctx context.Context, view logical.Storage, config *Config) (*Salt, error) {
|
|
// Setup the configuration
|
|
if config == nil {
|
|
config = &Config{}
|
|
}
|
|
if config.Location == "" {
|
|
config.Location = DefaultLocation
|
|
}
|
|
if config.HashFunc == nil {
|
|
config.HashFunc = SHA256Hash
|
|
}
|
|
if config.HMAC == nil {
|
|
config.HMAC = sha256.New
|
|
config.HMACType = "hmac-sha256"
|
|
}
|
|
|
|
// Create the salt
|
|
s := &Salt{
|
|
config: config,
|
|
}
|
|
|
|
// Look for the salt
|
|
var raw *logical.StorageEntry
|
|
var err error
|
|
if view != nil {
|
|
raw, err = view.Get(ctx, config.Location)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf("failed to read salt: {{err}}", err)
|
|
}
|
|
}
|
|
|
|
// Restore the salt if it exists
|
|
if raw != nil {
|
|
s.salt = string(raw.Value)
|
|
}
|
|
|
|
// Generate a new salt if necessary
|
|
if s.salt == "" {
|
|
s.salt, err = uuid.GenerateUUID()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf("failed to generate uuid: {{err}}", err)
|
|
}
|
|
s.generated = true
|
|
if view != nil {
|
|
raw := &logical.StorageEntry{
|
|
Key: config.Location,
|
|
Value: []byte(s.salt),
|
|
}
|
|
if err := view.Put(ctx, raw); err != nil {
|
|
return nil, errwrap.Wrapf("failed to persist salt: {{err}}", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if config.HMAC != nil {
|
|
if len(config.HMACType) == 0 {
|
|
return nil, fmt.Errorf("HMACType must be defined")
|
|
}
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// NewNonpersistentSalt creates a new salt with default configuration and no storage usage.
|
|
func NewNonpersistentSalt() *Salt {
|
|
// Setup the configuration
|
|
config := &Config{}
|
|
config.Location = ""
|
|
config.HashFunc = SHA256Hash
|
|
config.HMAC = sha256.New
|
|
config.HMACType = "hmac-sha256"
|
|
|
|
s := &Salt{
|
|
config: config,
|
|
}
|
|
s.salt, _ = uuid.GenerateUUID()
|
|
s.generated = true
|
|
return s
|
|
}
|
|
|
|
// SaltID is used to apply a salt and hash function to an ID to make sure
|
|
// it is not reversible
|
|
func (s *Salt) SaltID(id string) string {
|
|
return SaltID(s.salt, id, s.config.HashFunc)
|
|
}
|
|
|
|
// GetHMAC is used to apply a salt and hash function to data to make sure it is
|
|
// not reversible, with an additional HMAC
|
|
func (s *Salt) GetHMAC(data string) string {
|
|
hm := hmac.New(s.config.HMAC, []byte(s.salt))
|
|
hm.Write([]byte(data))
|
|
return hex.EncodeToString(hm.Sum(nil))
|
|
}
|
|
|
|
// GetIdentifiedHMAC is used to apply a salt and hash function to data to make
|
|
// sure it is not reversible, with an additional HMAC, and ID prepended
|
|
func (s *Salt) GetIdentifiedHMAC(data string) string {
|
|
return s.config.HMACType + ":" + s.GetHMAC(data)
|
|
}
|
|
|
|
// DidGenerate returns true if the underlying salt value was generated
|
|
// on initialization.
|
|
func (s *Salt) DidGenerate() bool {
|
|
return s.generated
|
|
}
|
|
|
|
// SaltIDHashFunc uses the supplied hash function instead of the configured
|
|
// hash func in the salt.
|
|
func (s *Salt) SaltIDHashFunc(id string, hashFunc HashFunc) string {
|
|
return SaltID(s.salt, id, hashFunc)
|
|
}
|
|
|
|
// SaltID is used to apply a salt and hash function to an ID to make sure
|
|
// it is not reversible
|
|
func SaltID(salt, id string, hash HashFunc) string {
|
|
comb := salt + id
|
|
hashVal := hash([]byte(comb))
|
|
return hex.EncodeToString(hashVal)
|
|
}
|
|
|
|
func HMACValue(salt, val string, hashFunc func() hash.Hash) string {
|
|
hm := hmac.New(hashFunc, []byte(salt))
|
|
hm.Write([]byte(val))
|
|
return hex.EncodeToString(hm.Sum(nil))
|
|
}
|
|
|
|
func HMACIdentifiedValue(salt, val, hmacType string, hashFunc func() hash.Hash) string {
|
|
return hmacType + ":" + HMACValue(salt, val, hashFunc)
|
|
}
|
|
|
|
// SHA1Hash returns the SHA1 of the input
|
|
func SHA1Hash(inp []byte) []byte {
|
|
hashed := sha1.Sum(inp)
|
|
return hashed[:]
|
|
}
|
|
|
|
// SHA256Hash returns the SHA256 of the input
|
|
func SHA256Hash(inp []byte) []byte {
|
|
hashed := sha256.Sum256(inp)
|
|
return hashed[:]
|
|
}
|