/* Copyright Docker attest authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package attestation import ( "context" "crypto" "crypto/x509" "fmt" "github.com/docker/attest/signerverifier" "github.com/docker/attest/tlog" "github.com/docker/attest/tuf" "github.com/secure-systems-lab/go-securesystemslib/dsse" ) func WithTUFDownloader(tufDownloader tuf.Downloader) func(*verifier) { return func(r *verifier) { r.tufDownloader = tufDownloader } } type SignatureVerifierFactory func(ctx context.Context, publicKey crypto.PublicKey, opts *VerifyOptions) (dsse.Verifier, error) func WithSignatureVerifierFactory(factory SignatureVerifierFactory) func(*verifier) { return func(r *verifier) { r.signatureVerifierFactory = factory } } func WithLogVerifierFactory(factory LogVerifierFactory) func(*verifier) { return func(r *verifier) { r.logVerifierFactory = factory } } type LogVerifierFactory func(ctx context.Context, opts *VerifyOptions) (tlog.TransparencyLog, error) func NewVerfier(options ...func(*verifier)) (Verifier, error) { verifier := &verifier{} for _, opt := range options { opt(verifier) } return verifier, nil } type Verifier interface { GetSignatureVerifier(ctx context.Context, publicKey crypto.PublicKey, opts *VerifyOptions) (dsse.Verifier, error) GetLogVerifier(ctx context.Context, opts *VerifyOptions) (tlog.TransparencyLog, error) VerifySignature(ctx context.Context, publicKey crypto.PublicKey, data []byte, signature []byte, opts *VerifyOptions) error VerifyLog(ctx context.Context, keyMeta *KeyMetadata, data []byte, sig *Signature, opts *VerifyOptions) error } // ensure it has all the necessary methods. var _ Verifier = (*verifier)(nil) type verifier struct { tufDownloader tuf.Downloader signatureVerifierFactory SignatureVerifierFactory logVerifierFactory LogVerifierFactory } // GetLogVerifier implements Verifier. func (v *verifier) GetLogVerifier(ctx context.Context, opts *VerifyOptions) (tlog.TransparencyLog, error) { if v.logVerifierFactory != nil { return v.logVerifierFactory(ctx, opts) } if opts.SkipTL { return nil, nil } // TODO support other transparency logs var transparencyLog tlog.TransparencyLog switch opts.TransparencyLog { case "", RekorTransparencyLogKind: var err error transparencyLog, err = tlog.NewRekorLog(tlog.WithTUFDownloader(v.tufDownloader)) if err != nil { return nil, fmt.Errorf("error failed to create rekor verifier: %w", err) } default: return nil, fmt.Errorf("unsupported transparency log: %s", opts.TransparencyLog) } return transparencyLog, nil } // GetSignatureVerifier implements Verifier. func (v *verifier) GetSignatureVerifier(ctx context.Context, publicKey crypto.PublicKey, opts *VerifyOptions) (dsse.Verifier, error) { if v.signatureVerifierFactory != nil { return v.signatureVerifierFactory(ctx, publicKey, opts) } // TODO: use details from opts to decide which algorithm to use here ecdsaVerifier, err := signerverifier.NewECDSAVerifier(publicKey) if err != nil { return nil, fmt.Errorf("error failed to create ecdsa verifier: %w", err) } return ecdsaVerifier, nil } func (v *verifier) VerifySignature(ctx context.Context, publicKey crypto.PublicKey, data []byte, signature []byte, opts *VerifyOptions) error { sigVerifier, err := v.GetSignatureVerifier(ctx, publicKey, opts) if err != nil { return fmt.Errorf("error failed to get verifier: %w", err) } return sigVerifier.Verify(ctx, data, signature) } func (v *verifier) VerifyLog(ctx context.Context, keyMeta *KeyMetadata, encPayload []byte, sig *Signature, opts *VerifyOptions) error { if opts.SkipTL { return nil } if sig.Extension == nil || sig.Extension.Kind == "" { return fmt.Errorf("error missing signature extension") } if sig.Extension.Kind != DockerDSSEExtKind { return fmt.Errorf("error unsupported signature extension kind: %s", sig.Extension.Kind) } transparencyLog, err := v.GetLogVerifier(ctx, opts) if err != nil { return fmt.Errorf("error failed to get transparency log verifier: %w", err) } if transparencyLog == nil { return fmt.Errorf("error missing transparency log verifier") } // verify TL entry payload publicKey, err := keyMeta.ParsedKey() if err != nil { return fmt.Errorf("error failed to parse public key: %w", err) } encodedPub, err := x509.MarshalPKIXPublicKey(publicKey) if err != nil { return fmt.Errorf("error failed to marshal public key: %w", err) } integratedTime, err := transparencyLog.VerifyEntry(ctx, sig.Extension.Ext.TL, encPayload, encodedPub) if err != nil { return fmt.Errorf("TL entry failed verification: %w", err) } if integratedTime.Before(keyMeta.From) { return fmt.Errorf("key %s was not yet valid at TL log time %s (key valid from %s)", keyMeta.ID, integratedTime, keyMeta.From) } if keyMeta.To != nil && !integratedTime.Before(*keyMeta.To) { return fmt.Errorf("key %s was already %s at TL log time %s (key %s at %s)", keyMeta.ID, keyMeta.Status, integratedTime, keyMeta.Status, *keyMeta.To) } return nil }