github.com/wfusion/gofusion@v1.1.14/config/crypto.go (about)

     1  package config
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"hash/crc64"
     7  	"math/rand"
     8  	"reflect"
     9  
    10  	"github.com/pkg/errors"
    11  	"github.com/spf13/cast"
    12  	"github.com/spf13/pflag"
    13  
    14  	"github.com/wfusion/gofusion/common/utils"
    15  	"github.com/wfusion/gofusion/common/utils/cipher"
    16  	"github.com/wfusion/gofusion/common/utils/compress"
    17  	"github.com/wfusion/gofusion/common/utils/encode"
    18  )
    19  
    20  const (
    21  	cryptoTagKey = "encrypted"
    22  )
    23  
    24  var (
    25  	cryptoFlagString string
    26  )
    27  
    28  func init() {
    29  	pflag.StringVarP(&cryptoFlagString, "crypto-config", "", "", "json string for crypto config")
    30  }
    31  
    32  type CryptoConf struct {
    33  	Config *cryptoConf            `yaml:"config" json:"config" toml:"config"`
    34  	Custom map[string]*cryptoConf `yaml:"custom" json:"custom" toml:"custom"`
    35  }
    36  
    37  func (c *CryptoConf) ToOptionMap() (result map[string][]utils.OptionExtender) {
    38  	result = make(map[string][]utils.OptionExtender)
    39  	if c.Config != nil {
    40  		result[""] = c.Config.ToOptions()
    41  	}
    42  	for name, cfg := range c.Custom {
    43  		result[name] = cfg.ToOptions()
    44  	}
    45  	return
    46  }
    47  
    48  // cryptoConf
    49  //nolint: revive // struct tag too long issue
    50  type cryptoConf struct {
    51  	Key        []byte `yaml:"-" json:"-" toml:"-"`
    52  	KeyBase64  string `yaml:"key_base64" json:"key_base64" toml:"key_base64"`
    53  	ConfuseKey bool   `yaml:"confuse_key" json:"confuse_key" toml:"confuse_key"`
    54  
    55  	IV       []byte `yaml:"-" json:"-" toml:"-"`
    56  	IVBase64 string `yaml:"iv_base64" json:"iv_base64" toml:"iv_base64"`
    57  
    58  	Algorithm       cipher.Algorithm `yaml:"-" json:"-" toml:"-"`
    59  	AlgorithmString string           `yaml:"algorithm" json:"algorithm" toml:"algorithm"`
    60  
    61  	Mode       cipher.Mode `yaml:"-" json:"-" toml:"-"`
    62  	ModeString string      `yaml:"mode" json:"mode" toml:"mode"`
    63  
    64  	CompressAlgorithm       compress.Algorithm `yaml:"-" json:"-" toml:"-"`
    65  	CompressAlgorithmString *string            `yaml:"compress_algorithm" json:"compress_algorithm" toml:"compress_algorithm"`
    66  
    67  	OutputAlgorithm       encode.Algorithm `yaml:"-" json:"-" toml:"-"`
    68  	OutputAlgorithmString *string          `yaml:"output_algorithm" json:"output_algorithm" toml:"output_algorithm"`
    69  }
    70  
    71  func (c *cryptoConf) ToOptions() (opts []utils.OptionExtender) {
    72  	if !c.Algorithm.IsValid() {
    73  		return nil
    74  	}
    75  
    76  	if c.ConfuseKey {
    77  		c.Key = c.cryptoConfuseKey(c.Key)
    78  	}
    79  
    80  	opts = make([]utils.OptionExtender, 0, 3)
    81  	opts = append(opts, encode.Cipher(c.Algorithm, c.Mode, c.Key, c.IV))
    82  	if c.CompressAlgorithm.IsValid() {
    83  		opts = append(opts, encode.Compress(c.CompressAlgorithm))
    84  	}
    85  	if c.OutputAlgorithm.IsValid() {
    86  		opts = append(opts, encode.Encode(c.OutputAlgorithm))
    87  	}
    88  	return
    89  }
    90  
    91  func (c *cryptoConf) cryptoConfuseKey(key []byte) (confused []byte) {
    92  	var (
    93  		k1 = make([]byte, len(key))
    94  		k2 = make([]byte, len(key))
    95  		k3 = make([]byte, len(key))
    96  	)
    97  	rndSeed := int64(crc64.Checksum(key, crc64.MakeTable(crc64.ISO)))
    98  	utils.Must(rand.New(rand.NewSource(cipher.RndSeed ^ compress.RndSeed ^ rndSeed)).Read(k1))
    99  	utils.Must(rand.New(rand.NewSource(cipher.RndSeed ^ encode.RndSeed ^ rndSeed)).Read(k2))
   100  	utils.Must(rand.New(rand.NewSource(compress.RndSeed ^ encode.RndSeed ^ rndSeed)).Read(k3))
   101  
   102  	confused = make([]byte, len(key))
   103  	utils.Must(rand.New(rand.NewSource(cipher.RndSeed ^ compress.RndSeed ^ encode.RndSeed)).Read(confused))
   104  	for i := 0; i < len(confused); i++ {
   105  		confused[i] ^= k1[i] ^ k2[i] ^ k3[i]
   106  	}
   107  	return
   108  }
   109  
   110  func CryptoConstruct(ctx context.Context, c CryptoConf, _ ...utils.OptionExtender) func() {
   111  	if c.Config != nil {
   112  		checkCryptoConf("", c.Config)
   113  	}
   114  	for name, cfg := range c.Custom {
   115  		if cfg != nil {
   116  			checkCryptoConf(name, cfg)
   117  		}
   118  	}
   119  
   120  	return func() {
   121  
   122  	}
   123  }
   124  
   125  func checkCryptoConf(name string, c *cryptoConf) {
   126  	// cipher
   127  	if c.Algorithm = cipher.ParseAlgorithm(c.AlgorithmString); !c.Algorithm.IsValid() {
   128  		panic(errors.Errorf("unknown config %s algorithm: %s", name, c.AlgorithmString))
   129  	}
   130  	c.Mode = cipher.ParseMode(c.ModeString)
   131  	if !c.Mode.IsValid() {
   132  		panic(errors.Errorf("unknown config %s mode: %s", name, c.ModeString))
   133  	}
   134  	if utils.IsStrBlank(c.KeyBase64) {
   135  		panic(errors.Errorf("%s not found crypto key", name))
   136  	}
   137  	c.Key = utils.Must(base64.StdEncoding.DecodeString(c.KeyBase64))
   138  	if c.Mode.NeedIV() && utils.IsStrBlank(c.IVBase64) {
   139  		panic(errors.Errorf("%s not found crypto iv", name))
   140  	}
   141  	c.IV = utils.Must(base64.StdEncoding.DecodeString(c.IVBase64))
   142  
   143  	// compress
   144  	if utils.IsStrPtrNotBlank(c.CompressAlgorithmString) {
   145  		c.CompressAlgorithm = compress.ParseAlgorithm(*c.CompressAlgorithmString)
   146  		if !c.CompressAlgorithm.IsValid() {
   147  			panic(errors.Errorf("unknown config %s compress algorithm: %s", name, *c.CompressAlgorithmString))
   148  		}
   149  	}
   150  
   151  	// output
   152  	if utils.IsStrPtrNotBlank(c.OutputAlgorithmString) {
   153  		c.OutputAlgorithm = encode.ParseAlgorithm(*c.OutputAlgorithmString)
   154  		if !c.OutputAlgorithm.IsValid() {
   155  			panic(errors.Errorf("unknown config %s output algorithm: %s", name, *c.OutputAlgorithmString))
   156  		}
   157  	}
   158  }
   159  
   160  type cryptoConfigOption struct {
   161  	name string
   162  }
   163  
   164  func CryptoConfigName(name string) utils.OptionFunc[cryptoConfigOption] {
   165  	return func(o *cryptoConfigOption) {
   166  		o.name = name
   167  	}
   168  }
   169  
   170  func CryptoEncryptFunc(opts ...utils.OptionExtender) func(src string) (dst string) {
   171  	o := utils.ApplyOptions[InitOption](opts...)
   172  	opt := utils.ApplyOptions[cryptoConfigOption](opts...)
   173  	optsMap := Use(o.AppName).(*registry).cryptoConfig().ToOptionMap()
   174  	opts = optsMap[opt.name]
   175  	return func(src string) (dst string) {
   176  		return utils.Must(encode.From(src).Encode(opts...).ToString())
   177  	}
   178  }
   179  
   180  func CryptoDecryptFunc(opts ...utils.OptionExtender) func(src string) (dst string) {
   181  	o := utils.ApplyOptions[InitOption](opts...)
   182  	opt := utils.ApplyOptions[cryptoConfigOption](opts...)
   183  	optsMap := Use(o.AppName).(*registry).cryptoConfig().ToOptionMap()
   184  	for _, opts := range optsMap {
   185  		utils.SliceReverse(opts)
   186  	}
   187  	opts = optsMap[opt.name]
   188  	return func(src string) (dst string) {
   189  		return utils.Must(encode.From(src).Decode(opts...).ToString())
   190  	}
   191  }
   192  
   193  func CryptoDecryptByTag(data any, opts ...utils.OptionExtender) {
   194  	o := utils.ApplyOptions[InitOption](opts...)
   195  	optsMap := Use(o.AppName).(*registry).cryptoConfig().ToOptionMap()
   196  	for _, opts := range optsMap {
   197  		utils.SliceReverse(opts)
   198  	}
   199  
   200  	supportedFields := utils.NewSet(reflect.Struct, reflect.Array, reflect.Slice, reflect.Map)
   201  	utils.TraverseValue(data, false, func(field reflect.StructField, value reflect.Value) (end, stepIn bool) {
   202  		if !value.IsValid() || !value.CanInterface() || !value.CanSet() {
   203  			return
   204  		}
   205  
   206  		vk := value.Kind()
   207  		stepIn = supportedFields.Contains(vk) ||
   208  			(vk == reflect.Ptr && value.Elem().IsValid() && value.Elem().Kind() == reflect.Struct)
   209  
   210  		configName, ok := field.Tag.Lookup(cryptoTagKey)
   211  		if !ok {
   212  			return
   213  		}
   214  		opts, ok := optsMap[configName]
   215  		if !ok {
   216  			return
   217  		}
   218  		src := cast.ToString(value.Interface())
   219  		if utils.IsStrBlank(src) {
   220  			return
   221  		}
   222  
   223  		dst := utils.Must(encode.From(src).Decode(opts...).ToString())
   224  		value.SetString(dst)
   225  		return
   226  	})
   227  }