github.com/zntrio/harp/v2@v2.0.9/pkg/container/seal/v2/unseal.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  	"crypto/ecdsa"
    22  	"crypto/elliptic"
    23  	"crypto/sha512"
    24  	"encoding/base64"
    25  	"errors"
    26  	"fmt"
    27  	"math/big"
    28  	"strings"
    29  
    30  	"github.com/awnumar/memguard"
    31  	"google.golang.org/protobuf/proto"
    32  
    33  	containerv1 "github.com/zntrio/harp/v2/api/gen/go/harp/container/v1"
    34  	"github.com/zntrio/harp/v2/pkg/sdk/types"
    35  )
    36  
    37  // Unseal a sealed container with the given identity.
    38  func (a *adapter) Unseal(container *containerv1.Container, identity *memguard.LockedBuffer) (*containerv1.Container, error) {
    39  	return a.unseal(container, identity, nil)
    40  }
    41  
    42  // Unseal a sealed container with the given identity and the given preshared key.
    43  func (a *adapter) UnsealWithPSK(container *containerv1.Container, identity, preSharedKey *memguard.LockedBuffer) (*containerv1.Container, error) {
    44  	return a.unseal(container, identity, preSharedKey)
    45  }
    46  
    47  //nolint:funlen,gocyclo // To refactor
    48  func (a *adapter) unseal(container *containerv1.Container, identity, preSharedKey *memguard.LockedBuffer) (*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 identity == nil {
    57  		return nil, fmt.Errorf("unable to process without container key")
    58  	}
    59  
    60  	// Check headers
    61  	if container.Headers.ContentType != containerSealedContentType {
    62  		return nil, fmt.Errorf("unable to unseal container")
    63  	}
    64  
    65  	// Check ephemeral container public encryption key
    66  	if len(container.Headers.EncryptionPublicKey) != publicKeySize {
    67  		return nil, fmt.Errorf("invalid container public size")
    68  	}
    69  
    70  	// Decode public key
    71  	var publicKey ecdsa.PublicKey
    72  	publicKey.Curve = elliptic.P384()
    73  	publicKey.X, publicKey.Y = elliptic.UnmarshalCompressed(elliptic.P384(), container.Headers.EncryptionPublicKey)
    74  	if publicKey.X == nil {
    75  		return nil, errors.New("invalid container signing public key")
    76  	}
    77  
    78  	// Decode private key
    79  	privRaw, err := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(identity.String(), PrivateKeyPrefix))
    80  	if err != nil {
    81  		return nil, fmt.Errorf("unable to decode private key: %w", err)
    82  	}
    83  	if len(privRaw) != privateKeySize {
    84  		return nil, fmt.Errorf("invalid identity private key length")
    85  	}
    86  	var pk ecdsa.PrivateKey
    87  	pk.PublicKey.Curve = elliptic.P384()
    88  	pk.D = big.NewInt(0).SetBytes(privRaw)
    89  
    90  	// Compute preshared key
    91  	var psk *[preSharedKeySize]byte
    92  	if preSharedKey != nil {
    93  		psk = pskStretch(preSharedKey.Bytes(), container.Headers.EncryptionPublicKey)
    94  	}
    95  
    96  	// Precompute identifier
    97  	derivedKey, err := deriveSharedKeyFromRecipient(&publicKey, &pk, psk)
    98  	if err != nil {
    99  		return nil, fmt.Errorf("unable to execute key agreement: %w", err)
   100  	}
   101  
   102  	// Try recipients
   103  	payloadKey, err := tryRecipientKeys(derivedKey, container.Headers.Recipients, psk)
   104  	if err != nil {
   105  		return nil, fmt.Errorf("error occurred during recipient key tests: %w", err)
   106  	}
   107  
   108  	// Check private key
   109  	if len(payloadKey) != encryptionKeySize {
   110  		return nil, fmt.Errorf("invalid encryption key size")
   111  	}
   112  	var encryptionKey [encryptionKeySize]byte
   113  	copy(encryptionKey[:], payloadKey[:encryptionKeySize])
   114  
   115  	// Decrypt signing public key
   116  	containerSignKeyRaw, err := decrypt(container.Headers.ContainerBox, payloadKey)
   117  	if err != nil {
   118  		return nil, fmt.Errorf("invalid container key")
   119  	}
   120  	if len(containerSignKeyRaw) != publicKeySize {
   121  		return nil, fmt.Errorf("invalid signature key size")
   122  	}
   123  
   124  	// Compute headers hash
   125  	headerHash, err := computeHeaderHash(container.Headers)
   126  	if err != nil {
   127  		return nil, fmt.Errorf("unable to compute header hash: %w", err)
   128  	}
   129  
   130  	// Decrypt payload
   131  	payloadRaw, err := decrypt(container.Raw, &encryptionKey)
   132  	if err != nil || len(payloadRaw) < signatureSize {
   133  		return nil, fmt.Errorf("invalid ciphered content")
   134  	}
   135  
   136  	// Prepare protected content
   137  	protectedHash := computeProtectedHash(headerHash, payloadRaw)
   138  
   139  	// Extract signature / content
   140  	detachedSig := payloadRaw[:signatureSize]
   141  	content := payloadRaw[signatureSize:]
   142  
   143  	// Compute SHA-384 checksum
   144  	digest := sha512.Sum384(protectedHash)
   145  
   146  	var (
   147  		r = big.NewInt(0).SetBytes(detachedSig[:48])
   148  		s = big.NewInt(0).SetBytes(detachedSig[48:])
   149  	)
   150  	// Validate signature
   151  	if ecdsa.Verify(&publicKey, digest[:], r, s) {
   152  		return nil, fmt.Errorf("unable to verify protected content: %w", err)
   153  	}
   154  
   155  	// Unmarshal inner container
   156  	out := &containerv1.Container{}
   157  	if err := proto.Unmarshal(content, out); err != nil {
   158  		return nil, fmt.Errorf("unable to unpack inner content: %w", err)
   159  	}
   160  
   161  	// No error
   162  	return out, nil
   163  }