github.com/zntrio/harp/v2@v2.0.9/pkg/container/seal/v1/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 v1
    19  
    20  import (
    21  	"bytes"
    22  	"crypto/rand"
    23  	"encoding/base64"
    24  	"fmt"
    25  	"strings"
    26  
    27  	"github.com/awnumar/memguard"
    28  
    29  	"github.com/zntrio/harp/v2/pkg/container/seal"
    30  	"github.com/zntrio/harp/v2/pkg/sdk/security/crypto/extra25519"
    31  
    32  	"golang.org/x/crypto/argon2"
    33  	"golang.org/x/crypto/blake2b"
    34  	"golang.org/x/crypto/nacl/box"
    35  )
    36  
    37  const (
    38  	PublicKeyPrefix  = "v1.sk."
    39  	PrivateKeyPrefix = "v1.ck."
    40  )
    41  
    42  // -----------------------------------------------------------------------------
    43  
    44  // CenerateKey create an X25519 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  		// Argon2ID(masterKey, Blake2B-512('harp deterministic salt v1', Target), 1, 64Mb, 4, 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, err := blake2b.New512([]byte("harp deterministic salt v1"))
    69  		if err != nil {
    70  			return "", "", fmt.Errorf("unable to initialize salt derivation: %w", err)
    71  		}
    72  		h.Write([]byte(opts.DCKDTarget))
    73  		salt := h.Sum(nil)
    74  		defer memguard.WipeBytes(salt)
    75  
    76  		// Derive deterministic container key using Argon2id
    77  		dk := argon2.IDKey(masterKey[:32], salt, 1, 64*1024, 4, 64)
    78  		defer memguard.WipeBytes(dk)
    79  
    80  		// Assign to seed
    81  		opts.RandomSource = bytes.NewBuffer(dk)
    82  	}
    83  
    84  	// Generate x25519 container key pair
    85  	pub, priv, errGen := box.GenerateKey(opts.RandomSource)
    86  	if errGen != nil {
    87  		return "", "", fmt.Errorf("unable to generate container key: %w", errGen)
    88  	}
    89  
    90  	// Encode keys
    91  	encodedPub := append([]byte(PublicKeyPrefix), base64.RawURLEncoding.EncodeToString(pub[:])...)
    92  	encodedPriv := append([]byte(PrivateKeyPrefix), base64.RawURLEncoding.EncodeToString(priv[:])...)
    93  
    94  	// No error
    95  	return string(encodedPub), string(encodedPriv), nil
    96  }
    97  
    98  // PublicKeys return the appropriate key format used by the sealing strategy.
    99  func (a *adapter) publicKeys(keys ...string) ([]*[32]byte, error) {
   100  	// v1.pk.[data]
   101  	res := []*[publicKeySize]byte{}
   102  
   103  	for _, key := range keys {
   104  		// Check key prefix
   105  		if !strings.HasPrefix(key, PublicKeyPrefix) {
   106  			return nil, fmt.Errorf("unsuppored public key %q for v1 seal algorithm", key)
   107  		}
   108  
   109  		// Remove prefix if exists
   110  		key = strings.TrimPrefix(key, PublicKeyPrefix)
   111  
   112  		// Decode key
   113  		keyRaw, err := base64.RawURLEncoding.DecodeString(key)
   114  		if err != nil {
   115  			return nil, fmt.Errorf("unable to decode public key %q: %w", key, err)
   116  		}
   117  
   118  		// Public key sanity checks
   119  		if len(keyRaw) != publicKeySize {
   120  			return nil, fmt.Errorf("invalid public key length for key %q", key)
   121  		}
   122  		if extra25519.IsEdLowOrder(keyRaw) {
   123  			return nil, fmt.Errorf("low order public key usage is forbidden for key '%s, try to generate a new one to fix the issue", key)
   124  		}
   125  
   126  		// Copy the public key
   127  		var pk [publicKeySize]byte
   128  		copy(pk[:], keyRaw[:publicKeySize])
   129  
   130  		// Append it to sealing keys
   131  		res = append(res, &pk)
   132  	}
   133  
   134  	// No error
   135  	return res, nil
   136  }