github.com/zntrio/harp/v2@v2.0.9/pkg/container/seal/v2/key.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package v2
    19  
    20  import (
    21  	"bytes"
    22  	"crypto/ecdsa"
    23  	"crypto/elliptic"
    24  	"crypto/hmac"
    25  	"crypto/rand"
    26  	"crypto/sha512"
    27  	"encoding/base64"
    28  	"fmt"
    29  	"strings"
    30  
    31  	"github.com/awnumar/memguard"
    32  
    33  	"github.com/zntrio/harp/v2/pkg/container/seal"
    34  	"github.com/zntrio/harp/v2/pkg/sdk/security/crypto/deterministicecdsa"
    35  
    36  	"golang.org/x/crypto/pbkdf2"
    37  )
    38  
    39  const (
    40  	PublicKeyPrefix  = "v2.sk."
    41  	PrivateKeyPrefix = "v2.ck."
    42  )
    43  
    44  // CenerateKey create an ECDSA P-384 key pair used as container identifier.
    45  func (a *adapter) GenerateKey(fopts ...seal.GenerateOption) (publicKey, privateKey string, err error) {
    46  	// Prepare defaults
    47  	opts := &seal.GenerateOptions{
    48  		DCKDMasterKey: nil,
    49  		DCKDTarget:    "",
    50  		RandomSource:  rand.Reader,
    51  	}
    52  
    53  	// Apply optional parameters
    54  	for _, f := range fopts {
    55  		f(opts)
    56  	}
    57  
    58  	// Master key derivation
    59  	if opts.DCKDMasterKey != nil {
    60  		// PBKDF2-SHA512(masterKey, HMAC-SHA-512('harp deterministic salt v2', Target), 250000, 64)
    61  		// Don't clean bytes, already done by memguard.
    62  		masterKey := opts.DCKDMasterKey.Bytes()
    63  		if len(masterKey) < 32 {
    64  			return "", "", fmt.Errorf("the master key must be 32 bytes long at least")
    65  		}
    66  
    67  		// Generate deterministic salt
    68  		h := hmac.New(sha512.New, []byte("harp deterministic salt v2"))
    69  		h.Write([]byte(opts.DCKDTarget))
    70  		salt := h.Sum(nil)
    71  		defer memguard.WipeBytes(salt)
    72  
    73  		// Derive deterministic container key using PBKDF2-SHA512
    74  		dk := pbkdf2.Key(masterKey[:32], salt, 250000, 64, sha512.New)
    75  		defer memguard.WipeBytes(dk)
    76  
    77  		// Assign to seed
    78  		opts.RandomSource = bytes.NewBuffer(dk)
    79  	}
    80  
    81  	// Generate ECDSA P-384 container key pair
    82  	priv, errGen := deterministicecdsa.GenerateKey(elliptic.P384(), opts.RandomSource)
    83  	if errGen != nil {
    84  		return "", "", fmt.Errorf("unable to generate container key: %w", errGen)
    85  	}
    86  
    87  	// Encode keys
    88  	encodedPub := append([]byte(PublicKeyPrefix), base64.RawURLEncoding.EncodeToString(elliptic.MarshalCompressed(priv.Curve, priv.PublicKey.X, priv.PublicKey.Y))...)
    89  	encodedPriv := append([]byte(PrivateKeyPrefix), base64.RawURLEncoding.EncodeToString(priv.D.Bytes())...)
    90  
    91  	// No error
    92  	return string(encodedPub), string(encodedPriv), nil
    93  }
    94  
    95  // PublicKeys return the appropriate key format used by the sealing strategy.
    96  func (a *adapter) publicKeys(keys ...string) ([]*ecdsa.PublicKey, error) {
    97  	// v2.pk.[data]
    98  	res := []*ecdsa.PublicKey{}
    99  
   100  	for _, key := range keys {
   101  		// Check key prefix
   102  		if !strings.HasPrefix(key, PublicKeyPrefix) {
   103  			return nil, fmt.Errorf("unsuppored public key %q for v2 seal algorithm", key)
   104  		}
   105  
   106  		// Remove prefix if exists
   107  		key = strings.TrimPrefix(key, PublicKeyPrefix)
   108  
   109  		// Decode key
   110  		keyRaw, err := base64.RawURLEncoding.DecodeString(key)
   111  		if err != nil {
   112  			return nil, fmt.Errorf("unable to decode public key %q: %w", key, err)
   113  		}
   114  
   115  		// Public key sanity checks
   116  		if len(keyRaw) != publicKeySize {
   117  			return nil, fmt.Errorf("invalid public key length for key %q", key)
   118  		}
   119  
   120  		// Decode the compressed point
   121  		x, y := elliptic.UnmarshalCompressed(elliptic.P384(), keyRaw)
   122  		if x == nil {
   123  			return nil, fmt.Errorf("invalid public key %q", key)
   124  		}
   125  
   126  		// Reassemble the public key
   127  		pub := ecdsa.PublicKey{
   128  			Curve: elliptic.P384(),
   129  			X:     x,
   130  			Y:     y,
   131  		}
   132  
   133  		// Append it to sealing keys
   134  		res = append(res, &pub)
   135  	}
   136  
   137  	// No error
   138  	return res, nil
   139  }