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 }