github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/lang/funcs/crypto.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package funcs 5 6 import ( 7 "crypto/md5" 8 "crypto/rsa" 9 "crypto/sha1" 10 "crypto/sha256" 11 "crypto/sha512" 12 "encoding/asn1" 13 "encoding/base64" 14 "encoding/hex" 15 "fmt" 16 "hash" 17 "io" 18 "strings" 19 20 uuidv5 "github.com/google/uuid" 21 uuid "github.com/hashicorp/go-uuid" 22 "github.com/zclconf/go-cty/cty" 23 "github.com/zclconf/go-cty/cty/function" 24 "github.com/zclconf/go-cty/cty/gocty" 25 "golang.org/x/crypto/bcrypt" 26 "golang.org/x/crypto/ssh" 27 ) 28 29 var UUIDFunc = function.New(&function.Spec{ 30 Params: []function.Parameter{}, 31 Type: function.StaticReturnType(cty.String), 32 RefineResult: refineNotNull, 33 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 34 result, err := uuid.GenerateUUID() 35 if err != nil { 36 return cty.UnknownVal(cty.String), err 37 } 38 return cty.StringVal(result), nil 39 }, 40 }) 41 42 var UUIDV5Func = function.New(&function.Spec{ 43 Params: []function.Parameter{ 44 { 45 Name: "namespace", 46 Type: cty.String, 47 }, 48 { 49 Name: "name", 50 Type: cty.String, 51 }, 52 }, 53 Type: function.StaticReturnType(cty.String), 54 RefineResult: refineNotNull, 55 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 56 var namespace uuidv5.UUID 57 switch { 58 case args[0].AsString() == "dns": 59 namespace = uuidv5.NameSpaceDNS 60 case args[0].AsString() == "url": 61 namespace = uuidv5.NameSpaceURL 62 case args[0].AsString() == "oid": 63 namespace = uuidv5.NameSpaceOID 64 case args[0].AsString() == "x500": 65 namespace = uuidv5.NameSpaceX500 66 default: 67 if namespace, err = uuidv5.Parse(args[0].AsString()); err != nil { 68 return cty.UnknownVal(cty.String), fmt.Errorf("uuidv5() doesn't support namespace %s (%v)", args[0].AsString(), err) 69 } 70 } 71 val := args[1].AsString() 72 return cty.StringVal(uuidv5.NewSHA1(namespace, []byte(val)).String()), nil 73 }, 74 }) 75 76 // Base64Sha256Func constructs a function that computes the SHA256 hash of a given string 77 // and encodes it with Base64. 78 var Base64Sha256Func = makeStringHashFunction(sha256.New, base64.StdEncoding.EncodeToString) 79 80 // MakeFileBase64Sha256Func constructs a function that is like Base64Sha256Func but reads the 81 // contents of a file rather than hashing a given literal string. 82 func MakeFileBase64Sha256Func(baseDir string) function.Function { 83 return makeFileHashFunction(baseDir, sha256.New, base64.StdEncoding.EncodeToString) 84 } 85 86 // Base64Sha512Func constructs a function that computes the SHA256 hash of a given string 87 // and encodes it with Base64. 88 var Base64Sha512Func = makeStringHashFunction(sha512.New, base64.StdEncoding.EncodeToString) 89 90 // MakeFileBase64Sha512Func constructs a function that is like Base64Sha512Func but reads the 91 // contents of a file rather than hashing a given literal string. 92 func MakeFileBase64Sha512Func(baseDir string) function.Function { 93 return makeFileHashFunction(baseDir, sha512.New, base64.StdEncoding.EncodeToString) 94 } 95 96 // BcryptFunc constructs a function that computes a hash of the given string using the Blowfish cipher. 97 var BcryptFunc = function.New(&function.Spec{ 98 Params: []function.Parameter{ 99 { 100 Name: "str", 101 Type: cty.String, 102 }, 103 }, 104 VarParam: &function.Parameter{ 105 Name: "cost", 106 Type: cty.Number, 107 }, 108 Type: function.StaticReturnType(cty.String), 109 RefineResult: refineNotNull, 110 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 111 defaultCost := 10 112 113 if len(args) > 1 { 114 var val int 115 if err := gocty.FromCtyValue(args[1], &val); err != nil { 116 return cty.UnknownVal(cty.String), err 117 } 118 defaultCost = val 119 } 120 121 if len(args) > 2 { 122 return cty.UnknownVal(cty.String), fmt.Errorf("bcrypt() takes no more than two arguments") 123 } 124 125 input := args[0].AsString() 126 out, err := bcrypt.GenerateFromPassword([]byte(input), defaultCost) 127 if err != nil { 128 return cty.UnknownVal(cty.String), fmt.Errorf("error occured generating password %s", err.Error()) 129 } 130 131 return cty.StringVal(string(out)), nil 132 }, 133 }) 134 135 // Md5Func constructs a function that computes the MD5 hash of a given string and encodes it with hexadecimal digits. 136 var Md5Func = makeStringHashFunction(md5.New, hex.EncodeToString) 137 138 // MakeFileMd5Func constructs a function that is like Md5Func but reads the 139 // contents of a file rather than hashing a given literal string. 140 func MakeFileMd5Func(baseDir string) function.Function { 141 return makeFileHashFunction(baseDir, md5.New, hex.EncodeToString) 142 } 143 144 // RsaDecryptFunc constructs a function that decrypts an RSA-encrypted ciphertext. 145 var RsaDecryptFunc = function.New(&function.Spec{ 146 Params: []function.Parameter{ 147 { 148 Name: "ciphertext", 149 Type: cty.String, 150 }, 151 { 152 Name: "privatekey", 153 Type: cty.String, 154 }, 155 }, 156 Type: function.StaticReturnType(cty.String), 157 RefineResult: refineNotNull, 158 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 159 s := args[0].AsString() 160 key := args[1].AsString() 161 162 b, err := base64.StdEncoding.DecodeString(s) 163 if err != nil { 164 return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "failed to decode input %q: cipher text must be base64-encoded", s) 165 } 166 167 rawKey, err := ssh.ParseRawPrivateKey([]byte(key)) 168 if err != nil { 169 var errStr string 170 switch e := err.(type) { 171 case asn1.SyntaxError: 172 errStr = strings.ReplaceAll(e.Error(), "asn1: syntax error", "invalid ASN1 data in the given private key") 173 case asn1.StructuralError: 174 errStr = strings.ReplaceAll(e.Error(), "asn1: struture error", "invalid ASN1 data in the given private key") 175 default: 176 errStr = fmt.Sprintf("invalid private key: %s", e) 177 } 178 return cty.UnknownVal(cty.String), function.NewArgErrorf(1, errStr) 179 } 180 privateKey, ok := rawKey.(*rsa.PrivateKey) 181 if !ok { 182 return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "invalid private key type %t", rawKey) 183 } 184 185 out, err := rsa.DecryptPKCS1v15(nil, privateKey, b) 186 if err != nil { 187 return cty.UnknownVal(cty.String), fmt.Errorf("failed to decrypt: %s", err) 188 } 189 190 return cty.StringVal(string(out)), nil 191 }, 192 }) 193 194 // Sha1Func contructs a function that computes the SHA1 hash of a given string 195 // and encodes it with hexadecimal digits. 196 var Sha1Func = makeStringHashFunction(sha1.New, hex.EncodeToString) 197 198 // MakeFileSha1Func constructs a function that is like Sha1Func but reads the 199 // contents of a file rather than hashing a given literal string. 200 func MakeFileSha1Func(baseDir string) function.Function { 201 return makeFileHashFunction(baseDir, sha1.New, hex.EncodeToString) 202 } 203 204 // Sha256Func contructs a function that computes the SHA256 hash of a given string 205 // and encodes it with hexadecimal digits. 206 var Sha256Func = makeStringHashFunction(sha256.New, hex.EncodeToString) 207 208 // MakeFileSha256Func constructs a function that is like Sha256Func but reads the 209 // contents of a file rather than hashing a given literal string. 210 func MakeFileSha256Func(baseDir string) function.Function { 211 return makeFileHashFunction(baseDir, sha256.New, hex.EncodeToString) 212 } 213 214 // Sha512Func contructs a function that computes the SHA512 hash of a given string 215 // and encodes it with hexadecimal digits. 216 var Sha512Func = makeStringHashFunction(sha512.New, hex.EncodeToString) 217 218 // MakeFileSha512Func constructs a function that is like Sha512Func but reads the 219 // contents of a file rather than hashing a given literal string. 220 func MakeFileSha512Func(baseDir string) function.Function { 221 return makeFileHashFunction(baseDir, sha512.New, hex.EncodeToString) 222 } 223 224 func makeStringHashFunction(hf func() hash.Hash, enc func([]byte) string) function.Function { 225 return function.New(&function.Spec{ 226 Params: []function.Parameter{ 227 { 228 Name: "str", 229 Type: cty.String, 230 }, 231 }, 232 Type: function.StaticReturnType(cty.String), 233 RefineResult: refineNotNull, 234 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 235 s := args[0].AsString() 236 h := hf() 237 h.Write([]byte(s)) 238 rv := enc(h.Sum(nil)) 239 return cty.StringVal(rv), nil 240 }, 241 }) 242 } 243 244 func makeFileHashFunction(baseDir string, hf func() hash.Hash, enc func([]byte) string) function.Function { 245 return function.New(&function.Spec{ 246 Params: []function.Parameter{ 247 { 248 Name: "path", 249 Type: cty.String, 250 }, 251 }, 252 Type: function.StaticReturnType(cty.String), 253 RefineResult: refineNotNull, 254 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 255 path := args[0].AsString() 256 f, err := openFile(baseDir, path) 257 if err != nil { 258 return cty.UnknownVal(cty.String), err 259 } 260 defer f.Close() 261 262 h := hf() 263 _, err = io.Copy(h, f) 264 if err != nil { 265 return cty.UnknownVal(cty.String), err 266 } 267 rv := enc(h.Sum(nil)) 268 return cty.StringVal(rv), nil 269 }, 270 }) 271 } 272 273 // UUID generates and returns a Type-4 UUID in the standard hexadecimal string 274 // format. 275 // 276 // This is not a pure function: it will generate a different result for each 277 // call. It must therefore be registered as an impure function in the function 278 // table in the "lang" package. 279 func UUID() (cty.Value, error) { 280 return UUIDFunc.Call(nil) 281 } 282 283 // UUIDV5 generates and returns a Type-5 UUID in the standard hexadecimal string 284 // format. 285 func UUIDV5(namespace cty.Value, name cty.Value) (cty.Value, error) { 286 return UUIDV5Func.Call([]cty.Value{namespace, name}) 287 } 288 289 // Base64Sha256 computes the SHA256 hash of a given string and encodes it with 290 // Base64. 291 // 292 // The given string is first encoded as UTF-8 and then the SHA256 algorithm is applied 293 // as defined in RFC 4634. The raw hash is then encoded with Base64 before returning. 294 // Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4. 295 func Base64Sha256(str cty.Value) (cty.Value, error) { 296 return Base64Sha256Func.Call([]cty.Value{str}) 297 } 298 299 // Base64Sha512 computes the SHA512 hash of a given string and encodes it with 300 // Base64. 301 // 302 // The given string is first encoded as UTF-8 and then the SHA256 algorithm is applied 303 // as defined in RFC 4634. The raw hash is then encoded with Base64 before returning. 304 // Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4 305 func Base64Sha512(str cty.Value) (cty.Value, error) { 306 return Base64Sha512Func.Call([]cty.Value{str}) 307 } 308 309 // Bcrypt computes a hash of the given string using the Blowfish cipher, 310 // returning a string in the Modular Crypt Format 311 // usually expected in the shadow password file on many Unix systems. 312 func Bcrypt(str cty.Value, cost ...cty.Value) (cty.Value, error) { 313 args := make([]cty.Value, len(cost)+1) 314 args[0] = str 315 copy(args[1:], cost) 316 return BcryptFunc.Call(args) 317 } 318 319 // Md5 computes the MD5 hash of a given string and encodes it with hexadecimal digits. 320 func Md5(str cty.Value) (cty.Value, error) { 321 return Md5Func.Call([]cty.Value{str}) 322 } 323 324 // RsaDecrypt decrypts an RSA-encrypted ciphertext, returning the corresponding 325 // cleartext. 326 func RsaDecrypt(ciphertext, privatekey cty.Value) (cty.Value, error) { 327 return RsaDecryptFunc.Call([]cty.Value{ciphertext, privatekey}) 328 } 329 330 // Sha1 computes the SHA1 hash of a given string and encodes it with hexadecimal digits. 331 func Sha1(str cty.Value) (cty.Value, error) { 332 return Sha1Func.Call([]cty.Value{str}) 333 } 334 335 // Sha256 computes the SHA256 hash of a given string and encodes it with hexadecimal digits. 336 func Sha256(str cty.Value) (cty.Value, error) { 337 return Sha256Func.Call([]cty.Value{str}) 338 } 339 340 // Sha512 computes the SHA512 hash of a given string and encodes it with hexadecimal digits. 341 func Sha512(str cty.Value) (cty.Value, error) { 342 return Sha512Func.Call([]cty.Value{str}) 343 }