github.com/hashicorp/vault/sdk@v0.11.0/helper/salt/salt.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package salt
     5  
     6  import (
     7  	"context"
     8  	"crypto/hmac"
     9  	"crypto/sha1"
    10  	"crypto/sha256"
    11  	"encoding/hex"
    12  	"fmt"
    13  	"hash"
    14  
    15  	"github.com/hashicorp/errwrap"
    16  	uuid "github.com/hashicorp/go-uuid"
    17  	"github.com/hashicorp/vault/sdk/logical"
    18  )
    19  
    20  const (
    21  	// DefaultLocation is the path in the view we store our key salt
    22  	// if no other path is provided.
    23  	DefaultLocation = "salt"
    24  )
    25  
    26  // Salt is used to manage a persistent salt key which is used to
    27  // hash values. This allows keys to be generated and recovered
    28  // using the global salt. Primarily, this allows paths in the storage
    29  // backend to be obfuscated if they may contain sensitive information.
    30  type Salt struct {
    31  	config    *Config
    32  	salt      string
    33  	generated bool
    34  }
    35  
    36  type HashFunc func([]byte) []byte
    37  
    38  // Config is used to parameterize the Salt
    39  type Config struct {
    40  	// Location is the path in the storage backend for the
    41  	// salt. Uses DefaultLocation if not specified.
    42  	Location string
    43  
    44  	// HashFunc is the hashing function to use for salting.
    45  	// Defaults to SHA1 if not provided.
    46  	HashFunc HashFunc
    47  
    48  	// HMAC allows specification of a hash function to use for
    49  	// the HMAC helpers
    50  	HMAC func() hash.Hash
    51  
    52  	// String prepended to HMAC strings for identification.
    53  	// Required if using HMAC
    54  	HMACType string
    55  }
    56  
    57  // NewSalt creates a new salt based on the configuration
    58  func NewSalt(ctx context.Context, view logical.Storage, config *Config) (*Salt, error) {
    59  	// Setup the configuration
    60  	if config == nil {
    61  		config = &Config{}
    62  	}
    63  	if config.Location == "" {
    64  		config.Location = DefaultLocation
    65  	}
    66  	if config.HashFunc == nil {
    67  		config.HashFunc = SHA256Hash
    68  	}
    69  	if config.HMAC == nil {
    70  		config.HMAC = sha256.New
    71  		config.HMACType = "hmac-sha256"
    72  	}
    73  
    74  	// Create the salt
    75  	s := &Salt{
    76  		config: config,
    77  	}
    78  
    79  	// Look for the salt
    80  	var raw *logical.StorageEntry
    81  	var err error
    82  	if view != nil {
    83  		raw, err = view.Get(ctx, config.Location)
    84  		if err != nil {
    85  			return nil, errwrap.Wrapf("failed to read salt: {{err}}", err)
    86  		}
    87  	}
    88  
    89  	// Restore the salt if it exists
    90  	if raw != nil {
    91  		s.salt = string(raw.Value)
    92  	}
    93  
    94  	// Generate a new salt if necessary
    95  	if s.salt == "" {
    96  		s.salt, err = uuid.GenerateUUID()
    97  		if err != nil {
    98  			return nil, errwrap.Wrapf("failed to generate uuid: {{err}}", err)
    99  		}
   100  		s.generated = true
   101  		if view != nil {
   102  			raw := &logical.StorageEntry{
   103  				Key:   config.Location,
   104  				Value: []byte(s.salt),
   105  			}
   106  			if err := view.Put(ctx, raw); err != nil {
   107  				return nil, errwrap.Wrapf("failed to persist salt: {{err}}", err)
   108  			}
   109  		}
   110  	}
   111  
   112  	if config.HMAC != nil {
   113  		if len(config.HMACType) == 0 {
   114  			return nil, fmt.Errorf("HMACType must be defined")
   115  		}
   116  	}
   117  
   118  	return s, nil
   119  }
   120  
   121  // NewNonpersistentSalt creates a new salt with default configuration and no storage usage.
   122  func NewNonpersistentSalt() *Salt {
   123  	// Setup the configuration
   124  	config := &Config{}
   125  	config.Location = ""
   126  	config.HashFunc = SHA256Hash
   127  	config.HMAC = sha256.New
   128  	config.HMACType = "hmac-sha256"
   129  
   130  	s := &Salt{
   131  		config: config,
   132  	}
   133  	s.salt, _ = uuid.GenerateUUID()
   134  	s.generated = true
   135  	return s
   136  }
   137  
   138  // SaltID is used to apply a salt and hash function to an ID to make sure
   139  // it is not reversible
   140  func (s *Salt) SaltID(id string) string {
   141  	return SaltID(s.salt, id, s.config.HashFunc)
   142  }
   143  
   144  // GetHMAC is used to apply a salt and hash function to data to make sure it is
   145  // not reversible, with an additional HMAC
   146  func (s *Salt) GetHMAC(data string) string {
   147  	hm := hmac.New(s.config.HMAC, []byte(s.salt))
   148  	hm.Write([]byte(data))
   149  	return hex.EncodeToString(hm.Sum(nil))
   150  }
   151  
   152  // GetIdentifiedHMAC is used to apply a salt and hash function to data to make
   153  // sure it is not reversible, with an additional HMAC, and ID prepended
   154  func (s *Salt) GetIdentifiedHMAC(data string) string {
   155  	return s.config.HMACType + ":" + s.GetHMAC(data)
   156  }
   157  
   158  // DidGenerate returns true if the underlying salt value was generated
   159  // on initialization.
   160  func (s *Salt) DidGenerate() bool {
   161  	return s.generated
   162  }
   163  
   164  // SaltIDHashFunc uses the supplied hash function instead of the configured
   165  // hash func in the salt.
   166  func (s *Salt) SaltIDHashFunc(id string, hashFunc HashFunc) string {
   167  	return SaltID(s.salt, id, hashFunc)
   168  }
   169  
   170  // SaltID is used to apply a salt and hash function to an ID to make sure
   171  // it is not reversible
   172  func SaltID(salt, id string, hash HashFunc) string {
   173  	comb := salt + id
   174  	hashVal := hash([]byte(comb))
   175  	return hex.EncodeToString(hashVal)
   176  }
   177  
   178  func HMACValue(salt, val string, hashFunc func() hash.Hash) string {
   179  	hm := hmac.New(hashFunc, []byte(salt))
   180  	hm.Write([]byte(val))
   181  	return hex.EncodeToString(hm.Sum(nil))
   182  }
   183  
   184  func HMACIdentifiedValue(salt, val, hmacType string, hashFunc func() hash.Hash) string {
   185  	return hmacType + ":" + HMACValue(salt, val, hashFunc)
   186  }
   187  
   188  // SHA1Hash returns the SHA1 of the input
   189  func SHA1Hash(inp []byte) []byte {
   190  	hashed := sha1.Sum(inp)
   191  	return hashed[:]
   192  }
   193  
   194  // SHA256Hash returns the SHA256 of the input
   195  func SHA256Hash(inp []byte) []byte {
   196  	hashed := sha256.Sum256(inp)
   197  	return hashed[:]
   198  }