From 01ed3089aa00371fb1a3de4970a8a1b4299c7de4 Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Mon, 15 Jan 2024 20:44:15 +0000 Subject: [PATCH] saving progress of refactors Signed-off-by: chaosinthecrd --- attestation/collection.go | 3 - attestation/context.go | 76 +++------ attestation/factory.go | 1 + dsse/dsse.go | 6 + dsse/dsse_test.go | 19 ++- dsse/sign.go | 9 +- dsse/verify.go | 156 ++++++++++--------- policy/policy.go | 42 +++++ run.go | 42 +++-- timestamp/{tsp.go => timestamp.go} | 83 +++++----- timestamp/{tsp_test.go => timestamp_test.go} | 2 +- verify.go | 76 +++++---- 12 files changed, 256 insertions(+), 259 deletions(-) rename timestamp/{tsp.go => timestamp.go} (66%) rename timestamp/{tsp_test.go => timestamp_test.go} (99%) diff --git a/attestation/collection.go b/attestation/collection.go index 9add8895..3fe3af4c 100644 --- a/attestation/collection.go +++ b/attestation/collection.go @@ -42,9 +42,6 @@ func NewCollection(name string, attestors []CompletedAttestor) Collection { Attestations: make([]CollectionAttestation, 0), } - //move start/stop time to collection - //todo: this is a bit of a hack, but it's the easiest way to get the start/stop time - for _, completed := range attestors { collection.Attestations = append(collection.Attestations, NewCollectionAttestation(completed)) } diff --git a/attestation/context.go b/attestation/context.go index 748829c5..baa3f967 100644 --- a/attestation/context.go +++ b/attestation/context.go @@ -117,64 +117,24 @@ func NewContext(attestors []Attestor, opts ...AttestationContextOption) (*Attest } func (ctx *AttestationContext) RunAttestors() error { - preAttestors := []Attestor{} - materialAttestors := []Attestor{} - exeucteAttestors := []Attestor{} - productAttestors := []Attestor{} - postAttestors := []Attestor{} - + attestors := make(map[RunType][]Attestor) for _, attestor := range ctx.attestors { - switch attestor.RunType() { - case PreMaterialRunType: - preAttestors = append(preAttestors, attestor) - - case MaterialRunType: - materialAttestors = append(materialAttestors, attestor) - - case ExecuteRunType: - exeucteAttestors = append(exeucteAttestors, attestor) - - case ProductRunType: - productAttestors = append(productAttestors, attestor) - - case PostProductRunType: - postAttestors = append(postAttestors, attestor) - - default: + if attestor.RunType() == "" { return ErrInvalidOption{ - Option: "attestor.RunType", + Option: "RunType", Reason: fmt.Sprintf("unknown run type %v", attestor.RunType()), } } + attestors[attestor.RunType()] = append(attestors[attestor.RunType()], attestor) } - for _, attestor := range preAttestors { - if err := ctx.runAttestor(attestor); err != nil { - return err - } - } - - for _, attestor := range materialAttestors { - if err := ctx.runAttestor(attestor); err != nil { - return err - } - } - - for _, attestor := range exeucteAttestors { - if err := ctx.runAttestor(attestor); err != nil { - return err - } - } - - for _, attestor := range productAttestors { - if err := ctx.runAttestor(attestor); err != nil { - return err - } - } - - for _, attestor := range postAttestors { - if err := ctx.runAttestor(attestor); err != nil { - return err + for _, atts := range attestors { + for _, att := range atts { + log.Infof("Starting %v attestor...", att.Name()) + if err := ctx.runAttestor(att); err != nil { + log.Errorf("Error running %v attestor: %w", att.Name(), err) + return err + } } } @@ -182,10 +142,9 @@ func (ctx *AttestationContext) RunAttestors() error { } func (ctx *AttestationContext) runAttestor(attestor Attestor) error { - log.Infof("Starting %v attestor...", attestor.Name()) startTime := time.Now() + // NOTE: Not sure if this is the right place to check for an error running the attestor - might be better to let the caller handle it if err := attestor.Attest(ctx); err != nil { - log.Errorf("Error running %v attestor: %w", attestor.Name(), err) ctx.completedAttestors = append(ctx.completedAttestors, CompletedAttestor{ Attestor: attestor, StartTime: startTime, @@ -205,17 +164,18 @@ func (ctx *AttestationContext) runAttestor(attestor Attestor) error { ctx.addMaterials(materialer) } - if producter, ok := attestor.(Producer); ok { - ctx.addProducts(producter) + if producer, ok := attestor.(Producer); ok { + ctx.addProducts(producer) } return nil } func (ctx *AttestationContext) CompletedAttestors() []CompletedAttestor { + // NOTE: Not sure if fashioning a copy of the slice is necessary here attestors := make([]CompletedAttestor, len(ctx.completedAttestors)) copy(attestors, ctx.completedAttestors) - return attestors + return ctx.completedAttestors } func (ctx *AttestationContext) WorkingDir() string { @@ -223,6 +183,7 @@ func (ctx *AttestationContext) WorkingDir() string { } func (ctx *AttestationContext) Hashes() []crypto.Hash { + // NOTE: Not sure if fashioning a copy of the slice is necessary here hashes := make([]crypto.Hash, len(ctx.hashes)) copy(hashes, ctx.hashes) return hashes @@ -242,12 +203,13 @@ func (ctx *AttestationContext) Materials() map[string]cryptoutil.DigestSet { } func (ctx *AttestationContext) Products() map[string]Product { + // NOTE: We're making a copy here and not using it prodCopy := make(map[string]Product) for k, v := range ctx.products { prodCopy[k] = v } - return ctx.products + return prodCopy } func (ctx *AttestationContext) addMaterials(materialer Materialer) { diff --git a/attestation/factory.go b/attestation/factory.go index abc0bb4d..4b59954a 100644 --- a/attestation/factory.go +++ b/attestation/factory.go @@ -41,6 +41,7 @@ type Subjecter interface { Subjects() map[string]cryptoutil.DigestSet } +// NOTE: not sure on the name of this interface, however I can't think of an alternative for now // Materialer allows attestors to communicate about materials that were observed // while the attestor executed. For example the material attestor records the hashes // of all files before a command is run. diff --git a/dsse/dsse.go b/dsse/dsse.go index 34d4796f..43e4b3c6 100644 --- a/dsse/dsse.go +++ b/dsse/dsse.go @@ -15,6 +15,7 @@ package dsse import ( + "encoding/json" "fmt" ) @@ -70,6 +71,11 @@ type SignatureTimestamp struct { Data []byte `json:"data"` } +func (e *Envelope) Write(b []byte) (int, error) { + json.Unmarshal(b, e) + return len(b), nil +} + // preauthEncode wraps the data to be signed or verified and it's type in the DSSE protocol's // pre-authentication encoding as detailed at https://github.com/secure-systems-lab/dsse/blob/master/protocol.md // PAE(type, body) = "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(body) + SP + body diff --git a/dsse/dsse_test.go b/dsse/dsse_test.go index 7a63e251..8f38f620 100644 --- a/dsse/dsse_test.go +++ b/dsse/dsse_test.go @@ -29,6 +29,7 @@ import ( "time" "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/timestamp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -244,30 +245,28 @@ func TestTimestamp(t *testing.T) { {t: time.Now().Add(128 * time.Hour)}, } - allTimestampers := make([]Timestamper, 0) - allTimestampVerifiers := make([]TimestampVerifier, 0) + allTimestampers := make([]timestamp.Timestamper, 0) for _, expected := range expectedTimestampers { allTimestampers = append(allTimestampers, expected) - allTimestampVerifiers = append(allTimestampVerifiers, expected) } for _, unexpected := range unexpectedTimestampers { allTimestampers = append(allTimestampers, unexpected) - allTimestampVerifiers = append(allTimestampVerifiers, unexpected) } env, err := Sign("dummydata", bytes.NewReader([]byte("this is some dummy data")), SignWithSigners(s), SignWithTimestampers(allTimestampers...)) require.NoError(t, err) - approvedVerifiers, err := env.Verify(VerifyWithVerifiers(v), VerifyWithRoots(root), VerifyWithIntermediates(intermediate), VerifyWithTimestampVerifiers(allTimestampVerifiers...)) + approvedVerifiers, err := env.Verify(VerifyWithVerifiers(v), VerifyWithRoots(root), VerifyWithIntermediates(intermediate), VerifyWithTimestampAuthorities(allTimestampers...)) require.NoError(t, err) assert.Len(t, approvedVerifiers, 1) - assert.Len(t, approvedVerifiers[0].PassedTimestampVerifiers, len(expectedTimestampers)) - assert.ElementsMatch(t, approvedVerifiers[0].PassedTimestampVerifiers, expectedTimestampers) + assert.Len(t, approvedVerifiers[0].TimestampAuthority, len(expectedTimestampers)) + assert.ElementsMatch(t, approvedVerifiers[0].TimestampAuthority, expectedTimestampers) } type dummyTimestamper struct { - t time.Time + t time.Time + url string } func (dt dummyTimestamper) Timestamp(context.Context, io.Reader) ([]byte, error) { @@ -286,3 +285,7 @@ func (dt dummyTimestamper) Verify(ctx context.Context, ts io.Reader, sig io.Read return dt.t, nil } + +func (dt dummyTimestamper) Url(ctx context.Context) string { + return dt.url +} diff --git a/dsse/sign.go b/dsse/sign.go index 25b958ec..7092203c 100644 --- a/dsse/sign.go +++ b/dsse/sign.go @@ -22,15 +22,12 @@ import ( "io" "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/timestamp" ) -type Timestamper interface { - Timestamp(context.Context, io.Reader) ([]byte, error) -} - type signOptions struct { signers []cryptoutil.Signer - timestampers []Timestamper + timestampers []timestamp.Timestamper } type SignOption func(*signOptions) @@ -41,7 +38,7 @@ func SignWithSigners(signers ...cryptoutil.Signer) SignOption { } } -func SignWithTimestampers(timestampers ...Timestamper) SignOption { +func SignWithTimestampers(timestampers ...timestamp.Timestamper) SignOption { return func(so *signOptions) { so.timestampers = timestampers } diff --git a/dsse/verify.go b/dsse/verify.go index b74c24ed..cb958755 100644 --- a/dsse/verify.go +++ b/dsse/verify.go @@ -18,22 +18,18 @@ import ( "bytes" "context" "crypto/x509" - "io" "time" "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/timestamp" ) -type TimestampVerifier interface { - Verify(context.Context, io.Reader, io.Reader) (time.Time, error) -} - type verificationOptions struct { - roots []*x509.Certificate - intermediates []*x509.Certificate - verifiers []cryptoutil.Verifier - threshold int - timestampVerifiers []TimestampVerifier + roots []*x509.Certificate + intermediates []*x509.Certificate + verifiers []cryptoutil.Verifier + threshold int + timestampAuthorities []timestamp.Timestamper } type VerificationOption func(*verificationOptions) @@ -62,15 +58,29 @@ func VerifyWithThreshold(threshold int) VerificationOption { } } -func VerifyWithTimestampVerifiers(verifiers ...TimestampVerifier) VerificationOption { +func VerifyWithTimestampAuthorities(timestampers ...timestamp.Timestamper) VerificationOption { return func(vo *verificationOptions) { - vo.timestampVerifiers = verifiers + vo.timestampAuthorities = timestampers } } type PassedVerifier struct { - Verifier cryptoutil.Verifier - PassedTimestampVerifiers []TimestampVerifier + Verifier cryptoutil.Verifier + TimestampAuthority TimestampInfo +} + +type TimestampInfo struct { + Timestamp time.Time + URL string +} + +func VerifyOpts(verifiers []cryptoutil.Verifier, roots []*x509.Certificate, intermediates []*x509.Certificate, timestampers []timestamp.Timestamper) []VerificationOption { + return []VerificationOption{ + VerifyWithVerifiers(verifiers...), + VerifyWithRoots(roots...), + VerifyWithIntermediates(intermediates...), + VerifyWithTimestampAuthorities(timestampers...), + } } func (e Envelope) Verify(opts ...VerificationOption) ([]PassedVerifier, error) { @@ -86,61 +96,17 @@ func (e Envelope) Verify(opts ...VerificationOption) ([]PassedVerifier, error) { return nil, ErrInvalidThreshold(options.threshold) } - pae := preauthEncode(e.PayloadType, e.Payload) if len(e.Signatures) == 0 { return nil, ErrNoSignatures{} } - matchingSigFound := false + pae := preauthEncode(e.PayloadType, e.Payload) + passedVerifiers := make([]PassedVerifier, 0) for _, sig := range e.Signatures { if sig.Certificate != nil && len(sig.Certificate) > 0 { - cert, err := cryptoutil.TryParseCertificate(sig.Certificate) - if err != nil { - continue - } - - sigIntermediates := make([]*x509.Certificate, 0) - for _, int := range sig.Intermediates { - intCert, err := cryptoutil.TryParseCertificate(int) - if err != nil { - continue - } - - sigIntermediates = append(sigIntermediates, intCert) - } - - sigIntermediates = append(sigIntermediates, options.intermediates...) - if len(options.timestampVerifiers) == 0 { - if verifier, err := verifyX509Time(cert, sigIntermediates, options.roots, pae, sig.Signature, time.Now()); err == nil { - matchingSigFound = true - passedVerifiers = append(passedVerifiers, PassedVerifier{Verifier: verifier}) - } - } else { - var passedVerifier cryptoutil.Verifier - passedTimestampVerifiers := []TimestampVerifier{} - - for _, timestampVerifier := range options.timestampVerifiers { - for _, sigTimestamp := range sig.Timestamps { - timestamp, err := timestampVerifier.Verify(context.TODO(), bytes.NewReader(sigTimestamp.Data), bytes.NewReader(sig.Signature)) - if err != nil { - continue - } - - if verifier, err := verifyX509Time(cert, sigIntermediates, options.roots, pae, sig.Signature, timestamp); err == nil { - passedVerifier = verifier - passedTimestampVerifiers = append(passedTimestampVerifiers, timestampVerifier) - } - } - } - - if len(passedTimestampVerifiers) > 0 { - matchingSigFound = true - passedVerifiers = append(passedVerifiers, PassedVerifier{ - Verifier: passedVerifier, - PassedTimestampVerifiers: passedTimestampVerifiers, - }) - } + if pvs, err := verifyX509Time(sig, pae, options); err == nil { + passedVerifiers = append(passedVerifiers, pvs...) } } @@ -148,32 +114,78 @@ func (e Envelope) Verify(opts ...VerificationOption) ([]PassedVerifier, error) { if verifier != nil { if err := verifier.Verify(bytes.NewReader(pae), sig.Signature); err == nil { passedVerifiers = append(passedVerifiers, PassedVerifier{Verifier: verifier}) - matchingSigFound = true } } } } - if !matchingSigFound { + // NOTE: Should we still pursue this logic if we verified with certificates...? + if len(passedVerifiers) == 0 { return nil, ErrNoMatchingSigs{} - } - - if len(passedVerifiers) < options.threshold { + } else if len(passedVerifiers) < options.threshold { return passedVerifiers, ErrThresholdNotMet{Theshold: options.threshold, Acutal: len(passedVerifiers)} } return passedVerifiers, nil } -func verifyX509Time(cert *x509.Certificate, sigIntermediates, roots []*x509.Certificate, pae, sig []byte, trustedTime time.Time) (cryptoutil.Verifier, error) { - verifier, err := cryptoutil.NewX509Verifier(cert, sigIntermediates, roots, trustedTime) +func verifyX509Time(sig Signature, pae []byte, opts *verificationOptions) ([]PassedVerifier, error) { + cert, err := cryptoutil.TryParseCertificate(sig.Certificate) if err != nil { return nil, err } - if err := verifier.Verify(bytes.NewReader(pae), sig); err != nil { - return nil, err + ints := make([]*x509.Certificate, 0) + if len(opts.intermediates) > 0 { + ints = append(ints, opts.intermediates...) + } + + for _, int := range sig.Intermediates { + intCert, err := cryptoutil.TryParseCertificate(int) + if err != nil { + continue + } + + ints = append(ints, intCert) + } + + var trustedTimes []TimestampInfo + if len(opts.timestampAuthorities) == 0 { + trustedTimes = append(trustedTimes, TimestampInfo{Timestamp: time.Now()}) + } else { + for _, timestampVerifier := range opts.timestampAuthorities { + for _, sigTimestamp := range sig.Timestamps { + tt, err := timestampVerifier.Verify(context.TODO(), bytes.NewReader(sigTimestamp.Data), bytes.NewReader(sig.Signature)) + if err != nil { + continue + } + trustedTimes = append(trustedTimes, TimestampInfo{URL: timestampVerifier.Url(context.TODO()), Timestamp: tt}) + } + } + } + + var pvs []PassedVerifier + for _, tt := range trustedTimes { + v, err := cryptoutil.NewX509Verifier(cert, ints, opts.roots, tt.Timestamp) + if err != nil { + return nil, err + } + + if err := v.Verify(bytes.NewReader(pae), sig.Signature); err != nil { + return nil, err + } + + if tt.URL != "" { + tt = TimestampInfo{} + } + + pvs = append(pvs, + PassedVerifier{ + Verifier: v, + TimestampAuthority: tt, + }, + ) } - return verifier, nil + return pvs, nil } diff --git a/policy/policy.go b/policy/policy.go index 3eddcd94..20a2ee1a 100644 --- a/policy/policy.go +++ b/policy/policy.go @@ -18,12 +18,15 @@ import ( "bytes" "context" "crypto/x509" + "fmt" "time" "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/dsse" "github.com/in-toto/go-witness/log" "github.com/in-toto/go-witness/source" + "github.com/in-toto/go-witness/timestamp" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -51,6 +54,45 @@ type PublicKey struct { Key []byte `json:"key"` } +// VerifyOpts returns the verify options for verifying subjects against the policy +func (p Policy) VerifyOpts() ([]dsse.VerificationOption, error) { + pubKeysById, err := p.PublicKeyVerifiers() + if err != nil { + return nil, fmt.Errorf("failed to get public keys from policy: %w", err) + } + + pubKeys := make([]cryptoutil.Verifier, 0) + for _, pubkey := range pubKeysById { + pubKeys = append(pubKeys, pubkey) + } + + trustBundlesById, err := p.TrustBundles() + if err != nil { + return nil, fmt.Errorf("failed to load policy trust bundles: %w", err) + } + + roots := make([]*x509.Certificate, 0) + intermediates := make([]*x509.Certificate, 0) + for _, trustBundle := range trustBundlesById { + roots = append(roots, trustBundle.Root) + intermediates = append(intermediates, intermediates...) + } + + timestampAuthoritiesById, err := p.TimestampAuthorityTrustBundles() + if err != nil { + return nil, fmt.Errorf("failed to load policy timestamp authorities: %w", err) + } + + timestampers := make([]timestamp.Timestamper, 0) + for _, timestampAuthority := range timestampAuthoritiesById { + certs := []*x509.Certificate{timestampAuthority.Root} + certs = append(certs, timestampAuthority.Intermediates...) + timestampers = append(timestampers, timestamp.NewTimestampAuthority(timestamp.VerifyWithCertChain(certs))) + } + + return dsse.VerifyOpts(pubKeys, roots, intermediates, timestampers), nil +} + // PublicKeyVerifiers returns verifiers for each of the policy's embedded public keys grouped by the key's ID func (p Policy) PublicKeyVerifiers() (map[string]cryptoutil.Verifier, error) { verifiers := make(map[string]cryptoutil.Verifier) diff --git a/run.go b/run.go index b6ccd2ca..8b6be89d 100644 --- a/run.go +++ b/run.go @@ -24,7 +24,7 @@ import ( "github.com/in-toto/go-witness/attestation/git" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/dsse" - "github.com/in-toto/go-witness/intoto" + "github.com/in-toto/go-witness/timestamp" ) type runOptions struct { @@ -32,7 +32,8 @@ type runOptions struct { signer cryptoutil.Signer attestors []attestation.Attestor attestationOpts []attestation.AttestationContextOption - timestampers []dsse.Timestamper + // NOTE: timestampers might be better named as something else + timestampers []timestamp.Timestamper } type RunOption func(ro *runOptions) @@ -49,7 +50,8 @@ func RunWithAttestationOpts(opts ...attestation.AttestationContextOption) RunOpt } } -func RunWithTimestampers(ts ...dsse.Timestamper) RunOption { +// NOTE: Rename function appropriately +func RunWithTimestampers(ts ...timestamp.Timestamper) RunOption { return func(ro *runOptions) { ro.timestampers = ts } @@ -64,7 +66,7 @@ func Run(stepName string, signer cryptoutil.Signer, opts ...RunOption) (RunResul ro := runOptions{ stepName: stepName, signer: signer, - attestors: []attestation.Attestor{environment.New(), git.New()}, + attestors: loadDefaultAttestors(), } for _, opt := range opts { @@ -86,7 +88,12 @@ func Run(stepName string, signer cryptoutil.Signer, opts ...RunOption) (RunResul } result.Collection = attestation.NewCollection(ro.stepName, runCtx.CompletedAttestors()) - result.SignedEnvelope, err = signCollection(result.Collection, dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) + data, err := json.Marshal(&result.Collection) + if err != nil { + return result, err + } + + err = Sign(bytes.NewReader(data), attestation.CollectionType, &result.SignedEnvelope, dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) if err != nil { return result, fmt.Errorf("failed to sign collection: %w", err) } @@ -94,6 +101,10 @@ func Run(stepName string, signer cryptoutil.Signer, opts ...RunOption) (RunResul return result, nil } +func loadDefaultAttestors() []attestation.Attestor { + return []attestation.Attestor{environment.New(), git.New()} +} + func validateRunOpts(ro runOptions) error { if ro.stepName == "" { return fmt.Errorf("step name is required") @@ -103,24 +114,9 @@ func validateRunOpts(ro runOptions) error { return fmt.Errorf("signer is required") } - return nil -} - -func signCollection(collection attestation.Collection, opts ...dsse.SignOption) (dsse.Envelope, error) { - data, err := json.Marshal(&collection) - if err != nil { - return dsse.Envelope{}, err - } - - stmt, err := intoto.NewStatement(attestation.CollectionType, data, collection.Subjects()) - if err != nil { - return dsse.Envelope{}, err + if len(ro.attestors) == 0 { + return fmt.Errorf("at least one attestor is required") } - stmtJson, err := json.Marshal(&stmt) - if err != nil { - return dsse.Envelope{}, err - } - - return dsse.Sign(intoto.PayloadType, bytes.NewReader(stmtJson), opts...) + return nil } diff --git a/timestamp/tsp.go b/timestamp/timestamp.go similarity index 66% rename from timestamp/tsp.go rename to timestamp/timestamp.go index e8a1e596..97bcf5e6 100644 --- a/timestamp/tsp.go +++ b/timestamp/timestamp.go @@ -29,34 +29,51 @@ import ( "github.com/in-toto/go-witness/cryptoutil" ) -type TSPTimestamper struct { +// Timestamper provides the way to +type Timestamper interface { + Timestamp(context.Context, io.Reader) ([]byte, error) + Verify(context.Context, io.Reader, io.Reader) (time.Time, error) + Url(context.Context) string +} + +type TimestampAuthority struct { url string hash crypto.Hash requestCertificate bool + certChain *x509.CertPool } -type TSPTimestamperOption func(*TSPTimestamper) +type TimestampAuthorityOption func(*TimestampAuthority) -func TimestampWithUrl(url string) TSPTimestamperOption { - return func(t *TSPTimestamper) { +func TimestampWithURL(url string) TimestampAuthorityOption { + return func(t *TimestampAuthority) { t.url = url } } -func TimestampWithHash(h crypto.Hash) TSPTimestamperOption { - return func(t *TSPTimestamper) { +func TimestampWithHash(h crypto.Hash) TimestampAuthorityOption { + return func(t *TimestampAuthority) { t.hash = h } } -func TimestampWithRequestCertificate(requestCertificate bool) TSPTimestamperOption { - return func(t *TSPTimestamper) { +func TimestampWithRequestCertificate(requestCertificate bool) TimestampAuthorityOption { + return func(t *TimestampAuthority) { t.requestCertificate = requestCertificate } } -func NewTimestamper(opts ...TSPTimestamperOption) TSPTimestamper { - t := TSPTimestamper{ +func VerifyWithCertChain(certs []*x509.Certificate) TimestampAuthorityOption { + return func(t *TimestampAuthority) { + t.certChain = x509.NewCertPool() + for _, cert := range certs { + t.certChain.AddCert(cert) + } + } +} + +func NewTimestampAuthority(opts ...TimestampAuthorityOption) TimestampAuthority { + t := TimestampAuthority{ hash: crypto.SHA256, requestCertificate: true, } @@ -68,7 +85,7 @@ func NewTimestamper(opts ...TSPTimestamperOption) TSPTimestamper { return t } -func (t TSPTimestamper) Timestamp(ctx context.Context, r io.Reader) ([]byte, error) { +func (t TimestampAuthority) Timestamp(ctx context.Context, r io.Reader) ([]byte, error) { tsq, err := timestamp.CreateRequest(r, ×tamp.RequestOptions{ Hash: t.hash, Certificates: t.requestCertificate, @@ -109,41 +126,7 @@ func (t TSPTimestamper) Timestamp(ctx context.Context, r io.Reader) ([]byte, err return timestamp.RawToken, nil } -type TSPVerifier struct { - certChain *x509.CertPool - hash crypto.Hash -} - -type TSPVerifierOption func(*TSPVerifier) - -func VerifyWithCerts(certs []*x509.Certificate) TSPVerifierOption { - return func(t *TSPVerifier) { - t.certChain = x509.NewCertPool() - for _, cert := range certs { - t.certChain.AddCert(cert) - } - } -} - -func VerifyWithHash(h crypto.Hash) TSPVerifierOption { - return func(t *TSPVerifier) { - t.hash = h - } -} - -func NewVerifier(opts ...TSPVerifierOption) TSPVerifier { - v := TSPVerifier{ - hash: crypto.SHA256, - } - - for _, opt := range opts { - opt(&v) - } - - return v -} - -func (v TSPVerifier) Verify(ctx context.Context, tsrData, signedData io.Reader) (time.Time, error) { +func (t TimestampAuthority) Verify(ctx context.Context, tsrData, signedData io.Reader) (time.Time, error) { tsrBytes, err := io.ReadAll(tsrData) if err != nil { return time.Time{}, err @@ -154,7 +137,7 @@ func (v TSPVerifier) Verify(ctx context.Context, tsrData, signedData io.Reader) return time.Time{}, err } - hashedData, err := cryptoutil.Digest(signedData, v.hash) + hashedData, err := cryptoutil.Digest(signedData, t.hash) if err != nil { return time.Time{}, err } @@ -168,9 +151,13 @@ func (v TSPVerifier) Verify(ctx context.Context, tsrData, signedData io.Reader) return time.Time{}, err } - if err := p7.VerifyWithChain(v.certChain); err != nil { + if err := p7.VerifyWithChain(t.certChain); err != nil { return time.Time{}, err } return ts.Time, nil } + +func (t TimestampAuthority) Url(ctx context.Context) string { + return t.url +} diff --git a/timestamp/tsp_test.go b/timestamp/timestamp_test.go similarity index 99% rename from timestamp/tsp_test.go rename to timestamp/timestamp_test.go index 60698931..34ea612c 100644 --- a/timestamp/tsp_test.go +++ b/timestamp/timestamp_test.go @@ -103,7 +103,7 @@ UKqK1drk/NAJBzewdXUh ) func TestTSP(t *testing.T) { - ts := NewTimestamper(TimestampWithUrl(tsaUrl)) + ts := NewTimestampAuthority(TimestampWithURL(tsaUrl)) payload := []byte("some data to timestamp") resp, err := ts.Timestamp(context.Background(), bytes.NewReader(payload)) require.NoError(t, err) diff --git a/verify.go b/verify.go index bb20df6b..66a38203 100644 --- a/verify.go +++ b/verify.go @@ -40,10 +40,13 @@ func VerifySignature(r io.Reader, verifiers ...cryptoutil.Verifier) (dsse.Envelo } type verifyOptions struct { - policyEnvelope dsse.Envelope - policyVerifiers []cryptoutil.Verifier - collectionSource source.Sourcer - subjectDigests []string + policyEnvelope dsse.Envelope + policyTimestampers []timestamp.Timestamper + policyRoots []*x509.Certificate + policyIntermediates []*x509.Certificate + policyVerifiers []cryptoutil.Verifier + collectionSource source.Sourcer + subjectDigests []string } type VerifyOption func(*verifyOptions) @@ -64,6 +67,24 @@ func VerifyWithCollectionSource(source source.Sourcer) VerifyOption { } } +func VerifyWithPolicyRoots(roots []*x509.Certificate) VerifyOption { + return func(vo *verifyOptions) { + vo.policyRoots = roots + } +} + +func VerifyWithPolicyIntermediates(intermediates []*x509.Certificate) VerifyOption { + return func(vo *verifyOptions) { + vo.policyIntermediates = intermediates + } +} + +func VerifyWithPolicyTimestampers(timestampers []timestamp.Timestamper) VerifyOption { + return func(vo *verifyOptions) { + vo.policyTimestampers = timestampers + } +} + // Verify verifies a set of attestations against a provided policy. The set of attestations that satisfy the policy will be returned // if verifiation is successful. func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers []cryptoutil.Verifier, opts ...VerifyOption) (map[string][]source.VerifiedCollection, error) { @@ -76,8 +97,8 @@ func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers [ opt(&vo) } - if _, err := vo.policyEnvelope.Verify(dsse.VerifyWithVerifiers(vo.policyVerifiers...)); err != nil { - return nil, fmt.Errorf("could not verify policy: %w", err) + if _, err := vo.policyEnvelope.Verify(dsseOpts(&vo)...); err != nil { + return nil, fmt.Errorf("failed to verify policy: %w", err) } pol := policy.Policy{} @@ -85,47 +106,16 @@ func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers [ return nil, fmt.Errorf("failed to unmarshal policy from envelope: %w", err) } - pubKeysById, err := pol.PublicKeyVerifiers() - if err != nil { - return nil, fmt.Errorf("failed to get pulic keys from policy: %w", err) - } - - pubkeys := make([]cryptoutil.Verifier, 0) - for _, pubkey := range pubKeysById { - pubkeys = append(pubkeys, pubkey) - } - - trustBundlesById, err := pol.TrustBundles() - if err != nil { - return nil, fmt.Errorf("failed to load policy trust bundles: %w", err) - } - - roots := make([]*x509.Certificate, 0) - intermediates := make([]*x509.Certificate, 0) - for _, trustBundle := range trustBundlesById { - roots = append(roots, trustBundle.Root) - intermediates = append(intermediates, intermediates...) - } - - timestampAuthoritiesById, err := pol.TimestampAuthorityTrustBundles() + sourceOpts, err := pol.VerifyOpts() if err != nil { - return nil, fmt.Errorf("failed to load policy timestamp authorities: %w", err) - } - - timestampVerifiers := make([]dsse.TimestampVerifier, 0) - for _, timestampAuthority := range timestampAuthoritiesById { - certs := []*x509.Certificate{timestampAuthority.Root} - certs = append(certs, timestampAuthority.Intermediates...) - timestampVerifiers = append(timestampVerifiers, timestamp.NewVerifier(timestamp.VerifyWithCerts(certs))) + return nil, fmt.Errorf("failed to prepare verification options from the policy %w", err) } verifiedSource := source.NewVerifiedSource( vo.collectionSource, - dsse.VerifyWithVerifiers(pubkeys...), - dsse.VerifyWithRoots(roots...), - dsse.VerifyWithIntermediates(intermediates...), - dsse.VerifyWithTimestampVerifiers(timestampVerifiers...), + sourceOpts..., ) + accepted, err := pol.Verify(ctx, policy.WithSubjectDigests(vo.subjectDigests), policy.WithVerifiedSource(verifiedSource)) if err != nil { return nil, fmt.Errorf("failed to verify policy: %w", err) @@ -133,3 +123,7 @@ func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers [ return accepted, nil } + +func dsseOpts(vo *verifyOptions) []dsse.VerificationOption { + return dsse.VerifyOpts(vo.policyVerifiers, vo.policyRoots, vo.policyIntermediates, vo.policyTimestampers) +}