zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/imagetrust/cosign.go (about)

     1  //go:build imagetrust
     2  // +build imagetrust
     3  
     4  package imagetrust
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"crypto"
    10  	"encoding/base64"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"path"
    15  
    16  	"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
    17  	"github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
    18  	godigest "github.com/opencontainers/go-digest"
    19  	"github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key"
    20  	sigs "github.com/sigstore/cosign/v2/pkg/signature"
    21  	"github.com/sigstore/sigstore/pkg/cryptoutils"
    22  	sigstoreSigs "github.com/sigstore/sigstore/pkg/signature"
    23  	"github.com/sigstore/sigstore/pkg/signature/options"
    24  
    25  	zerr "zotregistry.dev/zot/errors"
    26  )
    27  
    28  const cosignDirRelativePath = "_cosign"
    29  
    30  type PublicKeyLocalStorage struct {
    31  	cosignDir string
    32  }
    33  
    34  type PublicKeyAWSStorage struct {
    35  	secretsManagerClient SecretsManagerClient
    36  	secretsManagerCache  SecretsManagerCache
    37  }
    38  
    39  type publicKeyStorage interface {
    40  	StorePublicKey(name godigest.Digest, publicKeyContent []byte) error
    41  	GetPublicKeyVerifier(name string) (sigstoreSigs.Verifier, []byte, error)
    42  	GetPublicKeys() ([]string, error)
    43  }
    44  
    45  func NewPublicKeyLocalStorage(rootDir string) (*PublicKeyLocalStorage, error) {
    46  	dir := path.Join(rootDir, cosignDirRelativePath)
    47  
    48  	_, err := os.Stat(dir)
    49  	if os.IsNotExist(err) {
    50  		err = os.MkdirAll(dir, defaultDirPerms)
    51  		if err != nil {
    52  			return nil, err
    53  		}
    54  	}
    55  
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	return &PublicKeyLocalStorage{
    61  		cosignDir: dir,
    62  	}, nil
    63  }
    64  
    65  func NewPublicKeyAWSStorage(
    66  	secretsManagerClient SecretsManagerClient, secretsManagerCache SecretsManagerCache,
    67  ) *PublicKeyAWSStorage {
    68  	return &PublicKeyAWSStorage{
    69  		secretsManagerClient: secretsManagerClient,
    70  		secretsManagerCache:  secretsManagerCache,
    71  	}
    72  }
    73  
    74  func (local *PublicKeyLocalStorage) GetCosignDirPath() (string, error) {
    75  	if local.cosignDir != "" {
    76  		return local.cosignDir, nil
    77  	}
    78  
    79  	return "", zerr.ErrSignConfigDirNotSet
    80  }
    81  
    82  func VerifyCosignSignature(
    83  	cosignStorage publicKeyStorage, repo string, digest godigest.Digest, signatureKey string, layerContent []byte,
    84  ) (string, bool, error) {
    85  	publicKeys, err := cosignStorage.GetPublicKeys()
    86  	if err != nil {
    87  		return "", false, err
    88  	}
    89  
    90  	for _, publicKey := range publicKeys {
    91  		// cosign verify the image
    92  		pubKeyVerifier, pubKeyContent, err := cosignStorage.GetPublicKeyVerifier(publicKey)
    93  		if err != nil {
    94  			continue
    95  		}
    96  
    97  		pkcs11Key, ok := pubKeyVerifier.(*pkcs11key.Key)
    98  		if ok {
    99  			defer pkcs11Key.Close()
   100  		}
   101  
   102  		verifier := pubKeyVerifier
   103  
   104  		b64sig := signatureKey
   105  
   106  		signature, err := base64.StdEncoding.DecodeString(b64sig)
   107  		if err != nil {
   108  			continue
   109  		}
   110  
   111  		compressed := io.NopCloser(bytes.NewReader(layerContent))
   112  
   113  		payload, err := io.ReadAll(compressed)
   114  		if err != nil {
   115  			continue
   116  		}
   117  
   118  		err = verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload),
   119  			options.WithContext(context.Background()))
   120  
   121  		if err == nil {
   122  			return string(pubKeyContent), true, nil
   123  		}
   124  	}
   125  
   126  	return "", false, nil
   127  }
   128  
   129  func (local *PublicKeyLocalStorage) GetPublicKeyVerifier(fileName string) (sigstoreSigs.Verifier, []byte, error) {
   130  	cosignDir, err := local.GetCosignDirPath()
   131  	if err != nil {
   132  		return nil, []byte{}, err
   133  	}
   134  
   135  	ctx := context.Background()
   136  	keyRef := path.Join(cosignDir, fileName)
   137  	hashAlgorithm := crypto.SHA256
   138  
   139  	pubKeyContent, err := os.ReadFile(keyRef)
   140  	if err != nil {
   141  		return nil, nil, err
   142  	}
   143  
   144  	pubKey, err := sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, hashAlgorithm)
   145  	if err != nil {
   146  		return nil, nil, err
   147  	}
   148  
   149  	return pubKey, pubKeyContent, nil
   150  }
   151  
   152  func (cloud *PublicKeyAWSStorage) GetPublicKeyVerifier(secretName string) (sigstoreSigs.Verifier, []byte, error) {
   153  	hashAlgorithm := crypto.SHA256
   154  
   155  	// get key
   156  	raw, err := cloud.secretsManagerCache.GetSecretString(secretName)
   157  	if err != nil {
   158  		return nil, nil, err
   159  	}
   160  
   161  	rawDecoded, err := base64.StdEncoding.DecodeString(raw)
   162  	if err != nil {
   163  		return nil, nil, err
   164  	}
   165  
   166  	// PEM encoded file.
   167  	key, err := cryptoutils.UnmarshalPEMToPublicKey(rawDecoded)
   168  	if err != nil {
   169  		return nil, nil, err
   170  	}
   171  
   172  	pubKey, err := sigstoreSigs.LoadVerifier(key, hashAlgorithm)
   173  	if err != nil {
   174  		return nil, nil, err
   175  	}
   176  
   177  	return pubKey, rawDecoded, nil
   178  }
   179  
   180  func (local *PublicKeyLocalStorage) GetPublicKeys() ([]string, error) {
   181  	cosignDir, err := local.GetCosignDirPath()
   182  	if err != nil {
   183  		return []string{}, err
   184  	}
   185  
   186  	files, err := os.ReadDir(cosignDir)
   187  	if err != nil {
   188  		return []string{}, err
   189  	}
   190  
   191  	publicKeys := []string{}
   192  	for _, file := range files {
   193  		publicKeys = append(publicKeys, file.Name())
   194  	}
   195  
   196  	return publicKeys, nil
   197  }
   198  
   199  func (cloud *PublicKeyAWSStorage) GetPublicKeys() ([]string, error) {
   200  	ctx := context.Background()
   201  	listSecretsInput := secretsmanager.ListSecretsInput{
   202  		Filters: []types.Filter{
   203  			{
   204  				Key:    types.FilterNameStringTypeDescription,
   205  				Values: []string{"cosign public key"},
   206  			},
   207  		},
   208  	}
   209  
   210  	secrets, err := cloud.secretsManagerClient.ListSecrets(ctx, &listSecretsInput)
   211  	if err != nil {
   212  		return []string{}, err
   213  	}
   214  
   215  	publicKeys := []string{}
   216  
   217  	for _, secret := range secrets.SecretList {
   218  		publicKeys = append(publicKeys, *(secret.Name))
   219  	}
   220  
   221  	return publicKeys, nil
   222  }
   223  
   224  func UploadPublicKey(cosignStorage publicKeyStorage, publicKeyContent []byte) error {
   225  	// validate public key
   226  	if ok, err := validatePublicKey(publicKeyContent); !ok {
   227  		return err
   228  	}
   229  
   230  	name := godigest.FromBytes(publicKeyContent)
   231  
   232  	return cosignStorage.StorePublicKey(name, publicKeyContent)
   233  }
   234  
   235  func (local *PublicKeyLocalStorage) StorePublicKey(name godigest.Digest, publicKeyContent []byte) error {
   236  	// add public key to "{rootDir}/_cosign/{name.pub}"
   237  	cosignDir, err := local.GetCosignDirPath()
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	// store public key
   243  	publicKeyPath := path.Join(cosignDir, name.String())
   244  
   245  	return os.WriteFile(publicKeyPath, publicKeyContent, defaultFilePerms)
   246  }
   247  
   248  func (cloud *PublicKeyAWSStorage) StorePublicKey(name godigest.Digest, publicKeyContent []byte) error {
   249  	n := name.Encoded()
   250  	description := "cosign public key"
   251  	secret := base64.StdEncoding.EncodeToString(publicKeyContent)
   252  	secretInputParam := &secretsmanager.CreateSecretInput{
   253  		Name:         &n,
   254  		Description:  &description,
   255  		SecretString: &secret,
   256  	}
   257  
   258  	_, err := cloud.secretsManagerClient.CreateSecret(context.Background(), secretInputParam)
   259  	if err != nil && IsResourceExistsException(err) {
   260  		return nil
   261  	}
   262  
   263  	return err
   264  }
   265  
   266  func validatePublicKey(publicKeyContent []byte) (bool, error) {
   267  	_, err := cryptoutils.UnmarshalPEMToPublicKey(publicKeyContent)
   268  	if err != nil {
   269  		return false, fmt.Errorf("%w: %w", zerr.ErrInvalidPublicKeyContent, err)
   270  	}
   271  
   272  	return true, nil
   273  }