From 6eb0daa42c73f8df58b79f355f23f6b1aa159922 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Tue, 23 May 2023 08:16:58 -0400 Subject: [PATCH 1/7] add client method to generate TLE protobuf Signed-off-by: Bob Callaway --- cmd/rekor-cli/app/format/wrap.go | 2 +- cmd/rekor-cli/app/get.go | 4 ++ cmd/rekor-cli/app/pflags.go | 2 +- go.mod | 1 + go.sum | 2 + pkg/client/rekor_client.go | 70 ++++++++++++++++++++++++++++++++ 6 files changed, 79 insertions(+), 2 deletions(-) diff --git a/cmd/rekor-cli/app/format/wrap.go b/cmd/rekor-cli/app/format/wrap.go index 5e81c37f6..95eaf1805 100644 --- a/cmd/rekor-cli/app/format/wrap.go +++ b/cmd/rekor-cli/app/format/wrap.go @@ -44,7 +44,7 @@ func WrapCmd(f formatCmd) CobraCmd { } else { fmt.Println(toJSON(s)) } - case "json": + case "json", "bundle": fmt.Println(toJSON(obj)) } } diff --git a/cmd/rekor-cli/app/get.go b/cmd/rekor-cli/app/get.go index 6a6181341..3f8aece22 100644 --- a/cmd/rekor-cli/app/get.go +++ b/cmd/rekor-cli/app/get.go @@ -203,6 +203,10 @@ func compareEntryUUIDs(requestEntryUUID string, responseEntryUUID string) error } func parseEntry(uuid string, e models.LogEntryAnon) (interface{}, error) { + if viper.GetString("format") == "bundle" { + return client.GenerateTransparencyLogEntry(e) + } + b, err := base64.StdEncoding.DecodeString(e.Body.(string)) if err != nil { return nil, err diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index 1f147cae8..befeeb181 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -111,7 +111,7 @@ func initializePFlagMap() { }, formatFlag: func() pflag.Value { // this validates the output format requested - return valueFactory(formatFlag, validateString("required,oneof=json default"), "") + return valueFactory(formatFlag, validateString("required,oneof=json default bundle"), "") }, timeoutFlag: func() pflag.Value { // this validates the timeout is >= 0 diff --git a/go.mod b/go.mod index aadc3448c..0660b3b07 100644 --- a/go.mod +++ b/go.mod @@ -72,6 +72,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/google/s2a-go v0.1.3 // indirect github.com/jellydator/ttlcache/v3 v3.0.1 // indirect + github.com/sigstore/protobuf-specs v0.1.0 // indirect go.opentelemetry.io/otel v1.14.0 // indirect go.opentelemetry.io/otel/trace v1.14.0 // indirect k8s.io/klog/v2 v2.100.1 // indirect diff --git a/go.sum b/go.sum index 77fdd29a5..ce75df753 100644 --- a/go.sum +++ b/go.sum @@ -1818,6 +1818,8 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sigstore/protobuf-specs v0.1.0 h1:X0l/E2C2c79t/rI/lmSu8WAoKWsQtMqDzAMiDdEMGr8= +github.com/sigstore/protobuf-specs v0.1.0/go.mod h1:5shUCxf82hGnjUEFVWiktcxwzdtn6EfeeJssxZ5Q5HE= github.com/sigstore/sigstore v1.6.4 h1:jH4AzR7qlEH/EWzm+opSpxCfuUcjHL+LJPuQE7h40WE= github.com/sigstore/sigstore v1.6.4/go.mod h1:pjR64lBxnjoSrAr+Ydye/FV73IfrgtoYlAI11a8xMfA= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= diff --git a/pkg/client/rekor_client.go b/pkg/client/rekor_client.go index 601dd9d32..43dbfed13 100644 --- a/pkg/client/rekor_client.go +++ b/pkg/client/rekor_client.go @@ -15,7 +15,11 @@ package client import ( + "bytes" "crypto/tls" + "encoding/base64" + "encoding/hex" + "fmt" "net/http" "net/url" @@ -24,7 +28,11 @@ import ( "github.com/go-openapi/strfmt" "github.com/hashicorp/go-cleanhttp" retryablehttp "github.com/hashicorp/go-retryablehttp" + rekor_pb_common "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + rekor_pb "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" "github.com/sigstore/rekor/pkg/generated/client" + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/util" ) @@ -64,3 +72,65 @@ func GetRekorClient(rekorServerURL string, opts ...Option) (*client.Rekor, error registry.Add("signedCheckpoint", &util.SignedNote{}, util.SignedCheckpointValidator) return client.New(rt, registry), nil } + +func GenerateTransparencyLogEntry(anon models.LogEntryAnon) (*rekor_pb.TransparencyLogEntry, error) { + logIDHash, err := hex.DecodeString(*anon.LogID) + if err != nil { + return nil, fmt.Errorf("decoding logID string: %w", err) + } + + rootHash, err := hex.DecodeString(*anon.Verification.InclusionProof.RootHash) + if err != nil { + return nil, fmt.Errorf("decoding inclusion proof root hash: %w", err) + } + + inclusionProofHashes := make([][]byte, len(anon.Verification.InclusionProof.Hashes)) + for i, hash := range anon.Verification.InclusionProof.Hashes { + hashBytes, err := hex.DecodeString(hash) + if err != nil { + return nil, fmt.Errorf("decoding inclusion proof hash: %w", err) + } + inclusionProofHashes[i] = hashBytes + } + + b, err := base64.StdEncoding.DecodeString(anon.Body.(string)) + if err != nil { + return nil, fmt.Errorf("base64 decoding body: %w", err) + } + + pe, err := models.UnmarshalProposedEntry(bytes.NewReader(b), runtime.JSONConsumer()) + if err != nil { + return nil, err + } + eimpl, err := types.UnmarshalEntry(pe) + if err != nil { + return nil, err + } + + tle := &rekor_pb.TransparencyLogEntry{ + LogIndex: *anon.LogIndex, + LogId: &rekor_pb_common.LogId{ + KeyId: logIDHash, + }, + KindVersion: &rekor_pb.KindVersion{ + Kind: pe.Kind(), + Version: eimpl.APIVersion(), + }, + IntegratedTime: *anon.IntegratedTime, + InclusionPromise: &rekor_pb.InclusionPromise{ + SignedEntryTimestamp: anon.Verification.SignedEntryTimestamp, // may need to b64 decode + }, + InclusionProof: &rekor_pb.InclusionProof{ + LogIndex: *anon.LogIndex, + RootHash: rootHash, + TreeSize: *anon.Verification.InclusionProof.TreeSize, + Hashes: inclusionProofHashes, + Checkpoint: &rekor_pb.Checkpoint{ + Envelope: *anon.Verification.InclusionProof.Checkpoint, + }, + }, + CanonicalizedBody: b, // we don't call eimpl.Canonicalize in the case that the logic is different in this caller vs when it was persisted in the log + } + + return tle, nil +} From 9101b39fd128ea4808869cf9cdfdfb8ad36eb885 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Tue, 23 May 2023 08:24:01 -0400 Subject: [PATCH 2/7] go mod tidy Signed-off-by: Bob Callaway --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0660b3b07..3e2a28d45 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-retryablehttp v0.7.2 github.com/redis/go-redis/v9 v9.0.4 + github.com/sigstore/protobuf-specs v0.1.0 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 ) @@ -72,7 +73,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/google/s2a-go v0.1.3 // indirect github.com/jellydator/ttlcache/v3 v3.0.1 // indirect - github.com/sigstore/protobuf-specs v0.1.0 // indirect go.opentelemetry.io/otel v1.14.0 // indirect go.opentelemetry.io/otel/trace v1.14.0 // indirect k8s.io/klog/v2 v2.100.1 // indirect From e39db09150e159eff6a20dd92451ff5fa306536f Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Wed, 24 May 2023 09:59:28 -0400 Subject: [PATCH 3/7] remove CLI usage Signed-off-by: Bob Callaway --- cmd/rekor-cli/app/format/wrap.go | 2 +- cmd/rekor-cli/app/get.go | 4 ---- cmd/rekor-cli/app/pflags.go | 2 +- pkg/client/rekor_client.go | 8 +++----- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/cmd/rekor-cli/app/format/wrap.go b/cmd/rekor-cli/app/format/wrap.go index 95eaf1805..5e81c37f6 100644 --- a/cmd/rekor-cli/app/format/wrap.go +++ b/cmd/rekor-cli/app/format/wrap.go @@ -44,7 +44,7 @@ func WrapCmd(f formatCmd) CobraCmd { } else { fmt.Println(toJSON(s)) } - case "json", "bundle": + case "json": fmt.Println(toJSON(obj)) } } diff --git a/cmd/rekor-cli/app/get.go b/cmd/rekor-cli/app/get.go index 3f8aece22..6a6181341 100644 --- a/cmd/rekor-cli/app/get.go +++ b/cmd/rekor-cli/app/get.go @@ -203,10 +203,6 @@ func compareEntryUUIDs(requestEntryUUID string, responseEntryUUID string) error } func parseEntry(uuid string, e models.LogEntryAnon) (interface{}, error) { - if viper.GetString("format") == "bundle" { - return client.GenerateTransparencyLogEntry(e) - } - b, err := base64.StdEncoding.DecodeString(e.Body.(string)) if err != nil { return nil, err diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index befeeb181..1f147cae8 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -111,7 +111,7 @@ func initializePFlagMap() { }, formatFlag: func() pflag.Value { // this validates the output format requested - return valueFactory(formatFlag, validateString("required,oneof=json default bundle"), "") + return valueFactory(formatFlag, validateString("required,oneof=json default"), "") }, timeoutFlag: func() pflag.Value { // this validates the timeout is >= 0 diff --git a/pkg/client/rekor_client.go b/pkg/client/rekor_client.go index 43dbfed13..b3cf3d84c 100644 --- a/pkg/client/rekor_client.go +++ b/pkg/client/rekor_client.go @@ -107,7 +107,7 @@ func GenerateTransparencyLogEntry(anon models.LogEntryAnon) (*rekor_pb.Transpare return nil, err } - tle := &rekor_pb.TransparencyLogEntry{ + return &rekor_pb.TransparencyLogEntry{ LogIndex: *anon.LogIndex, LogId: &rekor_pb_common.LogId{ KeyId: logIDHash, @@ -118,7 +118,7 @@ func GenerateTransparencyLogEntry(anon models.LogEntryAnon) (*rekor_pb.Transpare }, IntegratedTime: *anon.IntegratedTime, InclusionPromise: &rekor_pb.InclusionPromise{ - SignedEntryTimestamp: anon.Verification.SignedEntryTimestamp, // may need to b64 decode + SignedEntryTimestamp: anon.Verification.SignedEntryTimestamp, }, InclusionProof: &rekor_pb.InclusionProof{ LogIndex: *anon.LogIndex, @@ -130,7 +130,5 @@ func GenerateTransparencyLogEntry(anon models.LogEntryAnon) (*rekor_pb.Transpare }, }, CanonicalizedBody: b, // we don't call eimpl.Canonicalize in the case that the logic is different in this caller vs when it was persisted in the log - } - - return tle, nil + }, nil } From e01d38a88da52c7156f4e3bb0b78657d9c0593d3 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Wed, 24 May 2023 11:33:32 -0400 Subject: [PATCH 4/7] add unit tests Signed-off-by: Bob Callaway --- pkg/client/rekor_client_test.go | 204 ++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/pkg/client/rekor_client_test.go b/pkg/client/rekor_client_test.go index f4f049c9c..5887b7bdd 100644 --- a/pkg/client/rekor_client_test.go +++ b/pkg/client/rekor_client_test.go @@ -16,13 +16,22 @@ package client import ( + "encoding/base64" + "encoding/hex" "net/http" "net/http/httptest" + "reflect" "strings" "testing" "time" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + rekor_pb_common "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + rekor_pb "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" "github.com/sigstore/rekor/pkg/generated/client/index" + "github.com/sigstore/rekor/pkg/generated/models" + _ "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" "go.uber.org/goleak" ) @@ -131,3 +140,198 @@ func TestRekorLeakedGoroutine_SearchByHash(t *testing.T) { rekor, _ := GetRekorClient(testServer.URL, WithInsecureTLS(true)) rekor.Index.SearchIndex(index.NewSearchIndexParams()) } + +func TestGenerateTransparencyLogEntry(t *testing.T) { + type TestCase struct { + description string + expectSuccess bool + proposedEntry models.LogEntryAnon + want rekor_pb.TransparencyLogEntry + } + + b64Body := "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2MmQwOGYyOGM2OWNhZGE3YjQyYTQ1Nzk0YjQ3ZWU2YzgxYTdkZmE3MTY4NDZiMzljODhmMGFkMTljMjA2OTk3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQm14U0N1TW1HSzhNQWRMd1FWZ21TZjVXKzlkdU5iQXN1cUNQNlNucUxCUkFpRUFvNGtGRVdDTG9HcTVUaysrUEhtTEgrb3N1emVTRjN4OTdBbmVicTRlbVRvPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUk1ha05EUVhKVFowRjNTVUpCWjBsVVRWQkRlVXdyYmxOb2MycHdaa2hZYUZkYVRVWkNUVUZIUlVSQlMwSm5aM0ZvYTJwUFVGRlJSRUY2UVhFS1RWSlZkMFYzV1VSV1VWRkxSWGQ0ZW1GWFpIcGtSemw1V2xNMWExcFlXWGhGVkVGUVFtZE9Wa0pCVFZSRFNFNXdXak5PTUdJelNteE5RalJZUkZSSmVRcE5SRWwzVFdwRmQwNUVXWGhOVm05WVJGUkplVTFFU1hkTmFrVjNUbFJaZUUxR2IzZEZla1ZTVFVFNFIwRXhWVVZEYUUxSll6SnNibU16VW5aamJWVjNDbGRVUVZSQ1oyTnhhR3RxVDFCUlNVSkNaMmR4YUd0cVQxQlJUVUpDZDA1RFFVRlVVMVJ2VEhWS2N5OTFSV05IU2tRME5VWmFiVE5wWmxKTU4yOXVRVWNLWlZaNWJuWkhVbmN6WnpKMU0wbFhTREZuU2tSamNERjRSWFI2UVZCUWJYQmhlVGRtTmxCNE1XeFpNa0ZyWnpsMGEyb3dRa1J2UTNkdk5FbENlbXBEUXdwQlkyOTNSR2RaUkZaU01GQkJVVWd2UWtGUlJFRm5aVUZOUWsxSFFURlZaRXBSVVUxTlFXOUhRME56UjBGUlZVWkNkMDFFVFVGM1IwRXhWV1JGZDBWQ0NpOTNVVU5OUVVGM1NGRlpSRlpTTUU5Q1FsbEZSazlSYTNZNVoyMVpXVFpWU0doQ1pWSnJMMWx4VlVsaU1WRldiMDFDT0VkQk1WVmtTWGRSV1UxQ1lVRUtSa1pxUVVoc0sxSlNZVlp0Y1ZoeVRXdExSMVJKZEVGeGVHTllOazFIVFVkQk1WVmtSVkZTWTAxR2NVZFhSMmd3WkVoQ2VrOXBPSFphTW13d1lVaFdhUXBNYlU1MllsTTVjbGx1VGpCTU0xSnNZMjVLYUZwdE9YbGlVekZ5WkZkS2JHTXpVbWhaTW5OMlRHMWtjR1JIYURGWmFUa3pZak5LY2xwdGVIWmtNMDEyQ21KWFJuQmlhVFUxWWxkNFFXTnRWbTFqZVRsdldsZEdhMk41T1hSWldFNHdXbGhKZDBWbldVdExkMWxDUWtGSFJIWjZRVUpCWjFGRlkwaFdlbUZFUVcwS1FtZHZja0puUlVWQldVOHZUVUZGUmtKQ2FISlpiazR3VEROU2JHTnVTbWhhYlRsNVlsTXhjbVJYU214ak0xSm9XVEp6ZDA1bldVdExkMWxDUWtGSFJBcDJla0ZDUVhkUmIxcHFSVEZPVjFGNVdXMUplRTU2V210TlZFVTFXV3BHYlU5VVNUSk9WRlV6V1hwTk5WbDZTbWxQVkdzMVdtcFNhRnBxWjNkWmVrRTFDa0puYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQmhTRlpwWkZoT2JHTnRUbllLWW01U2JHSnVVWFZaTWpsMFRVSTRSME5wYzBkQlVWRkNaemM0ZDBGUldVVkZXRXBzV201TmRtRkhWbWhhU0UxMllsZEdlbVJIVm5sTlEwRkhRMmx6UndwQlVWRkNaemM0ZDBGUlVVVkZhMG94WVZkNGEwbEdVbXhqTTFGblZVaFdhV0pIYkhwaFJFRkxRbWRuY1docmFrOVFVVkZFUVhkT2IwRkVRbXhCYWtWQkNtdDJORTFLYUdGRGFFMUJaMHBWVTNWWll6bFBWRWt3WTB0bU9XTnlObU14Y1RreVYyOXFMM1ZsV0RKRFR6Z3JMMDQyU25SM1FVNTRVSElyTjNWNlpGQUtRV3BDYVhwR2NHZEVMelJzWW5aa1NuRnplWE5HYlVSeU1TdFNNSGhKWjI1S1N5c3JaWGROYmtKaVMxQkVMemd3VTNJelFYTTVMMWxxV1U5M05EVjRkUXA2ZVdzOUNpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19" + bodyBytes, _ := base64.RawStdEncoding.DecodeString(b64Body) + + deadbeefBytes, _ := hex.DecodeString("deadbeef") + abcdefaaBytes, _ := hex.DecodeString("abcdefaa") + + testCases := []TestCase{ + { + description: "valid entry", + expectSuccess: true, + proposedEntry: models.LogEntryAnon{ + Body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2MmQwOGYyOGM2OWNhZGE3YjQyYTQ1Nzk0YjQ3ZWU2YzgxYTdkZmE3MTY4NDZiMzljODhmMGFkMTljMjA2OTk3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQm14U0N1TW1HSzhNQWRMd1FWZ21TZjVXKzlkdU5iQXN1cUNQNlNucUxCUkFpRUFvNGtGRVdDTG9HcTVUaysrUEhtTEgrb3N1emVTRjN4OTdBbmVicTRlbVRvPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUk1ha05EUVhKVFowRjNTVUpCWjBsVVRWQkRlVXdyYmxOb2MycHdaa2hZYUZkYVRVWkNUVUZIUlVSQlMwSm5aM0ZvYTJwUFVGRlJSRUY2UVhFS1RWSlZkMFYzV1VSV1VWRkxSWGQ0ZW1GWFpIcGtSemw1V2xNMWExcFlXWGhGVkVGUVFtZE9Wa0pCVFZSRFNFNXdXak5PTUdJelNteE5RalJZUkZSSmVRcE5SRWwzVFdwRmQwNUVXWGhOVm05WVJGUkplVTFFU1hkTmFrVjNUbFJaZUUxR2IzZEZla1ZTVFVFNFIwRXhWVVZEYUUxSll6SnNibU16VW5aamJWVjNDbGRVUVZSQ1oyTnhhR3RxVDFCUlNVSkNaMmR4YUd0cVQxQlJUVUpDZDA1RFFVRlVVMVJ2VEhWS2N5OTFSV05IU2tRME5VWmFiVE5wWmxKTU4yOXVRVWNLWlZaNWJuWkhVbmN6WnpKMU0wbFhTREZuU2tSamNERjRSWFI2UVZCUWJYQmhlVGRtTmxCNE1XeFpNa0ZyWnpsMGEyb3dRa1J2UTNkdk5FbENlbXBEUXdwQlkyOTNSR2RaUkZaU01GQkJVVWd2UWtGUlJFRm5aVUZOUWsxSFFURlZaRXBSVVUxTlFXOUhRME56UjBGUlZVWkNkMDFFVFVGM1IwRXhWV1JGZDBWQ0NpOTNVVU5OUVVGM1NGRlpSRlpTTUU5Q1FsbEZSazlSYTNZNVoyMVpXVFpWU0doQ1pWSnJMMWx4VlVsaU1WRldiMDFDT0VkQk1WVmtTWGRSV1UxQ1lVRUtSa1pxUVVoc0sxSlNZVlp0Y1ZoeVRXdExSMVJKZEVGeGVHTllOazFIVFVkQk1WVmtSVkZTWTAxR2NVZFhSMmd3WkVoQ2VrOXBPSFphTW13d1lVaFdhUXBNYlU1MllsTTVjbGx1VGpCTU0xSnNZMjVLYUZwdE9YbGlVekZ5WkZkS2JHTXpVbWhaTW5OMlRHMWtjR1JIYURGWmFUa3pZak5LY2xwdGVIWmtNMDEyQ21KWFJuQmlhVFUxWWxkNFFXTnRWbTFqZVRsdldsZEdhMk41T1hSWldFNHdXbGhKZDBWbldVdExkMWxDUWtGSFJIWjZRVUpCWjFGRlkwaFdlbUZFUVcwS1FtZHZja0puUlVWQldVOHZUVUZGUmtKQ2FISlpiazR3VEROU2JHTnVTbWhhYlRsNVlsTXhjbVJYU214ak0xSm9XVEp6ZDA1bldVdExkMWxDUWtGSFJBcDJla0ZDUVhkUmIxcHFSVEZPVjFGNVdXMUplRTU2V210TlZFVTFXV3BHYlU5VVNUSk9WRlV6V1hwTk5WbDZTbWxQVkdzMVdtcFNhRnBxWjNkWmVrRTFDa0puYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQmhTRlpwWkZoT2JHTnRUbllLWW01U2JHSnVVWFZaTWpsMFRVSTRSME5wYzBkQlVWRkNaemM0ZDBGUldVVkZXRXBzV201TmRtRkhWbWhhU0UxMllsZEdlbVJIVm5sTlEwRkhRMmx6UndwQlVWRkNaemM0ZDBGUlVVVkZhMG94WVZkNGEwbEdVbXhqTTFGblZVaFdhV0pIYkhwaFJFRkxRbWRuY1docmFrOVFVVkZFUVhkT2IwRkVRbXhCYWtWQkNtdDJORTFLYUdGRGFFMUJaMHBWVTNWWll6bFBWRWt3WTB0bU9XTnlObU14Y1RreVYyOXFMM1ZsV0RKRFR6Z3JMMDQyU25SM1FVNTRVSElyTjNWNlpGQUtRV3BDYVhwR2NHZEVMelJzWW5aa1NuRnplWE5HYlVSeU1TdFNNSGhKWjI1S1N5c3JaWGROYmtKaVMxQkVMemd3VTNJelFYTTVMMWxxV1U5M05EVjRkUXA2ZVdzOUNpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19", + IntegratedTime: swag.Int64(123), + LogID: swag.String("deadbeef"), + LogIndex: swag.Int64(1), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + Checkpoint: swag.String("checkpoint"), + Hashes: []string{"deadbeef", "abcdefaa"}, + LogIndex: swag.Int64(1), + RootHash: swag.String("abcdefaa"), + TreeSize: swag.Int64(2), + }, + SignedEntryTimestamp: strfmt.Base64("set"), + }, + }, + want: rekor_pb.TransparencyLogEntry{ + LogIndex: 1, + LogId: &rekor_pb_common.LogId{ + KeyId: deadbeefBytes, + }, + KindVersion: &rekor_pb.KindVersion{ + Kind: "hashedrekord", + Version: "0.0.1", + }, + IntegratedTime: 123, + InclusionPromise: &rekor_pb.InclusionPromise{ + SignedEntryTimestamp: []byte("set"), + }, + InclusionProof: &rekor_pb.InclusionProof{ + Checkpoint: &rekor_pb.Checkpoint{ + Envelope: "checkpoint", + }, + Hashes: [][]byte{deadbeefBytes, abcdefaaBytes}, + LogIndex: 1, + RootHash: abcdefaaBytes, + TreeSize: 2, + }, + CanonicalizedBody: bodyBytes, + }, + }, + { + description: "body is not valid base64", + expectSuccess: false, + proposedEntry: models.LogEntryAnon{ + Body: "not_base_64", + IntegratedTime: swag.Int64(123), + LogID: swag.String("deadbeef"), + LogIndex: swag.Int64(1), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + Checkpoint: swag.String("checkpoint"), + Hashes: []string{"deadbeef", "abcdefaa"}, + LogIndex: swag.Int64(1), + RootHash: swag.String("abcdefaa"), + TreeSize: swag.Int64(2), + }, + SignedEntryTimestamp: strfmt.Base64("set"), + }, + }, + }, + { + description: "logID is not valid hex", + expectSuccess: false, + proposedEntry: models.LogEntryAnon{ + Body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2MmQwOGYyOGM2OWNhZGE3YjQyYTQ1Nzk0YjQ3ZWU2YzgxYTdkZmE3MTY4NDZiMzljODhmMGFkMTljMjA2OTk3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQm14U0N1TW1HSzhNQWRMd1FWZ21TZjVXKzlkdU5iQXN1cUNQNlNucUxCUkFpRUFvNGtGRVdDTG9HcTVUaysrUEhtTEgrb3N1emVTRjN4OTdBbmVicTRlbVRvPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUk1ha05EUVhKVFowRjNTVUpCWjBsVVRWQkRlVXdyYmxOb2MycHdaa2hZYUZkYVRVWkNUVUZIUlVSQlMwSm5aM0ZvYTJwUFVGRlJSRUY2UVhFS1RWSlZkMFYzV1VSV1VWRkxSWGQ0ZW1GWFpIcGtSemw1V2xNMWExcFlXWGhGVkVGUVFtZE9Wa0pCVFZSRFNFNXdXak5PTUdJelNteE5RalJZUkZSSmVRcE5SRWwzVFdwRmQwNUVXWGhOVm05WVJGUkplVTFFU1hkTmFrVjNUbFJaZUUxR2IzZEZla1ZTVFVFNFIwRXhWVVZEYUUxSll6SnNibU16VW5aamJWVjNDbGRVUVZSQ1oyTnhhR3RxVDFCUlNVSkNaMmR4YUd0cVQxQlJUVUpDZDA1RFFVRlVVMVJ2VEhWS2N5OTFSV05IU2tRME5VWmFiVE5wWmxKTU4yOXVRVWNLWlZaNWJuWkhVbmN6WnpKMU0wbFhTREZuU2tSamNERjRSWFI2UVZCUWJYQmhlVGRtTmxCNE1XeFpNa0ZyWnpsMGEyb3dRa1J2UTNkdk5FbENlbXBEUXdwQlkyOTNSR2RaUkZaU01GQkJVVWd2UWtGUlJFRm5aVUZOUWsxSFFURlZaRXBSVVUxTlFXOUhRME56UjBGUlZVWkNkMDFFVFVGM1IwRXhWV1JGZDBWQ0NpOTNVVU5OUVVGM1NGRlpSRlpTTUU5Q1FsbEZSazlSYTNZNVoyMVpXVFpWU0doQ1pWSnJMMWx4VlVsaU1WRldiMDFDT0VkQk1WVmtTWGRSV1UxQ1lVRUtSa1pxUVVoc0sxSlNZVlp0Y1ZoeVRXdExSMVJKZEVGeGVHTllOazFIVFVkQk1WVmtSVkZTWTAxR2NVZFhSMmd3WkVoQ2VrOXBPSFphTW13d1lVaFdhUXBNYlU1MllsTTVjbGx1VGpCTU0xSnNZMjVLYUZwdE9YbGlVekZ5WkZkS2JHTXpVbWhaTW5OMlRHMWtjR1JIYURGWmFUa3pZak5LY2xwdGVIWmtNMDEyQ21KWFJuQmlhVFUxWWxkNFFXTnRWbTFqZVRsdldsZEdhMk41T1hSWldFNHdXbGhKZDBWbldVdExkMWxDUWtGSFJIWjZRVUpCWjFGRlkwaFdlbUZFUVcwS1FtZHZja0puUlVWQldVOHZUVUZGUmtKQ2FISlpiazR3VEROU2JHTnVTbWhhYlRsNVlsTXhjbVJYU214ak0xSm9XVEp6ZDA1bldVdExkMWxDUWtGSFJBcDJla0ZDUVhkUmIxcHFSVEZPVjFGNVdXMUplRTU2V210TlZFVTFXV3BHYlU5VVNUSk9WRlV6V1hwTk5WbDZTbWxQVkdzMVdtcFNhRnBxWjNkWmVrRTFDa0puYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQmhTRlpwWkZoT2JHTnRUbllLWW01U2JHSnVVWFZaTWpsMFRVSTRSME5wYzBkQlVWRkNaemM0ZDBGUldVVkZXRXBzV201TmRtRkhWbWhhU0UxMllsZEdlbVJIVm5sTlEwRkhRMmx6UndwQlVWRkNaemM0ZDBGUlVVVkZhMG94WVZkNGEwbEdVbXhqTTFGblZVaFdhV0pIYkhwaFJFRkxRbWRuY1docmFrOVFVVkZFUVhkT2IwRkVRbXhCYWtWQkNtdDJORTFLYUdGRGFFMUJaMHBWVTNWWll6bFBWRWt3WTB0bU9XTnlObU14Y1RreVYyOXFMM1ZsV0RKRFR6Z3JMMDQyU25SM1FVNTRVSElyTjNWNlpGQUtRV3BDYVhwR2NHZEVMelJzWW5aa1NuRnplWE5HYlVSeU1TdFNNSGhKWjI1S1N5c3JaWGROYmtKaVMxQkVMemd3VTNJelFYTTVMMWxxV1U5M05EVjRkUXA2ZVdzOUNpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19", + IntegratedTime: swag.Int64(123), + LogID: swag.String("not_valid_hex"), + LogIndex: swag.Int64(1), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + Checkpoint: swag.String("checkpoint"), + Hashes: []string{"deadbeef", "abcdefaa"}, + LogIndex: swag.Int64(1), + RootHash: swag.String("abcdefaa"), + TreeSize: swag.Int64(2), + }, + SignedEntryTimestamp: strfmt.Base64("set"), + }, + }, + }, + { + description: "rootHash is not valid hex", + expectSuccess: false, + proposedEntry: models.LogEntryAnon{ + Body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2MmQwOGYyOGM2OWNhZGE3YjQyYTQ1Nzk0YjQ3ZWU2YzgxYTdkZmE3MTY4NDZiMzljODhmMGFkMTljMjA2OTk3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQm14U0N1TW1HSzhNQWRMd1FWZ21TZjVXKzlkdU5iQXN1cUNQNlNucUxCUkFpRUFvNGtGRVdDTG9HcTVUaysrUEhtTEgrb3N1emVTRjN4OTdBbmVicTRlbVRvPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUk1ha05EUVhKVFowRjNTVUpCWjBsVVRWQkRlVXdyYmxOb2MycHdaa2hZYUZkYVRVWkNUVUZIUlVSQlMwSm5aM0ZvYTJwUFVGRlJSRUY2UVhFS1RWSlZkMFYzV1VSV1VWRkxSWGQ0ZW1GWFpIcGtSemw1V2xNMWExcFlXWGhGVkVGUVFtZE9Wa0pCVFZSRFNFNXdXak5PTUdJelNteE5RalJZUkZSSmVRcE5SRWwzVFdwRmQwNUVXWGhOVm05WVJGUkplVTFFU1hkTmFrVjNUbFJaZUUxR2IzZEZla1ZTVFVFNFIwRXhWVVZEYUUxSll6SnNibU16VW5aamJWVjNDbGRVUVZSQ1oyTnhhR3RxVDFCUlNVSkNaMmR4YUd0cVQxQlJUVUpDZDA1RFFVRlVVMVJ2VEhWS2N5OTFSV05IU2tRME5VWmFiVE5wWmxKTU4yOXVRVWNLWlZaNWJuWkhVbmN6WnpKMU0wbFhTREZuU2tSamNERjRSWFI2UVZCUWJYQmhlVGRtTmxCNE1XeFpNa0ZyWnpsMGEyb3dRa1J2UTNkdk5FbENlbXBEUXdwQlkyOTNSR2RaUkZaU01GQkJVVWd2UWtGUlJFRm5aVUZOUWsxSFFURlZaRXBSVVUxTlFXOUhRME56UjBGUlZVWkNkMDFFVFVGM1IwRXhWV1JGZDBWQ0NpOTNVVU5OUVVGM1NGRlpSRlpTTUU5Q1FsbEZSazlSYTNZNVoyMVpXVFpWU0doQ1pWSnJMMWx4VlVsaU1WRldiMDFDT0VkQk1WVmtTWGRSV1UxQ1lVRUtSa1pxUVVoc0sxSlNZVlp0Y1ZoeVRXdExSMVJKZEVGeGVHTllOazFIVFVkQk1WVmtSVkZTWTAxR2NVZFhSMmd3WkVoQ2VrOXBPSFphTW13d1lVaFdhUXBNYlU1MllsTTVjbGx1VGpCTU0xSnNZMjVLYUZwdE9YbGlVekZ5WkZkS2JHTXpVbWhaTW5OMlRHMWtjR1JIYURGWmFUa3pZak5LY2xwdGVIWmtNMDEyQ21KWFJuQmlhVFUxWWxkNFFXTnRWbTFqZVRsdldsZEdhMk41T1hSWldFNHdXbGhKZDBWbldVdExkMWxDUWtGSFJIWjZRVUpCWjFGRlkwaFdlbUZFUVcwS1FtZHZja0puUlVWQldVOHZUVUZGUmtKQ2FISlpiazR3VEROU2JHTnVTbWhhYlRsNVlsTXhjbVJYU214ak0xSm9XVEp6ZDA1bldVdExkMWxDUWtGSFJBcDJla0ZDUVhkUmIxcHFSVEZPVjFGNVdXMUplRTU2V210TlZFVTFXV3BHYlU5VVNUSk9WRlV6V1hwTk5WbDZTbWxQVkdzMVdtcFNhRnBxWjNkWmVrRTFDa0puYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQmhTRlpwWkZoT2JHTnRUbllLWW01U2JHSnVVWFZaTWpsMFRVSTRSME5wYzBkQlVWRkNaemM0ZDBGUldVVkZXRXBzV201TmRtRkhWbWhhU0UxMllsZEdlbVJIVm5sTlEwRkhRMmx6UndwQlVWRkNaemM0ZDBGUlVVVkZhMG94WVZkNGEwbEdVbXhqTTFGblZVaFdhV0pIYkhwaFJFRkxRbWRuY1docmFrOVFVVkZFUVhkT2IwRkVRbXhCYWtWQkNtdDJORTFLYUdGRGFFMUJaMHBWVTNWWll6bFBWRWt3WTB0bU9XTnlObU14Y1RreVYyOXFMM1ZsV0RKRFR6Z3JMMDQyU25SM1FVNTRVSElyTjNWNlpGQUtRV3BDYVhwR2NHZEVMelJzWW5aa1NuRnplWE5HYlVSeU1TdFNNSGhKWjI1S1N5c3JaWGROYmtKaVMxQkVMemd3VTNJelFYTTVMMWxxV1U5M05EVjRkUXA2ZVdzOUNpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19", + IntegratedTime: swag.Int64(123), + LogID: swag.String("deadbeef"), + LogIndex: swag.Int64(1), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + Checkpoint: swag.String("checkpoint"), + Hashes: []string{"deadbeef", "abcdefaa"}, + LogIndex: swag.Int64(1), + RootHash: swag.String("not_hex_string"), + TreeSize: swag.Int64(2), + }, + SignedEntryTimestamp: strfmt.Base64("set"), + }, + }, + }, + { + description: "one inclusion proof hash is not valid hex", + expectSuccess: false, + proposedEntry: models.LogEntryAnon{ + Body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2MmQwOGYyOGM2OWNhZGE3YjQyYTQ1Nzk0YjQ3ZWU2YzgxYTdkZmE3MTY4NDZiMzljODhmMGFkMTljMjA2OTk3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQm14U0N1TW1HSzhNQWRMd1FWZ21TZjVXKzlkdU5iQXN1cUNQNlNucUxCUkFpRUFvNGtGRVdDTG9HcTVUaysrUEhtTEgrb3N1emVTRjN4OTdBbmVicTRlbVRvPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUk1ha05EUVhKVFowRjNTVUpCWjBsVVRWQkRlVXdyYmxOb2MycHdaa2hZYUZkYVRVWkNUVUZIUlVSQlMwSm5aM0ZvYTJwUFVGRlJSRUY2UVhFS1RWSlZkMFYzV1VSV1VWRkxSWGQ0ZW1GWFpIcGtSemw1V2xNMWExcFlXWGhGVkVGUVFtZE9Wa0pCVFZSRFNFNXdXak5PTUdJelNteE5RalJZUkZSSmVRcE5SRWwzVFdwRmQwNUVXWGhOVm05WVJGUkplVTFFU1hkTmFrVjNUbFJaZUUxR2IzZEZla1ZTVFVFNFIwRXhWVVZEYUUxSll6SnNibU16VW5aamJWVjNDbGRVUVZSQ1oyTnhhR3RxVDFCUlNVSkNaMmR4YUd0cVQxQlJUVUpDZDA1RFFVRlVVMVJ2VEhWS2N5OTFSV05IU2tRME5VWmFiVE5wWmxKTU4yOXVRVWNLWlZaNWJuWkhVbmN6WnpKMU0wbFhTREZuU2tSamNERjRSWFI2UVZCUWJYQmhlVGRtTmxCNE1XeFpNa0ZyWnpsMGEyb3dRa1J2UTNkdk5FbENlbXBEUXdwQlkyOTNSR2RaUkZaU01GQkJVVWd2UWtGUlJFRm5aVUZOUWsxSFFURlZaRXBSVVUxTlFXOUhRME56UjBGUlZVWkNkMDFFVFVGM1IwRXhWV1JGZDBWQ0NpOTNVVU5OUVVGM1NGRlpSRlpTTUU5Q1FsbEZSazlSYTNZNVoyMVpXVFpWU0doQ1pWSnJMMWx4VlVsaU1WRldiMDFDT0VkQk1WVmtTWGRSV1UxQ1lVRUtSa1pxUVVoc0sxSlNZVlp0Y1ZoeVRXdExSMVJKZEVGeGVHTllOazFIVFVkQk1WVmtSVkZTWTAxR2NVZFhSMmd3WkVoQ2VrOXBPSFphTW13d1lVaFdhUXBNYlU1MllsTTVjbGx1VGpCTU0xSnNZMjVLYUZwdE9YbGlVekZ5WkZkS2JHTXpVbWhaTW5OMlRHMWtjR1JIYURGWmFUa3pZak5LY2xwdGVIWmtNMDEyQ21KWFJuQmlhVFUxWWxkNFFXTnRWbTFqZVRsdldsZEdhMk41T1hSWldFNHdXbGhKZDBWbldVdExkMWxDUWtGSFJIWjZRVUpCWjFGRlkwaFdlbUZFUVcwS1FtZHZja0puUlVWQldVOHZUVUZGUmtKQ2FISlpiazR3VEROU2JHTnVTbWhhYlRsNVlsTXhjbVJYU214ak0xSm9XVEp6ZDA1bldVdExkMWxDUWtGSFJBcDJla0ZDUVhkUmIxcHFSVEZPVjFGNVdXMUplRTU2V210TlZFVTFXV3BHYlU5VVNUSk9WRlV6V1hwTk5WbDZTbWxQVkdzMVdtcFNhRnBxWjNkWmVrRTFDa0puYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQmhTRlpwWkZoT2JHTnRUbllLWW01U2JHSnVVWFZaTWpsMFRVSTRSME5wYzBkQlVWRkNaemM0ZDBGUldVVkZXRXBzV201TmRtRkhWbWhhU0UxMllsZEdlbVJIVm5sTlEwRkhRMmx6UndwQlVWRkNaemM0ZDBGUlVVVkZhMG94WVZkNGEwbEdVbXhqTTFGblZVaFdhV0pIYkhwaFJFRkxRbWRuY1docmFrOVFVVkZFUVhkT2IwRkVRbXhCYWtWQkNtdDJORTFLYUdGRGFFMUJaMHBWVTNWWll6bFBWRWt3WTB0bU9XTnlObU14Y1RreVYyOXFMM1ZsV0RKRFR6Z3JMMDQyU25SM1FVNTRVSElyTjNWNlpGQUtRV3BDYVhwR2NHZEVMelJzWW5aa1NuRnplWE5HYlVSeU1TdFNNSGhKWjI1S1N5c3JaWGROYmtKaVMxQkVMemd3VTNJelFYTTVMMWxxV1U5M05EVjRkUXA2ZVdzOUNpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19", + IntegratedTime: swag.Int64(123), + LogID: swag.String("deadbeef"), + LogIndex: swag.Int64(1), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + Checkpoint: swag.String("checkpoint"), + Hashes: []string{"invalid_hex", "abcdefaa"}, + LogIndex: swag.Int64(1), + RootHash: swag.String("abcdefaa"), + TreeSize: swag.Int64(2), + }, + SignedEntryTimestamp: strfmt.Base64("set"), + }, + }, + }, + { + description: "body is valid base64 but not valid entry", + expectSuccess: false, + proposedEntry: models.LogEntryAnon{ + Body: "aW52YWxpZF9lbnRyeQo=", // "invalid_entry" + IntegratedTime: swag.Int64(123), + LogID: swag.String("deadbeef"), + LogIndex: swag.Int64(1), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + Checkpoint: swag.String("checkpoint"), + Hashes: []string{"deadbeef", "abcdefaa"}, + LogIndex: swag.Int64(1), + RootHash: swag.String("abcdefaa"), + TreeSize: swag.Int64(2), + }, + SignedEntryTimestamp: strfmt.Base64("set"), + }, + }, + }, + { + description: "kind/version are valid but spec is not schema valid", + expectSuccess: false, + proposedEntry: models.LogEntryAnon{ + Body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7ImhhcyI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjYyZDA4ZjI4YzY5Y2FkYTdiNDJhNDU3OTRiNDdlZTZjODFhN2RmYTcxNjg0NmIzOWM4OGYwYWQxOWMyMDY5OTcifX0sInNpZ25hdHVyZSI6eyJjb250ZW50IjoiTUVVQ0lCbXhTQ3VNbUdLOE1BZEx3UVZnbVNmNVcrOWR1TmJBc3VxQ1A2U25xTEJSQWlFQW80a0ZFV0NMb0dxNVRrKytQSG1MSCtvc3V6ZVNGM3g5N0FuZWJxNGVtVG89IiwicHVibGljS2V5Ijp7ImNvbnRlbnQiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VSTWFrTkRRWEpUWjBGM1NVSkJaMGxVVFZCRGVVd3JibE5vYzJwd1praFlhRmRhVFVaQ1RVRkhSVVJCUzBKblozRm9hMnBQVUZGUlJFRjZRWEVLVFZKVmQwVjNXVVJXVVZGTFJYZDRlbUZYWkhwa1J6bDVXbE0xYTFwWVdYaEZWRUZRUW1kT1ZrSkJUVlJEU0U1d1dqTk9NR0l6U214TlFqUllSRlJKZVFwTlJFbDNUV3BGZDA1RVdYaE5WbTlZUkZSSmVVMUVTWGROYWtWM1RsUlplRTFHYjNkRmVrVlNUVUU0UjBFeFZVVkRhRTFKWXpKc2JtTXpVblpqYlZWM0NsZFVRVlJDWjJOeGFHdHFUMUJSU1VKQ1oyZHhhR3RxVDFCUlRVSkNkMDVEUVVGVVUxUnZUSFZLY3k5MVJXTkhTa1EwTlVaYWJUTnBabEpNTjI5dVFVY0taVlo1Ym5aSFVuY3paekoxTTBsWFNERm5Ta1JqY0RGNFJYUjZRVkJRYlhCaGVUZG1ObEI0TVd4Wk1rRnJaemwwYTJvd1FrUnZRM2R2TkVsQ2VtcERRd3BCWTI5M1JHZFpSRlpTTUZCQlVVZ3ZRa0ZSUkVGblpVRk5RazFIUVRGVlpFcFJVVTFOUVc5SFEwTnpSMEZSVlVaQ2QwMUVUVUYzUjBFeFZXUkZkMFZDQ2k5M1VVTk5RVUYzU0ZGWlJGWlNNRTlDUWxsRlJrOVJhM1k1WjIxWldUWlZTR2hDWlZKckwxbHhWVWxpTVZGV2IwMUNPRWRCTVZWa1NYZFJXVTFDWVVFS1JrWnFRVWhzSzFKU1lWWnRjVmh5VFd0TFIxUkpkRUZ4ZUdOWU5rMUhUVWRCTVZWa1JWRlNZMDFHY1VkWFIyZ3daRWhDZWs5cE9IWmFNbXd3WVVoV2FRcE1iVTUyWWxNNWNsbHVUakJNTTFKc1kyNUthRnB0T1hsaVV6RnlaRmRLYkdNelVtaFpNbk4yVEcxa2NHUkhhREZaYVRrellqTktjbHB0ZUhaa00wMTJDbUpYUm5CaWFUVTFZbGQ0UVdOdFZtMWplVGx2V2xkR2EyTjVPWFJaV0U0d1dsaEpkMFZuV1V0TGQxbENRa0ZIUkhaNlFVSkJaMUZGWTBoV2VtRkVRVzBLUW1kdmNrSm5SVVZCV1U4dlRVRkZSa0pDYUhKWmJrNHdURE5TYkdOdVNtaGFiVGw1WWxNeGNtUlhTbXhqTTFKb1dUSnpkMDVuV1V0TGQxbENRa0ZIUkFwMmVrRkNRWGRSYjFwcVJURk9WMUY1V1cxSmVFNTZXbXROVkVVMVdXcEdiVTlVU1RKT1ZGVXpXWHBOTlZsNlNtbFBWR3MxV21wU2FGcHFaM2RaZWtFMUNrSm5iM0pDWjBWRlFWbFBMMDFCUlVKQ1EzUnZaRWhTZDJONmIzWk1NMUoyWVRKV2RVeHRSbXBrUjJ4MlltNU5kVm95YkRCaFNGWnBaRmhPYkdOdFRuWUtZbTVTYkdKdVVYVlpNamwwVFVJNFIwTnBjMGRCVVZGQ1p6YzRkMEZSV1VWRldFcHNXbTVOZG1GSFZtaGFTRTEyWWxkR2VtUkhWbmxOUTBGSFEybHpSd3BCVVZGQ1p6YzRkMEZSVVVWRmEwb3hZVmQ0YTBsR1VteGpNMUZuVlVoV2FXSkhiSHBoUkVGTFFtZG5jV2hyYWs5UVVWRkVRWGRPYjBGRVFteEJha1ZCQ210Mk5FMUthR0ZEYUUxQlowcFZVM1ZaWXpsUFZFa3dZMHRtT1dOeU5tTXhjVGt5VjI5cUwzVmxXREpEVHpnckwwNDJTblIzUVU1NFVISXJOM1Y2WkZBS1FXcENhWHBHY0dkRUx6UnNZblprU25GemVYTkdiVVJ5TVN0U01IaEpaMjVLU3lzclpYZE5ia0ppUzFCRUx6Z3dVM0l6UVhNNUwxbHFXVTkzTkRWNGRRcDZlV3M5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0K", + IntegratedTime: swag.Int64(123), + LogID: swag.String("deadbeef"), + LogIndex: swag.Int64(1), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + Checkpoint: swag.String("checkpoint"), + Hashes: []string{"deadbeef", "abcdefaa"}, + LogIndex: swag.Int64(1), + RootHash: swag.String("abcdefaa"), + TreeSize: swag.Int64(2), + }, + SignedEntryTimestamp: strfmt.Base64("set"), + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + e, err := GenerateTransparencyLogEntry(tc.proposedEntry) + if (err == nil) != tc.expectSuccess { + t.Fatalf("unexpected result: %v", err) + } + + if tc.expectSuccess && !reflect.DeepEqual(*e, tc.want) { + t.Errorf("unexpected value returned; got %v, wanted %v", e, tc.want) + } + }) + } +} From ce5e7136ff1d035b7dc932c81bff353612093438 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Wed, 24 May 2023 11:42:15 -0400 Subject: [PATCH 5/7] fix lint issues Signed-off-by: Bob Callaway --- pkg/client/rekor_client_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/client/rekor_client_test.go b/pkg/client/rekor_client_test.go index 5887b7bdd..874e68a21 100644 --- a/pkg/client/rekor_client_test.go +++ b/pkg/client/rekor_client_test.go @@ -146,7 +146,7 @@ func TestGenerateTransparencyLogEntry(t *testing.T) { description string expectSuccess bool proposedEntry models.LogEntryAnon - want rekor_pb.TransparencyLogEntry + want *rekor_pb.TransparencyLogEntry } b64Body := "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2MmQwOGYyOGM2OWNhZGE3YjQyYTQ1Nzk0YjQ3ZWU2YzgxYTdkZmE3MTY4NDZiMzljODhmMGFkMTljMjA2OTk3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQm14U0N1TW1HSzhNQWRMd1FWZ21TZjVXKzlkdU5iQXN1cUNQNlNucUxCUkFpRUFvNGtGRVdDTG9HcTVUaysrUEhtTEgrb3N1emVTRjN4OTdBbmVicTRlbVRvPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUk1ha05EUVhKVFowRjNTVUpCWjBsVVRWQkRlVXdyYmxOb2MycHdaa2hZYUZkYVRVWkNUVUZIUlVSQlMwSm5aM0ZvYTJwUFVGRlJSRUY2UVhFS1RWSlZkMFYzV1VSV1VWRkxSWGQ0ZW1GWFpIcGtSemw1V2xNMWExcFlXWGhGVkVGUVFtZE9Wa0pCVFZSRFNFNXdXak5PTUdJelNteE5RalJZUkZSSmVRcE5SRWwzVFdwRmQwNUVXWGhOVm05WVJGUkplVTFFU1hkTmFrVjNUbFJaZUUxR2IzZEZla1ZTVFVFNFIwRXhWVVZEYUUxSll6SnNibU16VW5aamJWVjNDbGRVUVZSQ1oyTnhhR3RxVDFCUlNVSkNaMmR4YUd0cVQxQlJUVUpDZDA1RFFVRlVVMVJ2VEhWS2N5OTFSV05IU2tRME5VWmFiVE5wWmxKTU4yOXVRVWNLWlZaNWJuWkhVbmN6WnpKMU0wbFhTREZuU2tSamNERjRSWFI2UVZCUWJYQmhlVGRtTmxCNE1XeFpNa0ZyWnpsMGEyb3dRa1J2UTNkdk5FbENlbXBEUXdwQlkyOTNSR2RaUkZaU01GQkJVVWd2UWtGUlJFRm5aVUZOUWsxSFFURlZaRXBSVVUxTlFXOUhRME56UjBGUlZVWkNkMDFFVFVGM1IwRXhWV1JGZDBWQ0NpOTNVVU5OUVVGM1NGRlpSRlpTTUU5Q1FsbEZSazlSYTNZNVoyMVpXVFpWU0doQ1pWSnJMMWx4VlVsaU1WRldiMDFDT0VkQk1WVmtTWGRSV1UxQ1lVRUtSa1pxUVVoc0sxSlNZVlp0Y1ZoeVRXdExSMVJKZEVGeGVHTllOazFIVFVkQk1WVmtSVkZTWTAxR2NVZFhSMmd3WkVoQ2VrOXBPSFphTW13d1lVaFdhUXBNYlU1MllsTTVjbGx1VGpCTU0xSnNZMjVLYUZwdE9YbGlVekZ5WkZkS2JHTXpVbWhaTW5OMlRHMWtjR1JIYURGWmFUa3pZak5LY2xwdGVIWmtNMDEyQ21KWFJuQmlhVFUxWWxkNFFXTnRWbTFqZVRsdldsZEdhMk41T1hSWldFNHdXbGhKZDBWbldVdExkMWxDUWtGSFJIWjZRVUpCWjFGRlkwaFdlbUZFUVcwS1FtZHZja0puUlVWQldVOHZUVUZGUmtKQ2FISlpiazR3VEROU2JHTnVTbWhhYlRsNVlsTXhjbVJYU214ak0xSm9XVEp6ZDA1bldVdExkMWxDUWtGSFJBcDJla0ZDUVhkUmIxcHFSVEZPVjFGNVdXMUplRTU2V210TlZFVTFXV3BHYlU5VVNUSk9WRlV6V1hwTk5WbDZTbWxQVkdzMVdtcFNhRnBxWjNkWmVrRTFDa0puYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQmhTRlpwWkZoT2JHTnRUbllLWW01U2JHSnVVWFZaTWpsMFRVSTRSME5wYzBkQlVWRkNaemM0ZDBGUldVVkZXRXBzV201TmRtRkhWbWhhU0UxMllsZEdlbVJIVm5sTlEwRkhRMmx6UndwQlVWRkNaemM0ZDBGUlVVVkZhMG94WVZkNGEwbEdVbXhqTTFGblZVaFdhV0pIYkhwaFJFRkxRbWRuY1docmFrOVFVVkZFUVhkT2IwRkVRbXhCYWtWQkNtdDJORTFLYUdGRGFFMUJaMHBWVTNWWll6bFBWRWt3WTB0bU9XTnlObU14Y1RreVYyOXFMM1ZsV0RKRFR6Z3JMMDQyU25SM1FVNTRVSElyTjNWNlpGQUtRV3BDYVhwR2NHZEVMelJzWW5aa1NuRnplWE5HYlVSeU1TdFNNSGhKWjI1S1N5c3JaWGROYmtKaVMxQkVMemd3VTNJelFYTTVMMWxxV1U5M05EVjRkUXA2ZVdzOUNpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19" @@ -175,7 +175,7 @@ func TestGenerateTransparencyLogEntry(t *testing.T) { SignedEntryTimestamp: strfmt.Base64("set"), }, }, - want: rekor_pb.TransparencyLogEntry{ + want: &rekor_pb.TransparencyLogEntry{ LogIndex: 1, LogId: &rekor_pb_common.LogId{ KeyId: deadbeefBytes, @@ -329,7 +329,7 @@ func TestGenerateTransparencyLogEntry(t *testing.T) { t.Fatalf("unexpected result: %v", err) } - if tc.expectSuccess && !reflect.DeepEqual(*e, tc.want) { + if tc.expectSuccess && !reflect.DeepEqual(e, tc.want) { t.Errorf("unexpected value returned; got %v, wanted %v", e, tc.want) } }) From 5226245f0d02bbdf1275d614699a69affa7df056 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Wed, 24 May 2023 18:22:51 -0400 Subject: [PATCH 6/7] add back CLI output option for rekor-cli get Signed-off-by: Bob Callaway --- cmd/rekor-cli/app/format/wrap.go | 13 +++++++++++++ cmd/rekor-cli/app/get.go | 4 ++++ cmd/rekor-cli/app/pflags.go | 2 +- pkg/client/rekor_client.go | 7 +++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/cmd/rekor-cli/app/format/wrap.go b/cmd/rekor-cli/app/format/wrap.go index 5e81c37f6..e431246a9 100644 --- a/cmd/rekor-cli/app/format/wrap.go +++ b/cmd/rekor-cli/app/format/wrap.go @@ -19,6 +19,9 @@ import ( "encoding/json" "fmt" + rekor_pb "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" + + "github.com/sigstore/rekor/pkg/client" "github.com/sigstore/rekor/pkg/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -46,6 +49,16 @@ func WrapCmd(f formatCmd) CobraCmd { } case "json": fmt.Println(toJSON(obj)) + case "tle": + if tle, ok := obj.(*rekor_pb.TransparencyLogEntry); ok { + json, err := client.MarshalTLEToJSON(tle) + if err != nil { + log.CliLogger.Fatalf("error converting to transparency log entry: %v", err) + } + fmt.Println(string(json)) + } else { + log.CliLogger.Fatal("unable to print transparency log entry") + } } } } diff --git a/cmd/rekor-cli/app/get.go b/cmd/rekor-cli/app/get.go index 6a6181341..e76caf1f5 100644 --- a/cmd/rekor-cli/app/get.go +++ b/cmd/rekor-cli/app/get.go @@ -203,6 +203,10 @@ func compareEntryUUIDs(requestEntryUUID string, responseEntryUUID string) error } func parseEntry(uuid string, e models.LogEntryAnon) (interface{}, error) { + if viper.GetString("format") == "tle" { + return client.GenerateTransparencyLogEntry(e) + } + b, err := base64.StdEncoding.DecodeString(e.Body.(string)) if err != nil { return nil, err diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index 1f147cae8..62f4a6cdf 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -111,7 +111,7 @@ func initializePFlagMap() { }, formatFlag: func() pflag.Value { // this validates the output format requested - return valueFactory(formatFlag, validateString("required,oneof=json default"), "") + return valueFactory(formatFlag, validateString("required,oneof=json default tle"), "") }, timeoutFlag: func() pflag.Value { // this validates the timeout is >= 0 diff --git a/pkg/client/rekor_client.go b/pkg/client/rekor_client.go index b3cf3d84c..1f26ceb4e 100644 --- a/pkg/client/rekor_client.go +++ b/pkg/client/rekor_client.go @@ -34,6 +34,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/util" + "google.golang.org/protobuf/encoding/protojson" ) func GetRekorClient(rekorServerURL string, opts ...Option) (*client.Rekor, error) { @@ -73,6 +74,7 @@ func GetRekorClient(rekorServerURL string, opts ...Option) (*client.Rekor, error return client.New(rt, registry), nil } +// GenerateTransparencyLogEntry returns a sigstore/protobuf-specs compliant func GenerateTransparencyLogEntry(anon models.LogEntryAnon) (*rekor_pb.TransparencyLogEntry, error) { logIDHash, err := hex.DecodeString(*anon.LogID) if err != nil { @@ -132,3 +134,8 @@ func GenerateTransparencyLogEntry(anon models.LogEntryAnon) (*rekor_pb.Transpare CanonicalizedBody: b, // we don't call eimpl.Canonicalize in the case that the logic is different in this caller vs when it was persisted in the log }, nil } + +// MarshalTLEToJSON marshals a TransparencyLogEntry message to JSON according to the protobuf JSON encoding rules +func MarshalTLEToJSON(tle *rekor_pb.TransparencyLogEntry) ([]byte, error) { + return protojson.Marshal(tle) +} From 9be41663c2b4aea846b2507aee76bf4b927c25b7 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Wed, 24 May 2023 19:32:40 -0400 Subject: [PATCH 7/7] fix func comment Signed-off-by: Bob Callaway --- pkg/client/rekor_client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/client/rekor_client.go b/pkg/client/rekor_client.go index 1f26ceb4e..206eb87ac 100644 --- a/pkg/client/rekor_client.go +++ b/pkg/client/rekor_client.go @@ -74,7 +74,8 @@ func GetRekorClient(rekorServerURL string, opts ...Option) (*client.Rekor, error return client.New(rt, registry), nil } -// GenerateTransparencyLogEntry returns a sigstore/protobuf-specs compliant +// GenerateTransparencyLogEntry returns a sigstore/protobuf-specs compliant message containing a +// TransparencyLogEntry as defined at https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_rekor.proto func GenerateTransparencyLogEntry(anon models.LogEntryAnon) (*rekor_pb.TransparencyLogEntry, error) { logIDHash, err := hex.DecodeString(*anon.LogID) if err != nil {