0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-23 22:27:35 -05:00
zot/pkg/meta/signatures/notation.go

168 lines
4.6 KiB
Go
Raw Normal View History

package signatures
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path"
"time"
"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/dir"
"github.com/notaryproject/notation-go/plugin"
"github.com/notaryproject/notation-go/verifier"
"github.com/notaryproject/notation-go/verifier/trustpolicy"
"github.com/notaryproject/notation-go/verifier/truststore"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
zerr "zotregistry.io/zot/errors"
)
const notationDirRelativePath = "_notation"
var notationDir = "" //nolint:gochecknoglobals
func InitNotationDir(rootDir string) error {
dir := path.Join(rootDir, notationDirRelativePath)
_, err := os.Stat(dir)
if os.IsNotExist(err) {
err = os.MkdirAll(dir, defaultDirPerms)
if err != nil {
return err
}
}
if err == nil {
notationDir = dir
}
return err
}
func GetNotationDirPath() (string, error) {
if notationDir != "" {
return notationDir, nil
}
return "", zerr.ErrSignConfigDirNotSet
}
// Equivalent function for trustpolicy.LoadDocument() but using a specific SysFS not the one returned by ConfigFS().
func LoadTrustPolicyDocument(notationDir string) (*trustpolicy.Document, error) {
jsonFile, err := dir.NewSysFS(notationDir).Open(dir.PathTrustPolicy)
if err != nil {
return nil, err
}
defer jsonFile.Close()
policyDocument := &trustpolicy.Document{}
err = json.NewDecoder(jsonFile).Decode(policyDocument)
if err != nil {
return nil, err
}
return policyDocument, nil
}
// NewFromConfig returns a verifier based on local file system.
// Equivalent function for verifier.NewFromConfig()
// but using LoadTrustPolicyDocumnt() function instead of trustpolicy.LoadDocument() function.
func NewFromConfig() (notation.Verifier, error) {
notationDir, err := GetNotationDirPath()
if err != nil {
return nil, err
}
// Load trust policy.
policyDocument, err := LoadTrustPolicyDocument(notationDir)
if err != nil {
return nil, err
}
// Load trust store.
x509TrustStore := truststore.NewX509TrustStore(dir.NewSysFS(notationDir))
return verifier.New(policyDocument, x509TrustStore,
plugin.NewCLIManager(dir.NewSysFS(path.Join(notationDir, dir.PathPlugins))))
}
func VerifyNotationSignature(
artifactDescriptor ispec.Descriptor, artifactReference string, rawSignature []byte, signatureMediaType string,
) (string, time.Time, bool, error) {
var (
date time.Time
author string
)
// If there's no signature associated with the reference.
if len(rawSignature) == 0 {
return author, date, false, notation.ErrorSignatureRetrievalFailed{
Msg: fmt.Sprintf("no signature associated with %q is provided, make sure the image was signed successfully",
artifactReference),
}
}
// Initialize verifier.
verifier, err := NewFromConfig()
if err != nil {
return author, date, false, err
}
ctx := context.Background()
// Set VerifyOptions.
opts := notation.VerifierVerifyOptions{
// ArtifactReference is important to validate registry scope format
// If "registryScopes" field from trustpolicy.json file is not wildcard then "domain:80/repo@" should not be hardcoded
ArtifactReference: "domain:80/repo@" + artifactReference,
SignatureMediaType: signatureMediaType,
PluginConfig: map[string]string{},
}
// Verify the notation signature which should be associated with the artifactDescriptor.
outcome, err := verifier.Verify(ctx, artifactDescriptor, rawSignature, opts)
if outcome.EnvelopeContent != nil {
author = outcome.EnvelopeContent.SignerInfo.CertificateChain[0].Subject.String()
if outcome.VerificationLevel == trustpolicy.LevelStrict && (err == nil ||
CheckExpiryErr(outcome.VerificationResults, outcome.EnvelopeContent.SignerInfo.CertificateChain[0].NotAfter, err)) {
expiry := outcome.EnvelopeContent.SignerInfo.SignedAttributes.Expiry
if !expiry.IsZero() && expiry.Before(outcome.EnvelopeContent.SignerInfo.CertificateChain[0].NotAfter) {
date = outcome.EnvelopeContent.SignerInfo.SignedAttributes.Expiry
} else {
date = outcome.EnvelopeContent.SignerInfo.CertificateChain[0].NotAfter
}
}
}
if err != nil {
return author, date, false, err
}
// Verification Succeeded.
return author, date, true, nil
}
func CheckExpiryErr(verificationResults []*notation.ValidationResult, notAfter time.Time, err error) bool {
for _, result := range verificationResults {
if result.Type == trustpolicy.TypeExpiry {
if errors.Is(err, result.Error) {
return true
}
} else if result.Type == trustpolicy.TypeAuthenticTimestamp {
if errors.Is(err, result.Error) && time.Now().After(notAfter) {
return true
} else {
return false
}
}
}
return false
}