github.com/greenpau/go-authcrunch@v1.1.4/pkg/kms/key.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kms
    16  
    17  import (
    18  	"bytes"
    19  	"crypto"
    20  	"crypto/ecdsa"
    21  	"crypto/elliptic"
    22  	"crypto/hmac"
    23  	"crypto/rand"
    24  	"crypto/rsa"
    25  	"crypto/x509"
    26  	"encoding/base64"
    27  	"encoding/json"
    28  	"encoding/pem"
    29  	"fmt"
    30  	"io/ioutil"
    31  	"os"
    32  	"path/filepath"
    33  	"strings"
    34  
    35  	jwtlib "github.com/golang-jwt/jwt/v4"
    36  	"github.com/greenpau/go-authcrunch/pkg/errors"
    37  	"github.com/greenpau/go-authcrunch/pkg/shared"
    38  	"github.com/greenpau/go-authcrunch/pkg/user"
    39  )
    40  
    41  // CryptoKey contains a crypto graphic key and associated metadata.
    42  type CryptoKey struct {
    43  	Config *CryptoKeyConfig   `json:"config,omitempty" xml:"config,omitempty" yaml:"config,omitempty"`
    44  	Sign   *CryptoKeyOperator `json:"sign,omitempty" xml:"sign,omitempty" yaml:"sign,omitempty"`
    45  	Verify *CryptoKeyOperator `json:"verify,omitempty" xml:"verify,omitempty" yaml:"verify,omitempty"`
    46  }
    47  
    48  // CryptoKeyTokenOperator represents CryptoKeyOperator token operator.
    49  type CryptoKeyTokenOperator struct {
    50  	ID               string                 `json:"id,omitempty" xml:"id,omitempty" yaml:"id,omitempty"`
    51  	Name             string                 `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"`
    52  	MaxLifetime      int                    `json:"max_lifetime,omitempty" xml:"max_lifetime,omitempty" yaml:"max_lifetime,omitempty"`
    53  	Methods          map[string]interface{} `json:"methods,omitempty" xml:"methods,omitempty" yaml:"methods,omitempty"`
    54  	PreferredMethods []string               `json:"preferred_methods,omitempty" xml:"preferred_methods,omitempty" yaml:"preferred_methods,omitempty"`
    55  	DefaultMethod    string                 `json:"default_method,omitempty" xml:"default_method,omitempty" yaml:"default_method,omitempty"`
    56  	Capable          bool                   `json:"capable,omitempty" xml:"capable,omitempty" yaml:"capable,omitempty"`
    57  	injectKeyID      bool
    58  }
    59  
    60  // CryptoKeyOperator represents CryptoKey operator.
    61  type CryptoKeyOperator struct {
    62  	Token   *CryptoKeyTokenOperator `json:"token,omitempty" xml:"token,omitempty" yaml:"token,omitempty"`
    63  	Secret  interface{}             `json:"secret,omitempty" xml:"secret,omitempty" yaml:"secret,omitempty"`
    64  	Capable bool                    `json:"capable,omitempty" xml:"capable,omitempty" yaml:"capable,omitempty"`
    65  }
    66  
    67  // NewCryptoKeyTokenOperator returns an instance of CryptoKeyTokenOperator.
    68  func NewCryptoKeyTokenOperator() *CryptoKeyTokenOperator {
    69  	op := &CryptoKeyTokenOperator{}
    70  	op.Methods = make(map[string]interface{})
    71  	return op
    72  }
    73  
    74  // NewCryptoKeyOperator returns an instance of CryptoKeyOperator.
    75  func NewCryptoKeyOperator() *CryptoKeyOperator {
    76  	op := &CryptoKeyOperator{}
    77  	op.Token = NewCryptoKeyTokenOperator()
    78  	return op
    79  }
    80  
    81  func newCryptoKey() *CryptoKey {
    82  	k := &CryptoKey{}
    83  	k.Sign = NewCryptoKeyOperator()
    84  	k.Verify = NewCryptoKeyOperator()
    85  	return k
    86  }
    87  
    88  // GetKeysFromConfigs loads keys from one or more key configs.
    89  func GetKeysFromConfigs(cfgs []*CryptoKeyConfig) ([]*CryptoKey, error) {
    90  	var keys []*CryptoKey
    91  	for _, cfg := range cfgs {
    92  		k, err := GetKeysFromConfig(cfg)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		keys = append(keys, k...)
    97  	}
    98  	return keys, nil
    99  }
   100  
   101  // GetKeysFromConfig loads keys from a single key config.
   102  func GetKeysFromConfig(cfg *CryptoKeyConfig) ([]*CryptoKey, error) {
   103  	var keys []*CryptoKey
   104  	switch cfg.Source {
   105  	case "config":
   106  		switch {
   107  		case cfg.Algorithm == "hmac":
   108  			// Discovered shared key
   109  			k := newCryptoKey()
   110  			k.Config = cfg
   111  			keys = append(keys, k)
   112  		case cfg.FilePath != "":
   113  			fileKeys, err := extractKeysFromFile(cfg.FilePath, cfg)
   114  			if err != nil {
   115  				return nil, err
   116  			}
   117  			keys = append(keys, fileKeys...)
   118  		case cfg.DirPath != "":
   119  			dirKeys, err := extractKeysFromDir(cfg.DirPath, cfg)
   120  			if err != nil {
   121  				return nil, err
   122  			}
   123  			keys = append(keys, dirKeys...)
   124  		default:
   125  			return nil, fmt.Errorf("unsupported config")
   126  		}
   127  	case "env":
   128  		switch {
   129  		case cfg.EnvVarType == "key":
   130  			if strings.HasPrefix(cfg.EnvVarValue, "---") {
   131  				// Discovered symmetric key
   132  				k, err := extractKey([]byte(cfg.EnvVarValue), cfg)
   133  				if err != nil {
   134  					return nil, err
   135  				}
   136  				keys = append(keys, k)
   137  				break
   138  			}
   139  			// Discovered shared key
   140  			k := newCryptoKey()
   141  			k.Config = cfg
   142  			k.Config.Algorithm = "hmac"
   143  			k.Config.Secret = k.Config.EnvVarValue
   144  			keys = append(keys, k)
   145  		case cfg.EnvVarType == "file":
   146  			fileKeys, err := extractKeysFromFile(cfg.EnvVarValue, cfg)
   147  			if err != nil {
   148  				return nil, err
   149  			}
   150  			keys = append(keys, fileKeys...)
   151  		case cfg.EnvVarType == "directory":
   152  			dirKeys, err := extractKeysFromDir(cfg.EnvVarValue, cfg)
   153  			if err != nil {
   154  				return nil, err
   155  			}
   156  			keys = append(keys, dirKeys...)
   157  		default:
   158  			return nil, fmt.Errorf("unsupported env config type %s", cfg.EnvVarType)
   159  		}
   160  	case "generate":
   161  		switch cfg.Algorithm {
   162  		case "ecdsa":
   163  			cfg.parsed = true
   164  			key, err := generateKey(cfg, cfg.ID, "ES512")
   165  			if err != nil {
   166  				return nil, fmt.Errorf("generating key failed: %v", err)
   167  			}
   168  			keys = append(keys, key)
   169  		default:
   170  			return nil, fmt.Errorf("unsupported algorithm for generate: %s", cfg.Algorithm)
   171  		}
   172  	default:
   173  		return nil, fmt.Errorf("unsupported source: '%s'", cfg.Source)
   174  	}
   175  
   176  	for _, k := range keys {
   177  		switch k.Config.Algorithm {
   178  		case "hmac":
   179  			k.Sign.Capable = true
   180  			k.Verify.Capable = true
   181  			k.Sign.Secret = []byte(k.Config.Secret)
   182  			k.Verify.Secret = []byte(k.Config.Secret)
   183  		case "rsa", "ecdsa":
   184  		default:
   185  			return nil, fmt.Errorf("unsupported config algorithm %s", k.Config.Algorithm)
   186  		}
   187  		k.enableUsage()
   188  	}
   189  	return keys, nil
   190  }
   191  
   192  func (k *CryptoKey) enableUsage() {
   193  	methods := getMethodsPerAlgo(k.Config.Algorithm)
   194  	if k.Sign.Capable {
   195  		k.Sign.Token.ID = k.Config.ID
   196  		k.Sign.Token.Capable = true
   197  		if len(k.Sign.Token.PreferredMethods) == 0 {
   198  			k.Sign.Token.PreferredMethods = methods
   199  		}
   200  		for _, m := range k.Sign.Token.PreferredMethods {
   201  			k.Sign.Token.Methods[m] = true
   202  		}
   203  		k.Sign.Token.Name = k.Config.TokenName
   204  		k.Sign.Token.MaxLifetime = k.Config.TokenLifetime
   205  		k.Sign.Token.DefaultMethod = k.Sign.Token.PreferredMethods[0]
   206  		if k.Config.ID != defaultKeyID && k.Config.ID != "" {
   207  			k.Sign.Token.injectKeyID = true
   208  		}
   209  	}
   210  	if k.Verify.Capable {
   211  		k.Verify.Token.ID = k.Config.ID
   212  		k.Verify.Token.Capable = true
   213  		if len(k.Verify.Token.PreferredMethods) == 0 {
   214  			k.Verify.Token.PreferredMethods = methods
   215  		}
   216  		for _, m := range k.Verify.Token.PreferredMethods {
   217  			k.Verify.Token.Methods[m] = true
   218  		}
   219  		k.Verify.Token.Name = k.Config.TokenName
   220  		k.Verify.Token.MaxLifetime = k.Config.TokenLifetime
   221  		k.Verify.Token.DefaultMethod = k.Verify.Token.PreferredMethods[0]
   222  	}
   223  }
   224  
   225  // SignToken signs data using the requested method and returns it as string.
   226  func (k *CryptoKey) SignToken(signMethod interface{}, usr *user.User) error {
   227  	if !k.Sign.Token.Capable {
   228  		return errors.ErrSigningKeyNotFound.WithArgs(signMethod)
   229  	}
   230  	response, err := k.sign(signMethod, usr.AsMap())
   231  	if err != nil {
   232  		return err
   233  	}
   234  	usr.Token = response.(string)
   235  	return nil
   236  }
   237  
   238  func (k *CryptoKey) sign(signMethod, data interface{}) (interface{}, error) {
   239  	var method string
   240  	if signMethod == nil {
   241  		if k.Sign.Token.DefaultMethod == "" {
   242  			return nil, errors.ErrInvalidSigningMethod
   243  		}
   244  		method = k.Sign.Token.DefaultMethod
   245  	} else {
   246  		method = signMethod.(string)
   247  		if _, supported := k.Sign.Token.Methods[method]; !supported {
   248  			return nil, errors.ErrUnsupportedSigningMethod.WithArgs(method)
   249  		}
   250  	}
   251  
   252  	header := map[string]interface{}{"typ": "JWT", "alg": method}
   253  	if k.Sign.Token.injectKeyID {
   254  		header["kid"] = k.Sign.Token.ID
   255  	}
   256  	jh, err := json.Marshal(header)
   257  	if err != nil {
   258  		return nil, errors.ErrDataSigningFailed.WithArgs(method, err)
   259  	}
   260  	jb, err := json.Marshal(data)
   261  	if err != nil {
   262  		return nil, errors.ErrDataSigningFailed.WithArgs(method, err)
   263  	}
   264  	s := base64.RawURLEncoding.EncodeToString(jh) + "." + base64.RawURLEncoding.EncodeToString(jb)
   265  
   266  	switch signingMethods[method] {
   267  	case "hmac":
   268  		return k.signHMAC(method, s)
   269  	case "rsa":
   270  		return k.signRSA(method, s)
   271  	case "ecdsa":
   272  		return k.signECDSA(method, s)
   273  	}
   274  
   275  	return nil, errors.ErrDataSigningFailed.WithArgs(method, "unsupported method")
   276  }
   277  
   278  // ProvideKey returns the appropriate encryption key.
   279  func (k *CryptoKey) ProvideKey(token *jwtlib.Token) (interface{}, error) {
   280  	switch k.Config.Algorithm {
   281  	case "hmac":
   282  		if _, validMethod := token.Method.(*jwtlib.SigningMethodHMAC); !validMethod {
   283  			return nil, errors.ErrUnexpectedSigningMethod.WithArgs("HS", token.Header["alg"])
   284  		}
   285  	case "rsa":
   286  		if _, validMethod := token.Method.(*jwtlib.SigningMethodRSA); !validMethod {
   287  			return nil, errors.ErrUnexpectedSigningMethod.WithArgs("RS", token.Header["alg"])
   288  		}
   289  	case "ecdsa":
   290  		if _, validMethod := token.Method.(*jwtlib.SigningMethodECDSA); !validMethod {
   291  			return nil, errors.ErrUnexpectedSigningMethod.WithArgs("ES", token.Header["alg"])
   292  		}
   293  	}
   294  	return k.Verify.Secret, nil
   295  }
   296  
   297  func extractBytesFromFile(fp string) ([]byte, error) {
   298  	ext := filepath.Ext(fp)
   299  	switch ext {
   300  	case ".pem", ".key":
   301  	default:
   302  		return nil, errors.ErrCryptoKeyConfigFileNotSupported.WithArgs(fp)
   303  	}
   304  	b, err := ioutil.ReadFile(fp)
   305  	if err != nil {
   306  		return nil, errors.ErrCryptoKeyConfigReadFile.WithArgs(fp, err)
   307  	}
   308  	return b, nil
   309  }
   310  
   311  func extractKeysFromFile(fp string, cfg *CryptoKeyConfig) ([]*CryptoKey, error) {
   312  	var keys []*CryptoKey
   313  	b, err := extractBytesFromFile(fp)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  	key, err := extractKey(b, cfg)
   318  	if err != nil {
   319  		return nil, errors.ErrCryptoKeyConfigReadFile.WithArgs(fp, err)
   320  	}
   321  	keys = append(keys, key)
   322  	if len(keys) == 0 {
   323  		return nil, errors.ErrCryptoKeyConfigFileKeyNotFound.WithArgs(fp)
   324  	}
   325  	return keys, nil
   326  }
   327  
   328  func extractKey(kb []byte, cfg *CryptoKeyConfig) (*CryptoKey, error) {
   329  	var curveName string
   330  	k := newCryptoKey()
   331  	kcfg := *cfg
   332  	k.Config = &kcfg
   333  
   334  	var block *pem.Block
   335  	if block, _ = pem.Decode(kb); block == nil {
   336  		return nil, errors.ErrNotPEMEncodedKey
   337  	}
   338  
   339  	switch {
   340  	case bytes.Contains(kb, []byte("RSA PRIVATE KEY")):
   341  		k.Config.Algorithm = "rsa"
   342  		privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
   343  		if err != nil {
   344  			return nil, err
   345  		}
   346  		k.Sign.Capable = true
   347  		k.Sign.Secret = privKey
   348  		switch k.Config.Usage {
   349  		case "sign":
   350  		default:
   351  			k.Verify.Capable = true
   352  			k.Verify.Secret = privKey.Public()
   353  		}
   354  	case bytes.Contains(kb, []byte("EC PRIVATE KEY")):
   355  		k.Config.Algorithm = "ecdsa"
   356  		privKey, err := x509.ParseECPrivateKey(block.Bytes)
   357  		if err != nil {
   358  			return nil, err
   359  		}
   360  		k.Sign.Capable = true
   361  		k.Sign.Secret = privKey
   362  		curve := privKey.Curve.Params()
   363  		if curve == nil {
   364  			return nil, errors.ErrNoECDSACurveParamsFound
   365  		}
   366  		curveName = curve.Name
   367  		switch k.Config.Usage {
   368  		case "sign":
   369  		default:
   370  			k.Verify.Capable = true
   371  			k.Verify.Secret = privKey.Public()
   372  		}
   373  	case bytes.Contains(kb, []byte("PRIVATE KEY")):
   374  		privKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
   375  		if err != nil {
   376  			return nil, err
   377  		}
   378  		switch privKey := privKey.(type) {
   379  		case *rsa.PrivateKey:
   380  			k.Config.Algorithm = "rsa"
   381  			k.Sign.Capable = true
   382  			k.Sign.Secret = privKey
   383  			switch k.Config.Usage {
   384  			case "sign":
   385  			default:
   386  				k.Verify.Capable = true
   387  				k.Verify.Secret = privKey.Public()
   388  			}
   389  		case *ecdsa.PrivateKey:
   390  			k.Config.Algorithm = "ecdsa"
   391  			k.Sign.Capable = true
   392  			k.Sign.Secret = privKey
   393  			curve := privKey.Curve.Params()
   394  			if curve == nil {
   395  				return nil, errors.ErrNoECDSACurveParamsFound
   396  			}
   397  			curveName = curve.Name
   398  			switch k.Config.Usage {
   399  			case "sign":
   400  			default:
   401  				k.Verify.Capable = true
   402  				k.Verify.Secret = privKey.Public()
   403  			}
   404  		default:
   405  			// case ed25519.PrivateKey
   406  			return nil, errors.ErrCryptoKeyConfigUnsupportedPrivateKeyAlgo.WithArgs(privKey)
   407  		}
   408  	case bytes.Contains(kb, []byte("RSA PUBLIC KEY")):
   409  		pubKey, err := x509.ParsePKCS1PublicKey(block.Bytes)
   410  		if err != nil {
   411  			return nil, err
   412  		}
   413  		k.Config.Algorithm = "rsa"
   414  		k.Verify.Capable = true
   415  		k.Verify.Secret = pubKey
   416  	case bytes.Contains(kb, []byte("PUBLIC KEY")):
   417  		pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
   418  		if err != nil {
   419  			return nil, err
   420  		}
   421  		k.Verify.Capable = true
   422  		switch pubKey := pubKey.(type) {
   423  		case *rsa.PublicKey:
   424  			k.Config.Algorithm = "rsa"
   425  			k.Verify.Secret = pubKey
   426  		case *ecdsa.PublicKey:
   427  			k.Config.Algorithm = "ecdsa"
   428  			k.Verify.Secret = pubKey
   429  			curve := pubKey.Curve.Params()
   430  			if curve == nil {
   431  				return nil, errors.ErrNoECDSACurveParamsFound
   432  			}
   433  			curveName = curve.Name
   434  		default:
   435  			// case *dsa.PublicKey
   436  			// case ed25519.PublicKey
   437  			return nil, errors.ErrCryptoKeyConfigUnsupportedPublicKeyAlgo.WithArgs(pubKey)
   438  		}
   439  	default:
   440  		return nil, errors.ErrNotPEMEncodedKey
   441  	}
   442  
   443  	if k.Config.Algorithm == "ecdsa" {
   444  		// See https://golang.org/src/crypto/elliptic/elliptic.go.
   445  		var method string
   446  		switch curveName {
   447  		case "P-256":
   448  			method = "ES256"
   449  		case "P-384":
   450  			method = "ES384"
   451  		case "P-521":
   452  			method = "ES512"
   453  		default:
   454  			return nil, errors.ErrUnsupportedECDSACurve.WithArgs(curveName)
   455  		}
   456  		if k.Sign.Capable {
   457  			k.Sign.Token.PreferredMethods = []string{method}
   458  		}
   459  		if k.Verify.Capable {
   460  			k.Verify.Token.PreferredMethods = []string{method}
   461  		}
   462  	}
   463  	return k, nil
   464  }
   465  
   466  func extractKeysFromDir(dirPath string, cfg *CryptoKeyConfig) ([]*CryptoKey, error) {
   467  	var dirKeys []*CryptoKey
   468  	err := filepath.Walk(dirPath, func(fp string, fi os.FileInfo, err error) error {
   469  		if err != nil {
   470  			return err
   471  		}
   472  		if fi.IsDir() {
   473  			return nil
   474  		}
   475  		ext := filepath.Ext(fp)
   476  		switch ext {
   477  		case ".pem", ".key":
   478  		default:
   479  			return nil
   480  		}
   481  
   482  		kcfg := *cfg
   483  		ncfg := &kcfg
   484  		kid := filepath.Base(fp)
   485  		kid = strings.TrimSuffix(kid, ext)
   486  		kid = normalizeKeyID(kid)
   487  
   488  		ncfg.ID = kid
   489  		ncfg.FilePath = fp
   490  
   491  		keys, err := extractKeysFromFile(fp, ncfg)
   492  		if err != nil {
   493  			return err
   494  		}
   495  		dirKeys = append(dirKeys, keys...)
   496  		return nil
   497  	})
   498  	if err != nil {
   499  		return nil, errors.ErrWalkDir.WithArgs(err)
   500  	}
   501  	if len(dirKeys) == 0 {
   502  		return nil, errors.ErrWalkDir.WithArgs("no crypto keys found")
   503  	}
   504  	return dirKeys, nil
   505  }
   506  
   507  func normalizeKeyID(s string) string {
   508  	b := []byte{}
   509  	for _, c := range []byte(s) {
   510  		if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_' || c == '-' {
   511  			b = append(b, c)
   512  		}
   513  	}
   514  	return strings.ToLower(string(b))
   515  }
   516  
   517  func (k *CryptoKey) signECDSA(method, data string) (interface{}, error) {
   518  	var h crypto.Hash
   519  	var cb int
   520  	switch method {
   521  	case "ES256":
   522  		h = crypto.SHA256
   523  		cb = 256
   524  	case "ES384":
   525  		h = crypto.SHA384
   526  		cb = 384
   527  	case "ES512":
   528  		h = crypto.SHA512
   529  		cb = 521
   530  	default:
   531  		return nil, errors.ErrDataSigningFailed.WithArgs("ECDSA", "unsupported method")
   532  	}
   533  	if !h.Available() {
   534  		return nil, errors.ErrDataSigningFailed.WithArgs("ECDSA", "unavailable method")
   535  	}
   536  	hf := h.New()
   537  	hf.Write([]byte(data))
   538  
   539  	pk := k.Sign.Secret.(*ecdsa.PrivateKey)
   540  	if cb != pk.Curve.Params().BitSize {
   541  		return nil, errors.ErrDataSigningFailed.WithArgs("ECDSA", "curve bitsize mismatch")
   542  	}
   543  
   544  	r, s, err := ecdsa.Sign(rand.Reader, pk, hf.Sum(nil))
   545  	if err != nil {
   546  		return nil, errors.ErrDataSigningFailed.WithArgs("ECDSA", err)
   547  	}
   548  
   549  	sz := cb / 8
   550  	if cb%8 > 0 {
   551  		sz++
   552  	}
   553  
   554  	b := make([]byte, 2*sz)
   555  	r.FillBytes(b[0:sz])
   556  	s.FillBytes(b[sz:])
   557  	return data + "." + base64.RawURLEncoding.EncodeToString(b), nil
   558  }
   559  
   560  func (k *CryptoKey) signRSA(method, data string) (interface{}, error) {
   561  	var h crypto.Hash
   562  	switch method {
   563  	case "RS256":
   564  		h = crypto.SHA256
   565  	case "RS384":
   566  		h = crypto.SHA384
   567  	case "RS512":
   568  		h = crypto.SHA512
   569  	default:
   570  		return nil, errors.ErrDataSigningFailed.WithArgs("RSA", "unsupported method")
   571  	}
   572  	if !h.Available() {
   573  		return nil, errors.ErrDataSigningFailed.WithArgs("RSA", "unavailable method")
   574  	}
   575  	hf := h.New()
   576  	hf.Write([]byte(data))
   577  
   578  	pk := k.Sign.Secret.(*rsa.PrivateKey)
   579  	b, err := rsa.SignPKCS1v15(rand.Reader, pk, h, hf.Sum(nil))
   580  	if err != nil {
   581  		return nil, errors.ErrDataSigningFailed.WithArgs("RSA", err)
   582  	}
   583  	return data + "." + base64.RawURLEncoding.EncodeToString(b), nil
   584  }
   585  
   586  func (k *CryptoKey) signHMAC(method, data string) (interface{}, error) {
   587  	var h crypto.Hash
   588  	switch method {
   589  	case "HS256":
   590  		h = crypto.SHA256
   591  	case "HS384":
   592  		h = crypto.SHA384
   593  	case "HS512":
   594  		h = crypto.SHA512
   595  	default:
   596  		return nil, errors.ErrDataSigningFailed.WithArgs("HMAC", "unsupported method")
   597  	}
   598  	if !h.Available() {
   599  		return nil, errors.ErrDataSigningFailed.WithArgs("HMAC", "unavailable method")
   600  	}
   601  	pk := k.Sign.Secret.([]byte)
   602  	hf := hmac.New(h.New, pk)
   603  	hf.Write([]byte(data))
   604  	return data + "." + base64.RawURLEncoding.EncodeToString(hf.Sum(nil)), nil
   605  }
   606  
   607  func generateKey(cfg *CryptoKeyConfig, tag, algo string) (*CryptoKey, error) {
   608  	generateES512Key := func() ([]byte, error) {
   609  		c := elliptic.P521()
   610  		priv, err := ecdsa.GenerateKey(c, rand.Reader)
   611  		if err != nil {
   612  			return nil, err
   613  		}
   614  		if !c.IsOnCurve(priv.PublicKey.X, priv.PublicKey.Y) {
   615  			return nil, err
   616  		}
   617  		derBytes, err := x509.MarshalECPrivateKey(priv)
   618  		if err != nil {
   619  			return nil, err
   620  		}
   621  		pemBytes := pem.EncodeToMemory(
   622  			&pem.Block{
   623  				Type:  "EC PRIVATE KEY",
   624  				Bytes: derBytes,
   625  			},
   626  		)
   627  		return pemBytes, nil
   628  	}
   629  	var (
   630  		generateKey func() ([]byte, error)
   631  		generated   bool
   632  		kb          string
   633  	)
   634  	switch algo {
   635  	case "ES512":
   636  		generateKey = generateES512Key
   637  	default:
   638  		return nil, errors.ErrCryptoKeyStoreAutoGenerateAlgo.WithArgs(algo)
   639  	}
   640  	for i := 1; i < 5; i++ {
   641  		pemBytes, err := generateKey()
   642  		if err != nil || pemBytes == nil {
   643  			// try again
   644  			continue
   645  		}
   646  		kb = string(pemBytes)
   647  		generated = true
   648  	}
   649  
   650  	if !generated {
   651  		return nil, errors.ErrCryptoKeyStoreAutoGenerateFailed.WithArgs("failed")
   652  	}
   653  	if err := shared.Buffer.Add(tag, kb); err != nil {
   654  		if err.Error() != "not empty" {
   655  			return nil, errors.ErrCryptoKeyStoreAutoGenerateFailed.WithArgs(err)
   656  		}
   657  		kb, err = shared.Buffer.Get(tag)
   658  		if err != nil {
   659  			return nil, errors.ErrCryptoKeyStoreAutoGenerateFailed.WithArgs(err)
   660  		}
   661  	}
   662  	key, err := extractKey([]byte(kb), cfg)
   663  	if err != nil {
   664  		return nil, errors.ErrCryptoKeyStoreAutoGenerateFailed.WithArgs(err)
   665  	}
   666  
   667  	return key, nil
   668  }