Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UNTESTED: Replace sigstore/rekor/pkg/client with a manually-created client #1845

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 1 addition & 24 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ require (
github.com/docker/docker v24.0.7+incompatible
github.com/docker/docker-credential-helpers v0.8.0
github.com/docker/go-connections v0.4.0
github.com/go-openapi/strfmt v0.21.7
github.com/go-openapi/swag v0.22.4
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-retryablehttp v0.7.5
github.com/klauspost/compress v1.17.3
Expand All @@ -31,7 +29,6 @@ require (
github.com/proglottis/gpgme v0.1.3
github.com/secure-systems-lab/go-securesystemslib v0.7.0
github.com/sigstore/fulcio v1.4.3
github.com/sigstore/rekor v1.2.2
github.com/sigstore/sigstore v1.7.5
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
Expand All @@ -56,7 +53,6 @@ require (
github.com/Microsoft/hcsshim v0.12.0-rc.1 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
Expand All @@ -70,16 +66,6 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.4 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/runtime v0.26.0 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/validate v0.22.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
Expand All @@ -89,25 +75,21 @@ require (
github.com/gorilla/mux v1.8.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/letsencrypt/boulder v0.0.0-20230213213521-fdfea0d469b6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/sys/mountinfo v0.7.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opencontainers/runc v1.1.10 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect
Expand All @@ -123,12 +105,8 @@ require (
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
go.mongodb.org/mongo-driver v1.11.3 // indirect
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/otel/trace v1.16.0 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
Expand All @@ -139,6 +117,5 @@ require (
google.golang.org/grpc v1.58.3 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gotest.tools/v3 v3.5.0 // indirect
)
203 changes: 0 additions & 203 deletions go.sum

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions signature/internal/rekor_api_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package internal

import (
"bytes"
"encoding/json"
"fmt"
)

const rekorHashedrekordKind = "hashedrekord"

type RekorHashedrekord struct {
APIVersion *string `json:"apiVersion"`
Spec json.RawMessage `json:"spec"`
}

func (m *RekorHashedrekord) Kind() string {
return rekorHashedrekordKind
}

func (m *RekorHashedrekord) SetKind(val string) {
}

func (m *RekorHashedrekord) UnmarshalJSON(raw []byte) error {
var base struct {
Kind string `json:"kind"`
}
dec := json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber()
if err := dec.Decode(&base); err != nil {
return err
}

switch base.Kind {
case rekorHashedrekordKind:
var data struct { // We can’t use RekorHashedRekord directly, because that would be an infinite recursion.
APIVersion *string `json:"apiVersion"`
Spec json.RawMessage `json:"spec"`
}
dec = json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber()
if err := dec.Decode(&data); err != nil {
return err
}
res := RekorHashedrekord{
APIVersion: data.APIVersion,
Spec: data.Spec,
}
*m = res
return nil

default:
return fmt.Errorf("invalid kind value: %q", base.Kind)
}
}

func (m RekorHashedrekord) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Kind string `json:"kind"`
APIVersion *string `json:"apiVersion"`
Spec json.RawMessage `json:"spec"`
}{
Kind: m.Kind(),
APIVersion: m.APIVersion,
Spec: m.Spec,
})
}

type RekorHashedrekordV001Schema struct {
Data *RekorHashedrekordV001SchemaData `json:"data"`
Signature *RekorHashedrekordV001SchemaSignature `json:"signature"`
}

type RekorHashedrekordV001SchemaData struct {
Hash *RekorHashedrekordV001SchemaDataHash `json:"hash,omitempty"`
}

type RekorHashedrekordV001SchemaDataHash struct {
Algorithm *string `json:"algorithm"`
Value *string `json:"value"`
}

const (
RekorHashedrekordV001SchemaDataHashAlgorithmSha256 string = "sha256"
)

type RekorHashedrekordV001SchemaSignature struct {
Content []byte `json:"content,omitempty"`
PublicKey *RekorHashedrekordV001SchemaSignaturePublicKey `json:"publicKey,omitempty"`
}

type RekorHashedrekordV001SchemaSignaturePublicKey struct {
Content []byte `json:"content,omitempty"`
}
28 changes: 8 additions & 20 deletions signature/internal/rekor_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ import (
"time"

"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
"github.com/sigstore/rekor/pkg/generated/models"
)

// This is the github.com/sigstore/rekor/pkg/generated/models.Hashedrekord.APIVersion for github.com/sigstore/rekor/pkg/generated/models.HashedrekordV001Schema.
// We could alternatively use github.com/sigstore/rekor/pkg/types/hashedrekord.APIVERSION, but that subpackage adds too many dependencies.
const HashedRekordV001APIVersion = "0.0.1"
const RekorHashedRekordV001APIVersion = "0.0.1"

// UntrustedRekorSET is a parsed content of the sigstore-signature Rekor SET
// (note that this a signature-specific format, not a format directly used by the Rekor API).
Expand Down Expand Up @@ -140,31 +139,20 @@ func VerifyRekorSET(publicKey *ecdsa.PublicKey, unverifiedRekorSET []byte, unver
if err := json.Unmarshal(untrustedSETPayloadCanonicalBytes, &rekorPayload); err != nil {
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf("parsing Rekor SET payload: %v", err.Error()))
}
// FIXME: Use a different decoder implementation? The Swagger-generated code is kinda ridiculous, with the need to re-marshal
// hashedRekor.Spec and so on.
// Especially if we anticipate needing to decode different data formats…
// That would also allow being much more strict about JSON.
//
// Alternatively, rely on the existing .Validate() methods instead of manually checking for nil all over the place.
var hashedRekord models.Hashedrekord
// FIXME: Consider being much more strict about decoding JSON.
var hashedRekord RekorHashedrekord
if err := json.Unmarshal(rekorPayload.Body, &hashedRekord); err != nil {
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf("decoding the body of a Rekor SET payload: %v", err))
}
// The decode of models.HashedRekord validates the "kind": "hashedrecord" field, which is otherwise invisible to us.
// The decode of HashedRekord validates the "kind": "hashedrecord" field, which is otherwise invisible to us.
if hashedRekord.APIVersion == nil {
return time.Time{}, NewInvalidSignatureError("missing Rekor SET Payload API version")
}
if *hashedRekord.APIVersion != HashedRekordV001APIVersion {
if *hashedRekord.APIVersion != RekorHashedRekordV001APIVersion {
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf("unsupported Rekor SET Payload hashedrekord version %#v", hashedRekord.APIVersion))
}
hashedRekordV001Bytes, err := json.Marshal(hashedRekord.Spec)
if err != nil {
// Coverage: hashedRekord.Spec is an any that was just unmarshaled,
// so this should never fail.
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf("re-creating hashedrekord spec: %v", err))
}
var hashedRekordV001 models.HashedrekordV001Schema
if err := json.Unmarshal(hashedRekordV001Bytes, &hashedRekordV001); err != nil {
var hashedRekordV001 RekorHashedrekordV001Schema
if err := json.Unmarshal(hashedRekord.Spec, &hashedRekordV001); err != nil {
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf("decoding hashedrekod spec: %v", err))
}

