github.com/zntrio/harp/v2@v2.0.9/pkg/container/seal/v2/helpers.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/aes"
    23  	"crypto/cipher"
    24  	"crypto/ecdsa"
    25  	"crypto/elliptic"
    26  	"crypto/hmac"
    27  	cryptorand "crypto/rand"
    28  	"crypto/sha512"
    29  	"encoding/binary"
    30  	"errors"
    31  	"fmt"
    32  	"io"
    33  
    34  	"github.com/awnumar/memguard"
    35  	"google.golang.org/protobuf/proto"
    36  
    37  	containerv1 "github.com/zntrio/harp/v2/api/gen/go/harp/container/v1"
    38  	"github.com/zntrio/harp/v2/pkg/sdk/security"
    39  
    40  	"golang.org/x/crypto/hkdf"
    41  )
    42  
    43  func pskStretch(key, salt []byte) *[preSharedKeySize]byte {
    44  	pskh := hmac.New(sha512.New, key)
    45  	pskh.Write(salt)
    46  	hashPsk := pskh.Sum(nil)
    47  
    48  	psk := &[preSharedKeySize]byte{}
    49  	copy(psk[:], hashPsk[:preSharedKeySize])
    50  
    51  	return psk
    52  }
    53  
    54  func tryRecipientKeys(derivedKey *[32]byte, recipients []*containerv1.Recipient, preSharedKey *[preSharedKeySize]byte) (*[32]byte, error) {
    55  	// Calculate recipient identifier
    56  	identifier, err := keyIdentifierFromDerivedKey(derivedKey, preSharedKey)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("unable to generate identifier: %w", err)
    59  	}
    60  
    61  	// Find matching recipient
    62  	for _, r := range recipients {
    63  		// Check recipient identifiers
    64  		if !security.SecureCompare(identifier, r.Identifier) {
    65  			continue
    66  		}
    67  
    68  		// Try to decrypt the secretbox with the derived key.
    69  		clearText, err := decrypt(r.Key, derivedKey)
    70  		if err != nil {
    71  			return nil, fmt.Errorf("invalid recipient encryption key")
    72  		}
    73  
    74  		var payloadKey [32]byte
    75  		copy(payloadKey[:], clearText)
    76  
    77  		// Encryption key found, return no error.
    78  		return &payloadKey, nil
    79  	}
    80  
    81  	// No recipient found in list.
    82  	return nil, fmt.Errorf("no recipient found")
    83  }
    84  
    85  func prepareSignature(rand io.Reader, encryptionKey *[32]byte) (*ecdsa.PrivateKey, []byte, error) {
    86  	// Generate ephemeral signing key
    87  	sigPriv, err := ecdsa.GenerateKey(signatureCurve, cryptorand.Reader)
    88  	if err != nil {
    89  		return nil, nil, fmt.Errorf("unable to generate signing keypair")
    90  	}
    91  
    92  	// Compress public key point
    93  	sigPub := elliptic.MarshalCompressed(sigPriv.Curve, sigPriv.PublicKey.X, sigPriv.PublicKey.Y)
    94  
    95  	// Encrypt public signing key
    96  	encryptedPubSig, err := encrypt(rand, sigPub, encryptionKey)
    97  	if err != nil {
    98  		return nil, nil, fmt.Errorf("unable to encrypt public signing key: %w", err)
    99  	}
   100  
   101  	// Cleanup
   102  	memguard.WipeBytes(sigPub)
   103  
   104  	// No error
   105  	return sigPriv, encryptedPubSig, nil
   106  }
   107  
   108  func signContainer(sigPriv *ecdsa.PrivateKey, headers *containerv1.Header, container *containerv1.Container) (content, containerSig []byte, err error) {
   109  	// Serialize protobuf payload
   110  	content, err = proto.Marshal(container)
   111  	if err != nil {
   112  		return nil, nil, fmt.Errorf("unable to encode container content: %w", err)
   113  	}
   114  
   115  	// Compute header hash
   116  	headerHash, err := computeHeaderHash(headers)
   117  	if err != nil {
   118  		return nil, nil, fmt.Errorf("unable to compute header hash: %w", err)
   119  	}
   120  
   121  	// Compute protected content hash
   122  	protectedHash := computeProtectedHash(headerHash, content)
   123  
   124  	// Sign the protected content
   125  	r, s, err := ecdsa.Sign(cryptorand.Reader, sigPriv, protectedHash)
   126  	if err != nil {
   127  		return nil, nil, fmt.Errorf("unable to sign protected content: %w", err)
   128  	}
   129  
   130  	// Container signature
   131  	containerSig = append(r.Bytes(), s.Bytes()...)
   132  
   133  	// No error
   134  	return content, containerSig, nil
   135  }
   136  
   137  func generatedEncryptionKey(rand io.Reader) (*[32]byte, error) {
   138  	// Generate payload encryption key
   139  	var payloadKey [encryptionKeySize]byte
   140  	if _, err := io.ReadFull(rand, payloadKey[:]); err != nil {
   141  		return nil, fmt.Errorf("unable to generate payload key for encryption")
   142  	}
   143  
   144  	// No error
   145  	return &payloadKey, nil
   146  }
   147  
   148  func encrypt(rand io.Reader, plaintext []byte, key *[32]byte) ([]byte, error) {
   149  	// Check cleartext message size.
   150  	if len(plaintext) > messageLimit {
   151  		return nil, errors.New("value too large")
   152  	}
   153  
   154  	// Generate random nonce
   155  	var seed [seedSize]byte
   156  	if _, err := io.ReadFull(rand, seed[:]); err != nil {
   157  		return nil, fmt.Errorf("unable to generate random encryption nonce: %w", err)
   158  	}
   159  
   160  	// Derive keys from seed and secret key
   161  	ek, n2, ak, err := kdf(key, seed[:])
   162  	if err != nil {
   163  		return nil, fmt.Errorf("unable to derive keys from seed: %w", err)
   164  	}
   165  
   166  	// Prepare an AES-256-CTR stream cipher
   167  	block, err := aes.NewCipher(ek)
   168  	if err != nil {
   169  		return nil, fmt.Errorf("unable to prepare block cipher: %w", err)
   170  	}
   171  	ciph := cipher.NewCTR(block, n2)
   172  
   173  	// Encrypt the payload
   174  	c := make([]byte, len(plaintext))
   175  	ciph.XORKeyStream(c, plaintext)
   176  
   177  	// Compute MAC
   178  	t, err := mac(ak, seed[:], c)
   179  	if err != nil {
   180  		return nil, fmt.Errorf("paseto: unable to compute MAC: %w", err)
   181  	}
   182  
   183  	// Serialize final payload
   184  	// n || c || t
   185  	body := append([]byte{}, seed[:]...)
   186  	body = append(body, c...)
   187  	body = append(body, t...)
   188  
   189  	// No error
   190  	return body, nil
   191  }
   192  
   193  func decrypt(ciphertext []byte, key *[32]byte) ([]byte, error) {
   194  	// Check arguments
   195  	if len(ciphertext) < nonceSize {
   196  		return nil, errors.New("ciphered text too short")
   197  	}
   198  
   199  	// Extract components
   200  	n := ciphertext[:seedSize]
   201  	t := ciphertext[len(ciphertext)-macSize:]
   202  	c := ciphertext[seedSize : len(ciphertext)-macSize]
   203  
   204  	// Derive keys from seed and secret key
   205  	ek, n2, ak, err := kdf(key, n)
   206  	if err != nil {
   207  		return nil, fmt.Errorf("unable to derive keys from seed: %w", err)
   208  	}
   209  
   210  	// Compute MAC
   211  	t2, err := mac(ak, n, c)
   212  	if err != nil {
   213  		return nil, fmt.Errorf("unable to compute MAC: %w", err)
   214  	}
   215  
   216  	// Time-constant compare MAC
   217  	if !security.SecureCompare(t, t2) {
   218  		return nil, errors.New("invalid pre-authentication header")
   219  	}
   220  
   221  	// Prepare an AES-256-CTR stream cipher
   222  	block, err := aes.NewCipher(ek)
   223  	if err != nil {
   224  		return nil, fmt.Errorf("unable to prepare block cipher: %w", err)
   225  	}
   226  	ciph := cipher.NewCTR(block, n2)
   227  
   228  	// Decrypt the payload
   229  	m := make([]byte, len(c))
   230  	ciph.XORKeyStream(m, c)
   231  
   232  	// No error
   233  	return m, nil
   234  }
   235  
   236  func computeHeaderHash(headers *containerv1.Header) ([]byte, error) {
   237  	// Check arguments
   238  	if headers == nil {
   239  		return nil, errors.New("unable process with nil headers")
   240  	}
   241  
   242  	// Prepare signature
   243  	header, err := proto.Marshal(headers)
   244  	if err != nil {
   245  		return nil, fmt.Errorf("unable to marshal container headers")
   246  	}
   247  
   248  	// Hash serialized proto
   249  	hash := sha512.Sum512(header)
   250  
   251  	// No error
   252  	return hash[:], nil
   253  }
   254  
   255  func computeProtectedHash(headerHash, content []byte) []byte {
   256  	// Prepare protected content
   257  	protected := bytes.Buffer{}
   258  	protected.Write([]byte("harp fips encrypted signature"))
   259  	protected.WriteByte(0x00)
   260  	protected.Write(headerHash)
   261  	contentHash := sha512.Sum512(content)
   262  	protected.Write(contentHash[:])
   263  
   264  	// No error
   265  	return protected.Bytes()
   266  }
   267  
   268  func packRecipient(rand io.Reader, payloadKey *[32]byte, ephPrivKey *ecdsa.PrivateKey, peerPublicKey *ecdsa.PublicKey, preSharedKey *[preSharedKeySize]byte) (*containerv1.Recipient, error) {
   269  	// Check arguments
   270  	if payloadKey == nil {
   271  		return nil, fmt.Errorf("unable to proceed with nil payload key")
   272  	}
   273  	if ephPrivKey == nil {
   274  		return nil, fmt.Errorf("unable to proceed with nil private key")
   275  	}
   276  	if peerPublicKey == nil {
   277  		return nil, fmt.Errorf("unable to proceed with nil public key")
   278  	}
   279  
   280  	// Create identifier
   281  	recipientKey, err := deriveSharedKeyFromRecipient(peerPublicKey, ephPrivKey, preSharedKey)
   282  	if err != nil {
   283  		return nil, fmt.Errorf("unable to execute key agreement: %w", err)
   284  	}
   285  
   286  	// Calculate identifier
   287  	identifier, err := keyIdentifierFromDerivedKey(recipientKey, preSharedKey)
   288  	if err != nil {
   289  		return nil, fmt.Errorf("unable to derive key identifier: %w", err)
   290  	}
   291  
   292  	// Encrypt the payload key
   293  	encryptedKey, err := encrypt(rand, payloadKey[:], recipientKey)
   294  	if err != nil {
   295  		return nil, fmt.Errorf("unable to encrypt payload key for recipient: %w", err)
   296  	}
   297  
   298  	// Pack recipient
   299  	recipient := &containerv1.Recipient{
   300  		Identifier: identifier,
   301  		Key:        encryptedKey,
   302  	}
   303  
   304  	// Return recipient
   305  	return recipient, nil
   306  }
   307  
   308  func deriveSharedKeyFromRecipient(publicKey *ecdsa.PublicKey, privateKey *ecdsa.PrivateKey, preSharedKey *[preSharedKeySize]byte) (*[32]byte, error) {
   309  	// Compute Z - ECDH(localPrivate, remotePublic)
   310  	Z, _ := privateKey.Curve.ScalarMult(publicKey.X, publicKey.Y, privateKey.D.Bytes())
   311  
   312  	// Prepare info: ( AlgorithmID || PartyInfo || KeyLength )
   313  	fixedInfo := []byte{}
   314  	fixedInfo = append(fixedInfo, lengthPrefixedArray([]byte("A256CTR"))...)
   315  	fixedInfo = append(fixedInfo, uint32ToBytes(encryptionKeySize)...)
   316  
   317  	// HKDF-HMAC-SHA512
   318  	kdf := hkdf.New(sha512.New, Z.Bytes(), nil, fixedInfo)
   319  
   320  	var sharedSecret [encryptionKeySize]byte
   321  	if _, err := io.ReadFull(kdf, sharedSecret[:]); err != nil {
   322  		return nil, fmt.Errorf("unable to derive shared secret: %w", err)
   323  	}
   324  
   325  	// Apply psk, this will act as a second knowledge factor to allow container
   326  	// unseal
   327  	if preSharedKey != nil {
   328  		// Compute HMAC-SHA512 of the shared secret.
   329  		pskh := hmac.New(sha512.New, preSharedKey[:])
   330  		pskh.Write([]byte{0x00, 0x00, 0x00, 0x01})
   331  		pskh.Write(sharedSecret[:])
   332  		skHash := pskh.Sum(nil)
   333  		copy(sharedSecret[:], skHash[:encryptionKeySize])
   334  	}
   335  
   336  	// No error
   337  	return &sharedSecret, nil
   338  }
   339  
   340  func keyIdentifierFromDerivedKey(derivedKey *[32]byte, preSharedKey *[preSharedKeySize]byte) ([]byte, error) {
   341  	// HMAC-SHA512
   342  	h := hmac.New(sha512.New, []byte("harp signcryption box key identifier"))
   343  	if _, err := h.Write(derivedKey[:]); err != nil {
   344  		return nil, fmt.Errorf("unable to generate recipient identifier")
   345  	}
   346  
   347  	// Apply psk if specified
   348  	if preSharedKey != nil {
   349  		if _, err := h.Write(preSharedKey[:]); err != nil {
   350  			return nil, fmt.Errorf("unable to generate recipient identifier")
   351  		}
   352  	}
   353  
   354  	// Return 32 bytes truncated hash.
   355  	return h.Sum(nil)[0:encryptionKeySize], nil
   356  }
   357  
   358  func lengthPrefixedArray(value []byte) []byte {
   359  	if len(value) == 0 {
   360  		return []byte{}
   361  	}
   362  	result := make([]byte, 4)
   363  	binary.BigEndian.PutUint32(result, uint32(len(value)))
   364  
   365  	//nolint:makezero // expected behavior
   366  	return append(result, value...)
   367  }
   368  
   369  func uint32ToBytes(value uint32) []byte {
   370  	result := make([]byte, 4)
   371  	binary.BigEndian.PutUint32(result, value)
   372  
   373  	return result
   374  }
   375  
   376  func kdf(key *[32]byte, n []byte) (ek, n2, ak []byte, err error) {
   377  	// Check arguments
   378  	if key == nil {
   379  		return nil, nil, nil, errors.New("unable to derive keys from a nil seed")
   380  	}
   381  
   382  	// Prepare HKDF-HMAC-SHA384
   383  	encKDF := hkdf.New(sha512.New384, key[:], nil, append([]byte("harp-encryption-key-v2"), n...))
   384  
   385  	// Derive encryption key
   386  	tmp := make([]byte, encryptionKeySize+nonceSize)
   387  	if _, err := io.ReadFull(encKDF, tmp); err != nil {
   388  		return nil, nil, nil, fmt.Errorf("unable to generate encryption key from seed: %w", err)
   389  	}
   390  
   391  	// Split encryption key (Ek) and nonce (n2)
   392  	ek = tmp[:encryptionKeySize]
   393  	n2 = tmp[encryptionKeySize:]
   394  
   395  	// Derive authentication key
   396  	authKDF := hkdf.New(sha512.New384, key[:], nil, append([]byte("harp-auth-key-for-aead"), n...))
   397  
   398  	// Derive authentication key
   399  	ak = make([]byte, nonceSize)
   400  	if _, err := io.ReadFull(authKDF, ak); err != nil {
   401  		return nil, nil, nil, fmt.Errorf("unable to generate authentication key from seed: %w", err)
   402  	}
   403  
   404  	// No error
   405  	return ek, n2, ak, nil
   406  }
   407  
   408  func mac(ak, n, c []byte) ([]byte, error) {
   409  	// Compute pre-authenticated content
   410  	preAuth, err := pae([]byte("harp-authentication-tag-v2"), n, c)
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  
   415  	// Compute MAC
   416  	mac := hmac.New(sha512.New384, ak)
   417  
   418  	// Hash pre-authentication content
   419  	mac.Write(preAuth)
   420  
   421  	// No error
   422  	return mac.Sum(nil), nil
   423  }
   424  
   425  func pae(pieces ...[]byte) ([]byte, error) {
   426  	output := &bytes.Buffer{}
   427  
   428  	// Encode piece count
   429  	count := len(pieces)
   430  	if err := binary.Write(output, binary.LittleEndian, uint64(count)); err != nil {
   431  		return nil, err
   432  	}
   433  
   434  	// For each element
   435  	for i := range pieces {
   436  		// Encode size
   437  		if err := binary.Write(output, binary.LittleEndian, uint64(len(pieces[i]))); err != nil {
   438  			return nil, err
   439  		}
   440  
   441  		// Encode data
   442  		if _, err := output.Write(pieces[i]); err != nil {
   443  			return nil, err
   444  		}
   445  	}
   446  
   447  	// No error
   448  	return output.Bytes(), nil
   449  }