| // 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[:] |
| } |