Expand Down Expand Up @@ -216,7 +204,7 @@ func VerifyRekorSET(publicKey *ecdsa.PublicKey, unverifiedRekorSET []byte, unver
if hashedRekordV001.Data.Hash.Algorithm == nil {
return time.Time{}, NewInvalidSignatureError(`Missing "data.hash.algorithm" field in hashedrekord`)
}
if *hashedRekordV001.Data.Hash.Algorithm != models.HashedrekordV001SchemaDataHashAlgorithmSha256 {
if *hashedRekordV001.Data.Hash.Algorithm != RekorHashedrekordV001SchemaDataHashAlgorithmSha256 {
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf(`Unexpected "data.hash.algorithm" value %#v`, *hashedRekordV001.Data.Hash.Algorithm))
}
if hashedRekordV001.Data.Hash.Value == nil {
Expand Down
36 changes: 20 additions & 16 deletions signature/internal/rekor_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ import (
"testing"
"time"

"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/sigstore/pkg/cryptoutils"
sigstoreSignature "github.com/sigstore/sigstore/pkg/signature"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -171,6 +168,11 @@ func TestUntrustedRekorPayloadUnmarshalJSON(t *testing.T) {
}
}

// stringPointer is a helper to create *string fields in JSON data.
func stringPointer(s string) *string {
return &s
}

func TestVerifyRekorSET(t *testing.T) {
cosignRekorKeyPEM, err := os.ReadFile("testdata/rekor.pub")
require.NoError(t, err)
Expand Down Expand Up @@ -246,22 +248,24 @@ func TestVerifyRekorSET(t *testing.T) {
cosignPayloadSHA256 := sha256.Sum256(cosignPayloadBytes)
cosignSigBytes, err := base64.StdEncoding.DecodeString(string(cosignSigBase64))
require.NoError(t, err)
validHashedRekord := models.Hashedrekord{
APIVersion: swag.String(HashedRekordV001APIVersion),
Spec: models.HashedrekordV001Schema{
Data: &models.HashedrekordV001SchemaData{
Hash: &models.HashedrekordV001SchemaDataHash{
Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256),
Value: swag.String(hex.EncodeToString(cosignPayloadSHA256[:])),
},
validHashedRekordSpec, err := json.Marshal(RekorHashedrekordV001Schema{
Data: &RekorHashedrekordV001SchemaData{
Hash: &RekorHashedrekordV001SchemaDataHash{
Algorithm: stringPointer(RekorHashedrekordV001SchemaDataHashAlgorithmSha256),
Value: stringPointer(hex.EncodeToString(cosignPayloadSHA256[:])),
},
Signature: &models.HashedrekordV001SchemaSignature{
Content: strfmt.Base64(cosignSigBytes),
PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{
Content: strfmt.Base64(cosignCertBytes),
},
},
Signature: &RekorHashedrekordV001SchemaSignature{
Content: cosignSigBytes,
PublicKey: &RekorHashedrekordV001SchemaSignaturePublicKey{
Content: cosignCertBytes,
},
},
})
require.NoError(t, err)
validHashedRekord := RekorHashedrekord{
APIVersion: stringPointer(RekorHashedRekordV001APIVersion),
Spec: validHashedRekordSpec,
}
validHashedRekordJSON, err := json.Marshal(validHashedRekord)
require.NoError(t, err)
Expand Down
79 changes: 79 additions & 0 deletions signature/sigstore/rekor/openapi_infra.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package rekor

// The following code is the essence of the relevant code paths from github.com/go-openapi/runtime,
// heavily modified since.

// Copyright 2015 go-swagger maintainers
//
// 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.

import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"path"
)

// makeRequest makes a http request to the requested requestPath, and returns the received response.
func (r *rekorClient) makeRequest(ctx context.Context, method, requestPath string, bodyContent any) (*http.Response, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

var body io.Reader
headers := http.Header{}

headers.Set("Accept", "application/json")
if bodyContent != nil {
buf := bytes.NewBuffer(nil)
body = buf
headers.Set("Content-Type", "application/json")
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(bodyContent); err != nil {
return nil, err
}
}

req, err := http.NewRequestWithContext(ctx, method, path.Join(r.basePath, requestPath), body)
if err != nil {
return nil, err
}
// Only Scheme and Host are used from rekorURL.
// Really this should probabbly use r.rekorURL.JoinPath(requestPath) (which, notably, correctly deals with path escaping),
// and pass that to NewRequestWithContext, but this use of path.Join is consistent with go-openapi/runtime v0.24.1 .
req.URL.Scheme = r.rekorURL.Scheme
req.URL.Host = r.rekorURL.Host
req.Header = headers

res, err := r.httpClient.Do(req)
if err != nil {
return nil, err
}
// Note that we don’t care to even read the Content-Type: header; we blindly assume the format is the requested JSON.
return res, nil
}

// decodeHTTPResponseBodyAsJSON decodes the body of a HTTP response in a manner compatible with github.com/go-openapi/runtime.
func decodeHTTPResponseBodyAsJSON(res *http.Response, data any) error {
dec := json.NewDecoder(res.Body)
dec.UseNumber()
err := dec.Decode(data)
if err == io.EOF {
// This seems unwanted at a first glance; go-swagger added it in https://github.com/go-swagger/go-swagger/issues/192 , it’s unclear
// whether it’s correct or still necessary.
err = nil
}
return err
}