| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package configutil |
| |
| import ( |
| "bytes" |
| "context" |
| "encoding/base64" |
| "errors" |
| "fmt" |
| "regexp" |
| |
| wrapping "github.com/hashicorp/go-kms-wrapping/v2" |
| "google.golang.org/protobuf/proto" |
| ) |
| |
| var ( |
| encryptRegex = regexp.MustCompile(`{{encrypt\(.*\)}}`) |
| decryptRegex = regexp.MustCompile(`{{decrypt\(.*\)}}`) |
| ) |
| |
| func EncryptDecrypt(rawStr string, decrypt, strip bool, wrapper wrapping.Wrapper) (string, error) { |
| var locs [][]int |
| raw := []byte(rawStr) |
| searchVal := "{{encrypt(" |
| replaceVal := "{{decrypt(" |
| suffixVal := ")}}" |
| if decrypt { |
| searchVal = "{{decrypt(" |
| replaceVal = "{{encrypt(" |
| locs = decryptRegex.FindAllIndex(raw, -1) |
| } else { |
| locs = encryptRegex.FindAllIndex(raw, -1) |
| } |
| if strip { |
| replaceVal = "" |
| suffixVal = "" |
| } |
| |
| out := make([]byte, 0, len(rawStr)*2) |
| var prevMaxLoc int |
| for _, match := range locs { |
| if len(match) != 2 { |
| return "", fmt.Errorf("expected two values for match, got %d", len(match)) |
| } |
| |
| // Append everything from the end of the last match to the beginning of this one |
| out = append(out, raw[prevMaxLoc:match[0]]...) |
| |
| // Transform. First pull off the suffix/prefix |
| matchBytes := raw[match[0]:match[1]] |
| matchBytes = bytes.TrimSuffix(bytes.TrimPrefix(matchBytes, []byte(searchVal)), []byte(")}}")) |
| var finalVal string |
| |
| // Now encrypt or decrypt |
| switch decrypt { |
| case false: |
| outBlob, err := wrapper.Encrypt(context.Background(), matchBytes, nil) |
| if err != nil { |
| return "", fmt.Errorf("error encrypting parameter: %w", err) |
| } |
| if outBlob == nil { |
| return "", errors.New("nil value returned from encrypting parameter") |
| } |
| outMsg, err := proto.Marshal(outBlob) |
| if err != nil { |
| return "", fmt.Errorf("error marshaling encrypted parameter: %w", err) |
| } |
| finalVal = base64.RawURLEncoding.EncodeToString(outMsg) |
| |
| default: |
| inMsg, err := base64.RawURLEncoding.DecodeString(string(matchBytes)) |
| if err != nil { |
| return "", fmt.Errorf("error decoding encrypted parameter: %w", err) |
| } |
| inBlob := new(wrapping.BlobInfo) |
| if err := proto.Unmarshal(inMsg, inBlob); err != nil { |
| return "", fmt.Errorf("error unmarshaling encrypted parameter: %w", err) |
| } |
| dec, err := wrapper.Decrypt(context.Background(), inBlob, nil) |
| if err != nil { |
| return "", fmt.Errorf("error decrypting encrypted parameter: %w", err) |
| } |
| finalVal = string(dec) |
| } |
| |
| // Append new value |
| out = append(out, []byte(fmt.Sprintf("%s%s%s", replaceVal, finalVal, suffixVal))...) |
| prevMaxLoc = match[1] |
| } |
| // At the end, append the rest |
| out = append(out, raw[prevMaxLoc:]...) |
| return string(out), nil |
| } |