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 }