github.com/zntrio/harp/v2@v2.0.9/pkg/container/seal/v1/seal.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  	"crypto/ed25519"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  
    26  	"github.com/awnumar/memguard"
    27  	"google.golang.org/protobuf/proto"
    28  
    29  	containerv1 "github.com/zntrio/harp/v2/api/gen/go/harp/container/v1"
    30  	"github.com/zntrio/harp/v2/pkg/sdk/security/crypto/extra25519"
    31  	"github.com/zntrio/harp/v2/pkg/sdk/types"
    32  
    33  	"golang.org/x/crypto/nacl/box"
    34  	"golang.org/x/crypto/nacl/secretbox"
    35  )
    36  
    37  // Seal a secret container with identities.
    38  func (a *adapter) Seal(rand io.Reader, container *containerv1.Container, encodedPeersPublicKey ...string) (*containerv1.Container, error) {
    39  	return a.seal(rand, container, nil, encodedPeersPublicKey...)
    40  }
    41  
    42  // Seal a secret container with identities and preshared key.
    43  func (a *adapter) SealWithPSK(rand io.Reader, container *containerv1.Container, psk *memguard.LockedBuffer, encodedPeersPublicKey ...string) (*containerv1.Container, error) {
    44  	return a.seal(rand, container, psk, encodedPeersPublicKey...)
    45  }
    46  
    47  //nolint:funlen,gocyclo // To refactor
    48  func (a *adapter) seal(rand io.Reader, container *containerv1.Container, preSharedKey *memguard.LockedBuffer, encodedPeerPublicKeys ...string) (*containerv1.Container, error) {
    49  	// Check parameters
    50  	if types.IsNil(container) {
    51  		return nil, fmt.Errorf("unable to process nil container")
    52  	}
    53  	if types.IsNil(container.Headers) {
    54  		return nil, fmt.Errorf("unable to process nil container headers")
    55  	}
    56  	if len(encodedPeerPublicKeys) == 0 {
    57  		return nil, fmt.Errorf("unable to process empty public keys")
    58  	}
    59  
    60  	// Convert public keys
    61  	peerPublicKeys, err := a.publicKeys(encodedPeerPublicKeys...)
    62  	if err != nil {
    63  		return nil, fmt.Errorf("unable to convert peer public keys: %w", err)
    64  	}
    65  
    66  	// Serialize protobuf payload
    67  	content, err := proto.Marshal(container)
    68  	if err != nil {
    69  		return nil, fmt.Errorf("unable to encode container content: %w", err)
    70  	}
    71  
    72  	// Check cleartext message size.
    73  	if len(content) > messageLimit {
    74  		return nil, errors.New("unable to seal the container, container is too large")
    75  	}
    76  
    77  	// Generate payload encryption key
    78  	var payloadKey [encryptionKeySize]byte
    79  	if _, err = io.ReadFull(rand, payloadKey[:]); err != nil {
    80  		return nil, fmt.Errorf("unable to generate payload key for encryption")
    81  	}
    82  
    83  	// Generate ephemeral signing key
    84  	sigPub, sigPriv, err := ed25519.GenerateKey(rand)
    85  	if err != nil {
    86  		return nil, fmt.Errorf("unable to generate signing keypair")
    87  	}
    88  
    89  	// Encrypt public signature key
    90  	var pubSigNonce [nonceSize]byte
    91  	copy(pubSigNonce[:], staticSignatureNonce)
    92  	encryptedPubSig := secretbox.Seal(nil, sigPub, &pubSigNonce, &payloadKey)
    93  	memguard.WipeBytes(pubSigNonce[:])
    94  
    95  	// Generate ephemeral encryption key
    96  	encPub, encPriv, err := box.GenerateKey(rand)
    97  	if err != nil {
    98  		return nil, fmt.Errorf("unable to generate ephemeral encryption keypair")
    99  	}
   100  
   101  	// Prepare sealed container
   102  	containerHeaders := &containerv1.Header{
   103  		ContentType:         containerSealedContentType,
   104  		EncryptionPublicKey: encPub[:],
   105  		ContainerBox:        encryptedPubSig,
   106  		Recipients:          []*containerv1.Recipient{},
   107  		SealVersion:         SealVersion,
   108  	}
   109  
   110  	// Compute preshared key
   111  	var psk *[preSharedKeySize]byte
   112  	if preSharedKey != nil {
   113  		psk, err = pskStretch(preSharedKey.Bytes(), containerHeaders.EncryptionPublicKey)
   114  		if err != nil {
   115  			return nil, fmt.Errorf("unable to stretch preshared key: %w", err)
   116  		}
   117  	}
   118  
   119  	// Process recipients
   120  	for _, peerPublicKey := range peerPublicKeys {
   121  		if types.IsNil(peerPublicKey) {
   122  			// Ignore nil key
   123  			continue
   124  		}
   125  		if extra25519.IsEdLowOrder(peerPublicKey[:]) {
   126  			return nil, fmt.Errorf("unable to process with low order public key")
   127  		}
   128  
   129  		// Pack recipient using its public key
   130  		r, errPack := packRecipient(rand, &payloadKey, encPriv, peerPublicKey, psk)
   131  		if errPack != nil {
   132  			return nil, fmt.Errorf("unable to pack container recipient (%X): %w", *peerPublicKey, err)
   133  		}
   134  
   135  		// Append to container
   136  		containerHeaders.Recipients = append(containerHeaders.Recipients, r)
   137  	}
   138  
   139  	// Sanity check
   140  	if len(containerHeaders.Recipients) == 0 {
   141  		return nil, errors.New("unable to seal a container without recipients")
   142  	}
   143  
   144  	// Compute header hash
   145  	headerHash, err := computeHeaderHash(containerHeaders)
   146  	if err != nil {
   147  		return nil, fmt.Errorf("unable to compute header hash: %w", err)
   148  	}
   149  
   150  	// Prepare protected content
   151  	protectedHash := computeProtectedHash(headerHash, content)
   152  
   153  	// Sign th protected content
   154  	containerSig := ed25519.Sign(sigPriv, protectedHash)
   155  
   156  	// Prepare encryption nonce form sigHash
   157  	var sigNonce [nonceSize]byte
   158  	copy(sigNonce[:], headerHash[:nonceSize])
   159  
   160  	// No error
   161  	return &containerv1.Container{
   162  		Headers: containerHeaders,
   163  		Raw:     secretbox.Seal(nil, append(containerSig, content...), &sigNonce, &payloadKey),
   164  	}, nil
   165  }