Files
attest/attestation/verifier.go
James Carnegie 05caa959c4 Use a Factory to create signature verifiers at policy evaluation time (#165)
* Make verifiers composable

* fix: remove unused code and improve signature verification logic

* fix: simplify abstractions and renamed some things

* fix: improve tl interface.

* fix: sort out signer/verifier
2024-09-18 13:34:10 +01:00

144 lines
4.8 KiB
Go

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
}