// Copyright 2023 The Sigstore 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 bundle import ( "crypto/x509" "encoding/base64" "errors" "fmt" "os" "strings" "github.com/secure-systems-lab/go-securesystemslib/dsse" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" protodsse "github.com/sigstore/protobuf-specs/gen/pb-go/dsse" "golang.org/x/mod/semver" "google.golang.org/protobuf/encoding/protojson" "github.com/sigstore/sigstore-go/pkg/tlog" "github.com/sigstore/sigstore-go/pkg/verify" ) var ErrValidation = errors.New("validation error") var ErrUnsupportedMediaType = fmt.Errorf("%w: unsupported media type", ErrValidation) var ErrEmptyBundle = fmt.Errorf("%w: empty protobuf bundle", ErrValidation) var ErrMissingVerificationMaterial = fmt.Errorf("%w: missing verification material", ErrValidation) var ErrMissingBundleContent = fmt.Errorf("%w: missing bundle content", ErrValidation) var ErrUnimplemented = errors.New("unimplemented") var ErrInvalidAttestation = fmt.Errorf("%w: invalid attestation", ErrValidation) var ErrMissingEnvelope = fmt.Errorf("%w: missing valid envelope", ErrInvalidAttestation) var ErrDecodingJSON = fmt.Errorf("%w: decoding json", ErrInvalidAttestation) var ErrDecodingB64 = fmt.Errorf("%w: decoding base64", ErrInvalidAttestation) const mediaTypeBase = "application/vnd.dev.sigstore.bundle" func ErrValidationError(err error) error { return fmt.Errorf("%w: %w", ErrValidation, err) } type Bundle struct { *protobundle.Bundle hasInclusionPromise bool hasInclusionProof bool } func NewBundle(pbundle *protobundle.Bundle) (*Bundle, error) { bundle := &Bundle{ Bundle: pbundle, hasInclusionPromise: false, hasInclusionProof: false, } err := bundle.validate() if err != nil { return nil, err } return bundle, nil } // Deprecated: use Bundle instead type ProtobufBundle = Bundle // Deprecated: use NewBundle instead func NewProtobufBundle(b *protobundle.Bundle) (*ProtobufBundle, error) { return NewBundle(b) } func (b *Bundle) validate() error { bundleVersion, err := getBundleVersion(b.Bundle.MediaType) if err != nil { return fmt.Errorf("error getting bundle version: %w", err) } // if bundle version is < 0.1, return error if semver.Compare(bundleVersion, "v0.1") < 0 { return fmt.Errorf("%w: bundle version %s is not supported", ErrUnsupportedMediaType, bundleVersion) } // fetch tlog entries, as next check needs to check them for inclusion proof/promise entries, err := b.TlogEntries() if err != nil { return err } // if bundle version == v0.1, require inclusion promise if semver.Compare(bundleVersion, "v0.1") == 0 { if len(entries) > 0 && !b.hasInclusionPromise { return errors.New("inclusion promises missing in bundle (required for bundle v0.1)") } } else { // if bundle version >= v0.2, require inclusion proof if len(entries) > 0 && !b.hasInclusionProof { return errors.New("inclusion proof missing in bundle (required for bundle v0.2)") } } // if bundle version >= v0.3, require verification material to not be X.509 certificate chain (only single certificate is allowed) if semver.Compare(bundleVersion, "v0.3") >= 0 { certs := b.Bundle.VerificationMaterial.GetX509CertificateChain() if certs != nil { return errors.New("verification material cannot be X.509 certificate chain (for bundle v0.3)") } } // if bundle version is >= v0.4, return error as this version is not supported if semver.Compare(bundleVersion, "v0.4") >= 0 { return fmt.Errorf("%w: bundle version %s is not yet supported", ErrUnsupportedMediaType, bundleVersion) } err = validateBundle(b.Bundle) if err != nil { return fmt.Errorf("invalid bundle: %w", err) } return nil } // MediaTypeString returns a mediatype string for the specified bundle version. // The function returns an error if the resulting string does validate. func MediaTypeString(version string) (string, error) { if version == "" { return "", fmt.Errorf("unable to build media type string, no version defined") } var mtString string version = strings.TrimPrefix(version, "v") mtString = fmt.Sprintf("%s.v%s+json", mediaTypeBase, strings.TrimPrefix(version, "v")) if version == "0.1" || version == "0.2" { mtString = fmt.Sprintf("%s+json;version=%s", mediaTypeBase, strings.TrimPrefix(version, "v")) } if _, err := getBundleVersion(mtString); err != nil { return "", fmt.Errorf("unable to build mediatype: %w", err) } return mtString, nil } func getBundleVersion(mediaType string) (string, error) { switch mediaType { case mediaTypeBase + "+json;version=0.1": return "v0.1", nil case mediaTypeBase + "+json;version=0.2": return "v0.2", nil case mediaTypeBase + "+json;version=0.3": return "v0.3", nil } if strings.HasPrefix(mediaType, mediaTypeBase+".v") && strings.HasSuffix(mediaType, "+json") { version := strings.TrimPrefix(mediaType, mediaTypeBase+".") version = strings.TrimSuffix(version, "+json") if semver.IsValid(version) { return version, nil } return "", fmt.Errorf("%w: invalid bundle version: %s", ErrUnsupportedMediaType, version) } return "", fmt.Errorf("%w: %s", ErrUnsupportedMediaType, mediaType) } func validateBundle(b *protobundle.Bundle) error { if b == nil { return ErrEmptyBundle } if b.Content == nil { return ErrMissingBundleContent } switch b.Content.(type) { case *protobundle.Bundle_DsseEnvelope, *protobundle.Bundle_MessageSignature: default: return fmt.Errorf("invalid bundle content: bundle content must be either a message signature or dsse envelope") } if b.VerificationMaterial == nil || b.VerificationMaterial.Content == nil { return ErrMissingVerificationMaterial } switch b.VerificationMaterial.Content.(type) { case *protobundle.VerificationMaterial_PublicKey, *protobundle.VerificationMaterial_Certificate, *protobundle.VerificationMaterial_X509CertificateChain: default: return fmt.Errorf("invalid verification material content: verification material must be one of public key, x509 certificate and x509 certificate chain") } return nil } func LoadJSONFromPath(path string) (*Bundle, error) { var bundle Bundle bundle.Bundle = new(protobundle.Bundle) contents, err := os.ReadFile(path) if err != nil { return nil, err } err = bundle.UnmarshalJSON(contents) if err != nil { return nil, err } return &bundle, nil } func (b *Bundle) MarshalJSON() ([]byte, error) { return protojson.Marshal(b.Bundle) } func (b *Bundle) UnmarshalJSON(data []byte) error { b.Bundle = new(protobundle.Bundle) err := protojson.Unmarshal(data, b.Bundle) if err != nil { return err } err = b.validate() if err != nil { return err } return nil } func (b *Bundle) VerificationContent() (verify.VerificationContent, error) { if b.VerificationMaterial == nil { return nil, ErrMissingVerificationMaterial } switch content := b.VerificationMaterial.GetContent().(type) { case *protobundle.VerificationMaterial_X509CertificateChain: if content.X509CertificateChain == nil { return nil, ErrMissingVerificationMaterial } certs := content.X509CertificateChain.GetCertificates() if len(certs) == 0 || certs[0].RawBytes == nil { return nil, ErrMissingVerificationMaterial } parsedCert, err := x509.ParseCertificate(certs[0].RawBytes) if err != nil { return nil, ErrValidationError(err) } cert := &Certificate{ Certificate: parsedCert, } return cert, nil case *protobundle.VerificationMaterial_Certificate: if content.Certificate == nil || content.Certificate.RawBytes == nil { return nil, ErrMissingVerificationMaterial } parsedCert, err := x509.ParseCertificate(content.Certificate.RawBytes) if err != nil { return nil, ErrValidationError(err) } cert := &Certificate{ Certificate: parsedCert, } return cert, nil case *protobundle.VerificationMaterial_PublicKey: if content.PublicKey == nil { return nil, ErrMissingVerificationMaterial } pk := &PublicKey{ hint: content.PublicKey.Hint, } return pk, nil default: return nil, ErrMissingVerificationMaterial } } func (b *Bundle) HasInclusionPromise() bool { return b.hasInclusionPromise } func (b *Bundle) HasInclusionProof() bool { return b.hasInclusionProof } func (b *Bundle) TlogEntries() ([]*tlog.Entry, error) { if b.VerificationMaterial == nil { return nil, nil } tlogEntries := make([]*tlog.Entry, len(b.VerificationMaterial.TlogEntries)) var err error for i, entry := range b.VerificationMaterial.TlogEntries { tlogEntries[i], err = tlog.ParseEntry(entry) if err != nil { return nil, ErrValidationError(err) } if tlogEntries[i].HasInclusionPromise() { b.hasInclusionPromise = true } if tlogEntries[i].HasInclusionProof() { b.hasInclusionProof = true } } return tlogEntries, nil } func (b *Bundle) SignatureContent() (verify.SignatureContent, error) { switch content := b.Bundle.Content.(type) { //nolint:gocritic case *protobundle.Bundle_DsseEnvelope: envelope, err := parseEnvelope(content.DsseEnvelope) if err != nil { return nil, err } return envelope, nil case *protobundle.Bundle_MessageSignature: if content.MessageSignature == nil || content.MessageSignature.MessageDigest == nil { return nil, ErrMissingVerificationMaterial } return NewMessageSignature( content.MessageSignature.MessageDigest.Digest, protocommon.HashAlgorithm_name[int32(content.MessageSignature.MessageDigest.Algorithm)], content.MessageSignature.Signature, ), nil } return nil, ErrMissingVerificationMaterial } func (b *Bundle) Envelope() (*Envelope, error) { switch content := b.Bundle.Content.(type) { //nolint:gocritic case *protobundle.Bundle_DsseEnvelope: envelope, err := parseEnvelope(content.DsseEnvelope) if err != nil { return nil, err } return envelope, nil } return nil, ErrMissingVerificationMaterial } func (b *Bundle) Timestamps() ([][]byte, error) { if b.VerificationMaterial == nil { return nil, ErrMissingVerificationMaterial } signedTimestamps := make([][]byte, 0) if b.VerificationMaterial.TimestampVerificationData == nil { return signedTimestamps, nil } for _, timestamp := range b.VerificationMaterial.TimestampVerificationData.Rfc3161Timestamps { signedTimestamps = append(signedTimestamps, timestamp.SignedTimestamp) } return signedTimestamps, nil } // MinVersion returns true if the bundle version is greater than or equal to the expected version. func (b *Bundle) MinVersion(expectVersion string) bool { version, err := getBundleVersion(b.Bundle.MediaType) if err != nil { return false } if !strings.HasPrefix(expectVersion, "v") { expectVersion = "v" + expectVersion } return semver.Compare(version, expectVersion) >= 0 } func parseEnvelope(input *protodsse.Envelope) (*Envelope, error) { if input == nil { return nil, ErrMissingEnvelope } output := &dsse.Envelope{} payload := input.GetPayload() if payload == nil { return nil, ErrMissingEnvelope } output.Payload = base64.StdEncoding.EncodeToString([]byte(payload)) output.PayloadType = string(input.GetPayloadType()) output.Signatures = make([]dsse.Signature, len(input.GetSignatures())) for i, sig := range input.GetSignatures() { if sig == nil { return nil, ErrMissingEnvelope } output.Signatures[i].KeyID = sig.GetKeyid() output.Signatures[i].Sig = base64.StdEncoding.EncodeToString(sig.GetSig()) } return &Envelope{Envelope: output}, nil }
// Copyright 2023 The Sigstore 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 bundle import ( "encoding/base64" in_toto "github.com/in-toto/attestation/go/v1" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/sigstore-go/pkg/verify" "google.golang.org/protobuf/encoding/protojson" ) const IntotoMediaType = "application/vnd.in-toto+json" type MessageSignature struct { digest []byte digestAlgorithm string signature []byte } func (m *MessageSignature) Digest() []byte { return m.digest } func (m *MessageSignature) DigestAlgorithm() string { return m.digestAlgorithm } func NewMessageSignature(digest []byte, digestAlgorithm string, signature []byte) *MessageSignature { return &MessageSignature{ digest: digest, digestAlgorithm: digestAlgorithm, signature: signature, } } type Envelope struct { *dsse.Envelope } func (e *Envelope) Statement() (*in_toto.Statement, error) { if e.PayloadType != IntotoMediaType { return nil, ErrUnsupportedMediaType } var statement in_toto.Statement raw, err := e.DecodeB64Payload() if err != nil { return nil, ErrDecodingB64 } err = protojson.Unmarshal(raw, &statement) if err != nil { return nil, ErrDecodingJSON } return &statement, nil } func (e *Envelope) EnvelopeContent() verify.EnvelopeContent { return e } func (e *Envelope) RawEnvelope() *dsse.Envelope { return e.Envelope } func (m *MessageSignature) EnvelopeContent() verify.EnvelopeContent { return nil } func (e *Envelope) MessageSignatureContent() verify.MessageSignatureContent { return nil } func (m *MessageSignature) MessageSignatureContent() verify.MessageSignatureContent { return m } func (m *MessageSignature) Signature() []byte { return m.signature } func (e *Envelope) Signature() []byte { if len(e.Envelope.Signatures) == 0 { return []byte{} } sigBytes, err := base64.StdEncoding.DecodeString(e.Envelope.Signatures[0].Sig) if err != nil { return []byte{} } return sigBytes }
// Copyright 2023 The Sigstore 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 bundle import ( "crypto" "crypto/x509" "time" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/verify" ) type Certificate struct { *x509.Certificate } type PublicKey struct { hint string } func (pk PublicKey) Hint() string { return pk.hint } func (c *Certificate) CompareKey(key any, _ root.TrustedMaterial) bool { x509Key, ok := key.(*x509.Certificate) if !ok { return false } return c.Certificate.Equal(x509Key) } func (c *Certificate) ValidAtTime(t time.Time, _ root.TrustedMaterial) bool { return !(c.Certificate.NotAfter.Before(t) || c.Certificate.NotBefore.After(t)) } func (c *Certificate) GetCertificate() *x509.Certificate { return c.Certificate } func (c *Certificate) HasPublicKey() (verify.PublicKeyProvider, bool) { return PublicKey{}, false } func (pk *PublicKey) CompareKey(key any, tm root.TrustedMaterial) bool { verifier, err := tm.PublicKeyVerifier(pk.hint) if err != nil { return false } pubKey, err := verifier.PublicKey() if err != nil { return false } if equaler, ok := key.(interface{ Equal(x crypto.PublicKey) bool }); ok { return equaler.Equal(pubKey) } return false } func (pk *PublicKey) ValidAtTime(t time.Time, tm root.TrustedMaterial) bool { verifier, err := tm.PublicKeyVerifier(pk.hint) if err != nil { return false } return verifier.ValidAtTime(t) } func (pk *PublicKey) GetCertificate() *x509.Certificate { return nil } func (pk *PublicKey) HasPublicKey() (verify.PublicKeyProvider, bool) { return *pk, true }
// Copyright 2023 The Sigstore 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 tlog import ( "bytes" "context" "crypto/ecdsa" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/hex" "encoding/json" "encoding/pem" "errors" "fmt" "time" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" v1 "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" dsse_v001 "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1" hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" intoto_v002 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.2" rekorVerify "github.com/sigstore/rekor/pkg/verify" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore-go/pkg/root" ) type Entry struct { kind string version string rekorEntry types.EntryImpl logEntryAnon models.LogEntryAnon signedEntryTimestamp []byte } type RekorPayload struct { Body interface{} `json:"body"` IntegratedTime int64 `json:"integratedTime"` LogIndex int64 `json:"logIndex"` LogID string `json:"logID"` //nolint:tagliatelle } var ErrNilValue = errors.New("validation error: nil value in transaction log entry") func NewEntry(body []byte, integratedTime int64, logIndex int64, logID []byte, signedEntryTimestamp []byte, inclusionProof *models.InclusionProof) (*Entry, error) { pe, err := models.UnmarshalProposedEntry(bytes.NewReader(body), runtime.JSONConsumer()) if err != nil { return nil, err } rekorEntry, err := types.UnmarshalEntry(pe) if err != nil { return nil, err } entry := &Entry{ rekorEntry: rekorEntry, logEntryAnon: models.LogEntryAnon{ Body: base64.StdEncoding.EncodeToString(body), IntegratedTime: swag.Int64(integratedTime), LogIndex: swag.Int64(logIndex), LogID: swag.String(string(logID)), }, kind: pe.Kind(), version: rekorEntry.APIVersion(), } if len(signedEntryTimestamp) > 0 { entry.signedEntryTimestamp = signedEntryTimestamp } if inclusionProof != nil { entry.logEntryAnon.Verification = &models.LogEntryAnonVerification{ InclusionProof: inclusionProof, } } return entry, nil } // ParseEntry decodes the entry bytes to a specific entry type (types.EntryImpl). func ParseEntry(protoEntry *v1.TransparencyLogEntry) (entry *Entry, err error) { if protoEntry == nil || protoEntry.CanonicalizedBody == nil || protoEntry.IntegratedTime == 0 || protoEntry.LogIndex == 0 || protoEntry.LogId == nil || protoEntry.LogId.KeyId == nil || protoEntry.KindVersion == nil { return nil, ErrNilValue } signedEntryTimestamp := []byte{} if protoEntry.InclusionPromise != nil && protoEntry.InclusionPromise.SignedEntryTimestamp != nil { signedEntryTimestamp = protoEntry.InclusionPromise.SignedEntryTimestamp } var inclusionProof *models.InclusionProof if protoEntry.InclusionProof != nil { var hashes []string for _, v := range protoEntry.InclusionProof.Hashes { hashes = append(hashes, hex.EncodeToString(v)) } rootHash := hex.EncodeToString(protoEntry.InclusionProof.RootHash) if protoEntry.InclusionProof.Checkpoint == nil { return nil, fmt.Errorf("inclusion proof missing required checkpoint") } if protoEntry.InclusionProof.Checkpoint.Envelope == "" { return nil, fmt.Errorf("inclusion proof checkpoint empty") } inclusionProof = &models.InclusionProof{ LogIndex: swag.Int64(protoEntry.InclusionProof.LogIndex), RootHash: &rootHash, TreeSize: swag.Int64(protoEntry.InclusionProof.TreeSize), Hashes: hashes, Checkpoint: swag.String(protoEntry.InclusionProof.Checkpoint.Envelope), } } entry, err = NewEntry(protoEntry.CanonicalizedBody, protoEntry.IntegratedTime, protoEntry.LogIndex, protoEntry.LogId.KeyId, signedEntryTimestamp, inclusionProof) if err != nil { return nil, err } if entry.kind != protoEntry.KindVersion.Kind || entry.version != protoEntry.KindVersion.Version { return nil, fmt.Errorf("kind and version mismatch: %s/%s != %s/%s", entry.kind, entry.version, protoEntry.KindVersion.Kind, protoEntry.KindVersion.Version) } return entry, nil } func ValidateEntry(entry *Entry) error { switch e := entry.rekorEntry.(type) { case *dsse_v001.V001Entry: err := e.DSSEObj.Validate(strfmt.Default) if err != nil { return err } case *hashedrekord_v001.V001Entry: err := e.HashedRekordObj.Validate(strfmt.Default) if err != nil { return err } case *intoto_v002.V002Entry: err := e.IntotoObj.Validate(strfmt.Default) if err != nil { return err } default: return fmt.Errorf("unsupported entry type: %T", e) } return nil } func (entry *Entry) IntegratedTime() time.Time { return time.Unix(*entry.logEntryAnon.IntegratedTime, 0) } func (entry *Entry) Signature() []byte { switch e := entry.rekorEntry.(type) { case *dsse_v001.V001Entry: sigBytes, err := base64.StdEncoding.DecodeString(*e.DSSEObj.Signatures[0].Signature) if err != nil { return []byte{} } return sigBytes case *hashedrekord_v001.V001Entry: return e.HashedRekordObj.Signature.Content case *intoto_v002.V002Entry: sigBytes, err := base64.StdEncoding.DecodeString(string(*e.IntotoObj.Content.Envelope.Signatures[0].Sig)) if err != nil { return []byte{} } return sigBytes } return []byte{} } func (entry *Entry) PublicKey() any { var pemString []byte switch e := entry.rekorEntry.(type) { case *dsse_v001.V001Entry: pemString = []byte(*e.DSSEObj.Signatures[0].Verifier) case *hashedrekord_v001.V001Entry: pemString = []byte(e.HashedRekordObj.Signature.PublicKey.Content) case *intoto_v002.V002Entry: pemString = []byte(*e.IntotoObj.Content.Envelope.Signatures[0].PublicKey) } certBlock, _ := pem.Decode(pemString) var pk any var err error pk, err = x509.ParseCertificate(certBlock.Bytes) if err != nil { pk, err = x509.ParsePKIXPublicKey(certBlock.Bytes) if err != nil { return nil } } return pk } func (entry *Entry) LogKeyID() string { return *entry.logEntryAnon.LogID } func (entry *Entry) LogIndex() int64 { return *entry.logEntryAnon.LogIndex } func (entry *Entry) Body() any { return entry.logEntryAnon.Body } func (entry *Entry) HasInclusionPromise() bool { return entry.signedEntryTimestamp != nil } func (entry *Entry) HasInclusionProof() bool { return entry.logEntryAnon.Verification != nil } func VerifyInclusion(entry *Entry, verifier signature.Verifier) error { err := rekorVerify.VerifyInclusion(context.TODO(), &entry.logEntryAnon) if err != nil { return err } err = rekorVerify.VerifyCheckpointSignature(&entry.logEntryAnon, verifier) if err != nil { return err } return nil } func VerifySET(entry *Entry, verifiers map[string]*root.TransparencyLog) error { rekorPayload := RekorPayload{ Body: entry.logEntryAnon.Body, IntegratedTime: *entry.logEntryAnon.IntegratedTime, LogIndex: *entry.logEntryAnon.LogIndex, LogID: hex.EncodeToString([]byte(*entry.logEntryAnon.LogID)), } verifier, ok := verifiers[hex.EncodeToString([]byte(*entry.logEntryAnon.LogID))] if !ok { return errors.New("rekor log public key not found for payload") } if verifier.ValidityPeriodStart.IsZero() { return errors.New("rekor validity period start time not set") } if (verifier.ValidityPeriodStart.After(entry.IntegratedTime())) || (!verifier.ValidityPeriodEnd.IsZero() && verifier.ValidityPeriodEnd.Before(entry.IntegratedTime())) { return errors.New("rekor log public key not valid at payload integrated time") } contents, err := json.Marshal(rekorPayload) if err != nil { return fmt.Errorf("marshaling: %w", err) } canonicalized, err := jsoncanonicalizer.Transform(contents) if err != nil { return fmt.Errorf("canonicalizing: %w", err) } hash := sha256.Sum256(canonicalized) if ecdsaPublicKey, ok := verifier.PublicKey.(*ecdsa.PublicKey); !ok { return fmt.Errorf("unsupported public key type: %T", verifier.PublicKey) } else if !ecdsa.VerifyASN1(ecdsaPublicKey, hash[:], entry.signedEntryTimestamp) { return errors.New("unable to verify SET") } return nil }