From 45a5ce849d09daeeae1053b7e64497a458af7361 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Mon, 21 Jun 2021 09:10:44 -0400 Subject: [PATCH 1/8] Refactor PKI factory and add type checking This allows for more DRY addition of new PKI types, and stricter type checking. This also allows for simpler enumeration of supported PKI formats which will be used in further updates to simplify the CLI codebase. Signed-off-by: Bob Callaway --- pkg/api/error.go | 1 + pkg/api/index.go | 7 +- pkg/pki/factory/factory.go | 87 +++++++++++++++++++++ pkg/pki/factory/factory_test.go | 121 ++++++++++++++++++++++++++++++ pkg/pki/minisign/minisign.go | 7 +- pkg/pki/minisign/minisign_test.go | 4 - pkg/pki/pgp/pgp.go | 11 +-- pkg/pki/pgp/pgp_test.go | 4 - pkg/pki/pkcs7/pkcs7.go | 7 +- pkg/pki/pki.go | 52 +------------ pkg/pki/pki_test.go | 108 -------------------------- pkg/pki/ssh/ssh.go | 9 ++- pkg/pki/x509/x509.go | 10 ++- pkg/types/intoto/v0.0.1/entry.go | 13 +++- pkg/types/jar/v0.0.1/entry.go | 6 +- pkg/types/rekord/v0.0.1/entry.go | 6 +- pkg/types/rpm/v0.0.1/entry.go | 6 +- 17 files changed, 266 insertions(+), 193 deletions(-) create mode 100644 pkg/pki/factory/factory.go create mode 100644 pkg/pki/factory/factory_test.go delete mode 100644 pkg/pki/pki_test.go diff --git a/pkg/api/error.go b/pkg/api/error.go index 9fd054354..bff642cab 100644 --- a/pkg/api/error.go +++ b/pkg/api/error.go @@ -46,6 +46,7 @@ const ( signingError = "Error signing" failedToGenerateTimestampResponse = "Error generating timestamp response" sthGenerateError = "Error generating signed tree head" + unsupportedPKIFormat = "The PKI format requested is not supported by this server" ) func errorMsg(message string, code int) *models.Error { diff --git a/pkg/api/index.go b/pkg/api/index.go index 5815545a9..f5ed59624 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -28,7 +28,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/generated/restapi/operations/index" - "github.com/sigstore/rekor/pkg/pki" + pkifactory "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/util" ) @@ -45,7 +45,10 @@ func SearchIndexHandler(params index.SearchIndexParams) middleware.Responder { result = append(result, resultUUIDs...) } if params.Query.PublicKey != nil { - af := pki.NewArtifactFactory(swag.StringValue(params.Query.PublicKey.Format)) + af, err := pkifactory.NewArtifactFactory(swag.StringValue(params.Query.PublicKey.Format)) + if err != nil { + return handleRekorAPIError(params, http.StatusBadRequest, err, unsupportedPKIFormat) + } keyReader, err := util.FileOrURLReadCloser(httpReqCtx, params.Query.PublicKey.URL.String(), params.Query.PublicKey.Content) if err != nil { return handleRekorAPIError(params, http.StatusBadRequest, err, malformedPublicKey) diff --git a/pkg/pki/factory/factory.go b/pkg/pki/factory/factory.go new file mode 100644 index 000000000..5fb99e90b --- /dev/null +++ b/pkg/pki/factory/factory.go @@ -0,0 +1,87 @@ +// +// Copyright 2021 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 factory + +import ( + "fmt" + "io" + + "github.com/sigstore/rekor/pkg/pki" + "github.com/sigstore/rekor/pkg/pki/minisign" + "github.com/sigstore/rekor/pkg/pki/pgp" + "github.com/sigstore/rekor/pkg/pki/pkcs7" + "github.com/sigstore/rekor/pkg/pki/ssh" + "github.com/sigstore/rekor/pkg/pki/x509" +) + +type ArtifactFactory struct { + impl pkiImpl +} + +func NewArtifactFactory(format string) (*ArtifactFactory, error) { + if impl, ok := artifactFactoryMap[format]; ok { + return &ArtifactFactory{impl: impl}, nil + } + return nil, fmt.Errorf("%v is not a supported PKI format", format) +} + +type pkiImpl struct { + newPubKey func(io.Reader) (pki.PublicKey, error) + newSignature func(io.Reader) (pki.Signature, error) +} + +var artifactFactoryMap map[string]pkiImpl + +func init() { + artifactFactoryMap = map[string]pkiImpl{ + "pgp": { + newPubKey: pgp.NewPublicKey, + newSignature: pgp.NewSignature, + }, + "minisign": { + newPubKey: minisign.NewPublicKey, + newSignature: minisign.NewSignature, + }, + "ssh": { + newPubKey: ssh.NewPublicKey, + newSignature: ssh.NewSignature, + }, + "x509": { + newPubKey: x509.NewPublicKey, + newSignature: x509.NewSignature, + }, + "pkcs7": { + newPubKey: pkcs7.NewPublicKey, + newSignature: pkcs7.NewSignature, + }, + } +} + +func SupportedFormats() []string { + var formats []string + for f := range artifactFactoryMap { + formats = append(formats, f) + } + return formats +} + +func (a ArtifactFactory) NewPublicKey(r io.Reader) (pki.PublicKey, error) { + return a.impl.newPubKey(r) +} + +func (a ArtifactFactory) NewSignature(r io.Reader) (pki.Signature, error) { + return a.impl.newSignature(r) +} diff --git a/pkg/pki/factory/factory_test.go b/pkg/pki/factory/factory_test.go new file mode 100644 index 000000000..7e914b411 --- /dev/null +++ b/pkg/pki/factory/factory_test.go @@ -0,0 +1,121 @@ +// +// Copyright 2021 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 factory + +import ( + "os" + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + +func TestFactoryNewKey(t *testing.T) { + type TestCase struct { + name string + format string + keyFile string + sigFile string + expectSuccess bool + expectValidFormat bool + } + + testCases := []TestCase{ + { + name: "valid pgp", + format: "pgp", + keyFile: "../pgp/testdata/valid_armored_public.pgp", + sigFile: "../pgp/testdata/hello_world.txt.asc.sig", + expectSuccess: true, + expectValidFormat: true, + }, + { + name: "valid minisign", + format: "minisign", + keyFile: "../minisign/testdata/minisign.pub", + sigFile: "../minisign/testdata/hello_world.txt.minisig", + expectSuccess: true, + expectValidFormat: true, + }, + { + name: "valid x509", + format: "x509", + keyFile: "../x509/testdata/ec.pub", + sigFile: "../x509/testdata/hello_world.txt.sig", + expectSuccess: true, + expectValidFormat: true, + }, + { + name: "valid ssh", + format: "ssh", + keyFile: "../ssh/testdata/id_rsa.pub", + sigFile: "../ssh/testdata/hello_world.txt.sig", + expectSuccess: true, + expectValidFormat: true, + }, + { + name: "invalid ssh signature", + format: "ssh", + keyFile: "../ssh/testdata/id_rsa.pub", + sigFile: "../ssh/testdata/hello_world.txt", + expectSuccess: false, + expectValidFormat: true, + }, + { + name: "invalid ssh key", + format: "ssh", + keyFile: "../ssh/testdata/hello_world.txt", + sigFile: "../ssh/testdata/hello_world.txt.sig", + expectSuccess: false, + expectValidFormat: true, + }, + { + format: "bogus", + expectSuccess: false, + expectValidFormat: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + factory, err := NewArtifactFactory(tc.format) + if tc.expectValidFormat != (err == nil) { + t.Fatalf("unexpected error initializing factory for %v", tc.format) + } + if factory != nil { + keyFile, _ := os.Open(tc.keyFile) + _, newKeyErr := factory.NewPublicKey(keyFile) + + sigFile, _ := os.Open(tc.sigFile) + _, newSigErr := factory.NewSignature(sigFile) + + if tc.expectSuccess { + if newKeyErr != nil || newSigErr != nil { + t.Errorf("unexpected error generating public key %v or signature %v", newKeyErr, newSigErr) + } + } else { // expect a failure{ + if newKeyErr == nil && newSigErr == nil { + t.Error("expected error generating public key and signature. got none") + } + } + } + }) + } +} diff --git a/pkg/pki/minisign/minisign.go b/pkg/pki/minisign/minisign.go index 22bc3b056..4f1deab28 100644 --- a/pkg/pki/minisign/minisign.go +++ b/pkg/pki/minisign/minisign.go @@ -25,6 +25,7 @@ import ( "strings" minisign "github.com/jedisct1/go-minisign" + "github.com/sigstore/rekor/pkg/pki" ) // Signature Signature that follows the minisign standard; supports both minisign and signify generated signatures @@ -33,7 +34,7 @@ type Signature struct { } // NewSignature creates and validates a minisign signature object -func NewSignature(r io.Reader) (*Signature, error) { +func NewSignature(r io.Reader) (pki.Signature, error) { var s Signature var inputBuffer bytes.Buffer @@ -86,7 +87,7 @@ func (s Signature) CanonicalValue() ([]byte, error) { } // Verify implements the pki.Signature interface -func (s Signature) Verify(r io.Reader, k interface{}) error { +func (s Signature) Verify(r io.Reader, k pki.PublicKey) error { if s.signature == nil { return fmt.Errorf("minisign signature has not been initialized") } @@ -117,7 +118,7 @@ type PublicKey struct { } // NewPublicKey implements the pki.PublicKey interface -func NewPublicKey(r io.Reader) (*PublicKey, error) { +func NewPublicKey(r io.Reader) (pki.PublicKey, error) { var k PublicKey var inputBuffer bytes.Buffer diff --git a/pkg/pki/minisign/minisign_test.go b/pkg/pki/minisign/minisign_test.go index f7aecd94f..a054aba6f 100644 --- a/pkg/pki/minisign/minisign_test.go +++ b/pkg/pki/minisign/minisign_test.go @@ -301,8 +301,4 @@ func TestVerifySignature(t *testing.T) { if err := validSig.Verify(bytes.NewReader([]byte("irrelevant")), &emptyKey); err == nil { t.Errorf("expected error when using empty key to verify") } - - if err := validSig.Verify(bytes.NewReader([]byte("irrelevant")), sigFile); err == nil { - t.Errorf("expected error when using non key to verify") - } } diff --git a/pkg/pki/pgp/pgp.go b/pkg/pki/pgp/pgp.go index 7e3c7bba0..2ce9eedfd 100644 --- a/pkg/pki/pgp/pgp.go +++ b/pkg/pki/pgp/pgp.go @@ -25,6 +25,7 @@ import ( "net/http" "github.com/go-playground/validator" + "github.com/sigstore/rekor/pkg/pki" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/packet" @@ -37,7 +38,7 @@ type Signature struct { } // NewSignature creates and validates a PGP signature object -func NewSignature(r io.Reader) (*Signature, error) { +func NewSignature(r io.Reader) (pki.Signature, error) { var s Signature var inputBuffer bytes.Buffer @@ -80,7 +81,7 @@ func NewSignature(r io.Reader) (*Signature, error) { } // FetchSignature implements pki.Signature interface -func FetchSignature(ctx context.Context, url string) (*Signature, error) { +func FetchSignature(ctx context.Context, url string) (pki.Signature, error) { req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, fmt.Errorf("error initializing fetch for PGP signature: %w", err) @@ -130,7 +131,7 @@ func (s Signature) CanonicalValue() ([]byte, error) { } // Verify implements the pki.Signature interface -func (s Signature) Verify(r io.Reader, k interface{}) error { +func (s Signature) Verify(r io.Reader, k pki.PublicKey) error { if len(s.signature) == 0 { return fmt.Errorf("PGP signature has not been initialized") } @@ -161,7 +162,7 @@ type PublicKey struct { } // NewPublicKey implements the pki.PublicKey interface -func NewPublicKey(r io.Reader) (*PublicKey, error) { +func NewPublicKey(r io.Reader) (pki.PublicKey, error) { var k PublicKey var inputBuffer bytes.Buffer @@ -221,7 +222,7 @@ func NewPublicKey(r io.Reader) (*PublicKey, error) { } // FetchPublicKey implements pki.PublicKey interface -func FetchPublicKey(ctx context.Context, url string) (*PublicKey, error) { +func FetchPublicKey(ctx context.Context, url string) (pki.PublicKey, error) { //TODO: detect if url is hkp and adjust accordingly req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { diff --git a/pkg/pki/pgp/pgp_test.go b/pkg/pki/pgp/pgp_test.go index 17c0b3406..924d00e1e 100644 --- a/pkg/pki/pgp/pgp_test.go +++ b/pkg/pki/pgp/pgp_test.go @@ -455,8 +455,4 @@ func TestVerifySignature(t *testing.T) { if err := validSig.Verify(bytes.NewReader([]byte("irrelevant")), &emptyKey); err == nil { t.Errorf("expected error when using empty key to verify") } - - if err := validSig.Verify(bytes.NewReader([]byte("irrelevant")), sigFile); err == nil { - t.Errorf("expected error when using non key to verify") - } } diff --git a/pkg/pki/pkcs7/pkcs7.go b/pkg/pki/pkcs7/pkcs7.go index 8e842621b..8d0a45958 100644 --- a/pkg/pki/pkcs7/pkcs7.go +++ b/pkg/pki/pkcs7/pkcs7.go @@ -29,6 +29,7 @@ import ( "strings" "github.com/sassoftware/relic/lib/pkcs7" + "github.com/sigstore/rekor/pkg/pki" ) // EmailAddressOID defined by https://oidref.com/1.2.840.113549.1.9.1 @@ -41,7 +42,7 @@ type Signature struct { } // NewSignature creates and validates an PKCS7 signature object -func NewSignature(r io.Reader) (*Signature, error) { +func NewSignature(r io.Reader) (pki.Signature, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err @@ -105,7 +106,7 @@ func (s Signature) CanonicalValue() ([]byte, error) { } // Verify implements the pki.Signature interface -func (s Signature) Verify(r io.Reader, k interface{}) error { +func (s Signature) Verify(r io.Reader, k pki.PublicKey) error { if len(*s.raw) == 0 { return fmt.Errorf("PKCS7 signature has not been initialized") } @@ -140,7 +141,7 @@ type PublicKey struct { } // NewPublicKey implements the pki.PublicKey interface -func NewPublicKey(r io.Reader) (*PublicKey, error) { +func NewPublicKey(r io.Reader) (pki.PublicKey, error) { rawPub, err := ioutil.ReadAll(r) if err != nil { return nil, err diff --git a/pkg/pki/pki.go b/pkg/pki/pki.go index 446383875..3e110467e 100644 --- a/pkg/pki/pki.go +++ b/pkg/pki/pki.go @@ -16,15 +16,7 @@ package pki import ( - "fmt" "io" - "strings" - - "github.com/sigstore/rekor/pkg/pki/minisign" - "github.com/sigstore/rekor/pkg/pki/pgp" - "github.com/sigstore/rekor/pkg/pki/pkcs7" - "github.com/sigstore/rekor/pkg/pki/ssh" - "github.com/sigstore/rekor/pkg/pki/x509" ) // PublicKey Generic object representing a public key (regardless of format & algorithm) @@ -36,47 +28,5 @@ type PublicKey interface { // Signature Generic object representing a signature (regardless of format & algorithm) type Signature interface { CanonicalValue() ([]byte, error) - Verify(r io.Reader, k interface{}) error -} - -type ArtifactFactory struct { - format string -} - -func NewArtifactFactory(format string) *ArtifactFactory { - return &ArtifactFactory{ - format: format, - } -} - -func (a ArtifactFactory) NewPublicKey(r io.Reader) (PublicKey, error) { - switch strings.ToLower(a.format) { - case "pgp": - return pgp.NewPublicKey(r) - case "minisign": - return minisign.NewPublicKey(r) - case "x509": - return x509.NewPublicKey(r) - case "ssh": - return ssh.NewPublicKey(r) - case "pkcs7": - return pkcs7.NewPublicKey(r) - } - return nil, fmt.Errorf("unknown key format '%v'", a.format) -} - -func (a ArtifactFactory) NewSignature(r io.Reader) (Signature, error) { - switch strings.ToLower(a.format) { - case "pgp": - return pgp.NewSignature(r) - case "minisign": - return minisign.NewSignature(r) - case "x509": - return x509.NewSignature(r) - case "ssh": - return ssh.NewSignature(r) - case "pkcs7": - return pkcs7.NewSignature(r) - } - return nil, fmt.Errorf("unknown key format '%v'", a.format) + Verify(r io.Reader, k PublicKey) error } diff --git a/pkg/pki/pki_test.go b/pkg/pki/pki_test.go deleted file mode 100644 index 0454a3eb9..000000000 --- a/pkg/pki/pki_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// -// Copyright 2021 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 pki - -import ( - "os" - "testing" - - "go.uber.org/goleak" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m) -} - -func TestFactoryNewKey(t *testing.T) { - type TestCase struct { - name string - format string - keyFile string - sigFile string - expectSuccess bool - } - - testCases := []TestCase{ - { - name: "valid pgp", - format: "pgp", - keyFile: "pgp/testdata/valid_armored_public.pgp", - sigFile: "pgp/testdata/hello_world.txt.asc.sig", - expectSuccess: true, - }, - { - name: "valid minisign", - format: "minisign", - keyFile: "minisign/testdata/minisign.pub", - sigFile: "minisign/testdata/hello_world.txt.minisig", - expectSuccess: true, - }, - { - name: "valid x509", - format: "x509", - keyFile: "x509/testdata/ec.pub", - sigFile: "x509/testdata/hello_world.txt.sig", - expectSuccess: true, - }, - { - name: "valid ssh", - format: "ssh", - keyFile: "ssh/testdata/id_rsa.pub", - sigFile: "ssh/testdata/hello_world.txt.sig", - expectSuccess: true, - }, - { - name: "invalid ssh signature", - format: "ssh", - keyFile: "ssh/testdata/id_rsa.pub", - sigFile: "ssh/testdata/hello_world.txt", - expectSuccess: false, - }, - { - name: "invalid ssh key", - format: "ssh", - keyFile: "ssh/testdata/hello_world.txt", - sigFile: "ssh/testdata/hello_world.txt.sig", - expectSuccess: false, - }, - { - format: "bogus", - expectSuccess: false, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - factory := NewArtifactFactory(tc.format) - keyFile, _ := os.Open(tc.keyFile) - _, newKeyErr := factory.NewPublicKey(keyFile) - - sigFile, _ := os.Open(tc.sigFile) - _, newSigErr := factory.NewSignature(sigFile) - - if tc.expectSuccess { - if newKeyErr != nil || newSigErr != nil { - t.Errorf("unexpected error generating public key %v or signature %v", newKeyErr, newSigErr) - } - } else { // expect a failure{ - if newKeyErr == nil && newSigErr == nil { - t.Error("expected error generating public key and signature. got none") - } - } - }) - } -} diff --git a/pkg/pki/ssh/ssh.go b/pkg/pki/ssh/ssh.go index 847443185..0c9c66645 100644 --- a/pkg/pki/ssh/ssh.go +++ b/pkg/pki/ssh/ssh.go @@ -20,6 +20,7 @@ import ( "io" "io/ioutil" + "github.com/sigstore/rekor/pkg/pki" "golang.org/x/crypto/ssh" ) @@ -30,7 +31,7 @@ type Signature struct { } // NewSignature creates and Validates an ssh signature object -func NewSignature(r io.Reader) (*Signature, error) { +func NewSignature(r io.Reader) (pki.Signature, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err @@ -48,14 +49,14 @@ func (s Signature) CanonicalValue() ([]byte, error) { } // Verify implements the pki.Signature interface -func (s Signature) Verify(r io.Reader, k interface{}) error { +func (s Signature) Verify(r io.Reader, k pki.PublicKey) error { if s.signature == nil { return fmt.Errorf("ssh signature has not been initialized") } key, ok := k.(*PublicKey) if !ok { - return fmt.Errorf("Invalid public key type for: %v", k) + return fmt.Errorf("invalid public key type for: %v", k) } ck, err := key.CanonicalValue() @@ -75,7 +76,7 @@ type PublicKey struct { } // NewPublicKey implements the pki.PublicKey interface -func NewPublicKey(r io.Reader) (*PublicKey, error) { +func NewPublicKey(r io.Reader) (pki.PublicKey, error) { rawPub, err := ioutil.ReadAll(r) if err != nil { return nil, err diff --git a/pkg/pki/x509/x509.go b/pkg/pki/x509/x509.go index 7bf2922fc..bf7bccda2 100644 --- a/pkg/pki/x509/x509.go +++ b/pkg/pki/x509/x509.go @@ -32,6 +32,7 @@ import ( "strings" "github.com/go-playground/validator" + "github.com/sigstore/rekor/pkg/pki" ) // EmailAddressOID defined by https://oidref.com/1.2.840.113549.1.9.1 @@ -42,7 +43,7 @@ type Signature struct { } // NewSignature creates and validates an x509 signature object -func NewSignature(r io.Reader) (*Signature, error) { +func NewSignature(r io.Reader) (pki.Signature, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err @@ -58,8 +59,9 @@ func (s Signature) CanonicalValue() ([]byte, error) { } // Verify implements the pki.Signature interface -func (s Signature) Verify(r io.Reader, k interface{}) error { +func (s Signature) Verify(r io.Reader, k pki.PublicKey) error { if len(s.signature) == 0 { + //lint:ignore ST1005 X509 is correct capitalization return fmt.Errorf("X509 signature has not been initialized") } @@ -73,7 +75,7 @@ func (s Signature) Verify(r io.Reader, k interface{}) error { key, ok := k.(*PublicKey) if !ok { - return fmt.Errorf("Invalid public key type for: %v", k) + return fmt.Errorf("invalid public key type for: %v", k) } p := key.key @@ -111,7 +113,7 @@ type cert struct { } // NewPublicKey implements the pki.PublicKey interface -func NewPublicKey(r io.Reader) (*PublicKey, error) { +func NewPublicKey(r io.Reader) (pki.PublicKey, error) { rawPub, err := ioutil.ReadAll(r) if err != nil { return nil, err diff --git a/pkg/types/intoto/v0.0.1/entry.go b/pkg/types/intoto/v0.0.1/entry.go index 3ebd86bea..cd9f3b132 100644 --- a/pkg/types/intoto/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.1/entry.go @@ -34,6 +34,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki" + pkifactory "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/intoto" ) @@ -88,7 +89,11 @@ func (v *V001Entry) Unmarshal(pe models.ProposedEntry) error { } // Only support x509 signatures for intoto attestations - af := pki.NewArtifactFactory("x509") + af, err := pkifactory.NewArtifactFactory("x509") + if err != nil { + return err + } + v.keyObj, err = af.NewPublicKey(bytes.NewReader(*v.IntotoObj.PublicKey)) if err != nil { return err @@ -188,7 +193,11 @@ func (v *verifier) Sign(d []byte) ([]byte, string, error) { } func (v *verifier) Verify(keyID string, data, sig []byte) (bool, error) { - af := pki.NewArtifactFactory("x509") + af, err := pkifactory.NewArtifactFactory("x509") + if err != nil { + return false, err + } + s, err := af.NewSignature(bytes.NewReader(sig)) if err != nil { return false, err diff --git a/pkg/types/jar/v0.0.1/entry.go b/pkg/types/jar/v0.0.1/entry.go index 1f4f8215b..6f2e239ca 100644 --- a/pkg/types/jar/v0.0.1/entry.go +++ b/pkg/types/jar/v0.0.1/entry.go @@ -31,6 +31,7 @@ import ( "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki" + pkifactory "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/jar" "github.com/sigstore/rekor/pkg/util" @@ -178,7 +179,10 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { } v.jarObj = jarObj[0] - af := pki.NewArtifactFactory("pkcs7") + af, err := pkifactory.NewArtifactFactory("pkcs7") + if err != nil { + return err + } // we need to find and extract the PKCS7 bundle from the JAR file manually sigPKCS7, err := extractPKCS7SignatureFromJAR(zipReader) if err != nil { diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index 5373f1357..36b7ca4af 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -33,6 +33,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki" + pkifactory "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/rekord" "github.com/sigstore/rekor/pkg/util" @@ -161,7 +162,10 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { if v.RekordObj.Data.Hash != nil && v.RekordObj.Data.Hash.Value != nil { oldSHA = swag.StringValue(v.RekordObj.Data.Hash.Value) } - artifactFactory := pki.NewArtifactFactory(v.RekordObj.Signature.Format) + artifactFactory, err := pkifactory.NewArtifactFactory(v.RekordObj.Signature.Format) + if err != nil { + return err + } g.Go(func() error { defer hashW.Close() diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index f8d31cd9a..5bcb5231c 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -36,6 +36,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki" + pkifactory "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/pki/pgp" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/rpm" @@ -163,7 +164,10 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { if v.RPMModel.Package.Hash != nil && v.RPMModel.Package.Hash.Value != nil { oldSHA = swag.StringValue(v.RPMModel.Package.Hash.Value) } - artifactFactory := pki.NewArtifactFactory("pgp") + artifactFactory, err := pkifactory.NewArtifactFactory("pgp") + if err != nil { + return err + } g.Go(func() error { defer hashW.Close() From 58498c066aab1b86ef928af2dd2edd4b268e8a8d Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Wed, 23 Jun 2021 13:21:44 -0400 Subject: [PATCH 2/8] revamp CLI flags; support different versions for upload Signed-off-by: Bob Callaway --- cmd/rekor-cli/app/get.go | 5 +- cmd/rekor-cli/app/log_info.go | 1 + cmd/rekor-cli/app/log_proof.go | 1 + cmd/rekor-cli/app/pflag_groups.go | 188 +++++++ cmd/rekor-cli/app/pflags.go | 738 ++++++------------------- cmd/rekor-cli/app/pflags_test.go | 23 +- cmd/rekor-cli/app/root.go | 69 +-- cmd/rekor-cli/app/search.go | 60 +- cmd/rekor-cli/app/timestamp.go | 80 +-- cmd/rekor-cli/app/upload.go | 58 +- cmd/rekor-cli/app/verify.go | 28 +- docker-compose.yml | 2 +- pkg/pki/factory/factory.go | 28 +- pkg/types/entries.go | 26 + pkg/types/intoto/intoto.go | 18 +- pkg/types/intoto/intoto_test.go | 4 + pkg/types/intoto/v0.0.1/entry.go | 44 ++ pkg/types/jar/jar.go | 18 +- pkg/types/jar/jar_test.go | 4 + pkg/types/jar/v0.0.1/entry.go | 51 ++ pkg/types/rekord/rekord.go | 18 +- pkg/types/rekord/rekord_test.go | 4 + pkg/types/rekord/v0.0.1/entry.go | 99 ++++ pkg/types/rfc3161/rfc3161.go | 30 +- pkg/types/rfc3161/rfc3161_test.go | 3 + pkg/types/rfc3161/v0.0.1/entry.go | 38 +- pkg/types/rfc3161/v0.0.1/entry_test.go | 6 +- pkg/types/rpm/rpm.go | 18 +- pkg/types/rpm/rpm_test.go | 4 + pkg/types/rpm/v0.0.1/entry.go | 63 +++ pkg/types/types.go | 34 +- pkg/types/versionmap.go | 9 + 32 files changed, 963 insertions(+), 809 deletions(-) create mode 100644 cmd/rekor-cli/app/pflag_groups.go diff --git a/cmd/rekor-cli/app/get.go b/cmd/rekor-cli/app/get.go index d40801c37..0113a8c2a 100644 --- a/cmd/rekor-cli/app/get.go +++ b/cmd/rekor-cli/app/get.go @@ -141,11 +141,12 @@ func parseEntry(uuid string, e models.LogEntryAnon) (interface{}, error) { } func init() { + initializePFlagMap() if err := addUUIDPFlags(getCmd, false); err != nil { - log.Logger.Fatal("Error parsing cmd line args:", err) + log.Logger.Fatal("Error parsing cmd line args: ", err) } if err := addLogIndexFlag(getCmd, false); err != nil { - log.Logger.Fatal("Error parsing cmd line args:", err) + log.Logger.Fatal("Error parsing cmd line args: ", err) } rootCmd.AddCommand(getCmd) diff --git a/cmd/rekor-cli/app/log_info.go b/cmd/rekor-cli/app/log_info.go index e52f50976..e77b648c2 100644 --- a/cmd/rekor-cli/app/log_info.go +++ b/cmd/rekor-cli/app/log_info.go @@ -152,5 +152,6 @@ var logInfoCmd = &cobra.Command{ } func init() { + initializePFlagMap() rootCmd.AddCommand(logInfoCmd) } diff --git a/cmd/rekor-cli/app/log_proof.go b/cmd/rekor-cli/app/log_proof.go index 55813cc9b..fad409f61 100644 --- a/cmd/rekor-cli/app/log_proof.go +++ b/cmd/rekor-cli/app/log_proof.go @@ -97,6 +97,7 @@ var logProofCmd = &cobra.Command{ } func init() { + initializePFlagMap() logProofCmd.Flags().Uint64("first-size", 1, "the size of the log where the proof should begin") logProofCmd.Flags().Uint64("last-size", 0, "the size of the log where the proof should end") if err := logProofCmd.MarkFlagRequired("last-size"); err != nil { diff --git a/cmd/rekor-cli/app/pflag_groups.go b/cmd/rekor-cli/app/pflag_groups.go new file mode 100644 index 000000000..4108f0f9b --- /dev/null +++ b/cmd/rekor-cli/app/pflag_groups.go @@ -0,0 +1,188 @@ +// +// Copyright 2021 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 app + +import ( + "errors" + "fmt" + "net/url" + "strings" + + "github.com/sigstore/rekor/pkg/pki/factory" + "github.com/sigstore/rekor/pkg/types" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// addFlagToCmd adds the +func addFlagToCmd(cmd *cobra.Command, required bool, flagType FlagType, flag, desc string) error { + cmd.Flags().Var(NewFlagValue(flagType, ""), flag, desc) + if required { + return cmd.MarkFlagRequired(flag) + } + return nil +} + +func addLogIndexFlag(cmd *cobra.Command, required bool) error { + return addFlagToCmd(cmd, required, logIndexFlag, "log-index", "the index of the entry in the transparency log") +} + +func addUUIDPFlags(cmd *cobra.Command, required bool) error { + return addFlagToCmd(cmd, required, uuidFlag, "uuid", "UUID of entry in transparency log (if known)") +} + +func addArtifactPFlags(cmd *cobra.Command) error { + flags := map[string]struct { + flagType FlagType + desc string + required bool + }{ + "signature": { + fileOrURLFlag, + "path or URL to detached signature file", + false, + }, + "type": { + typeFlag, + fmt.Sprintf("type of entry expressed as type(:version)?; supported types = %v", types.ListImplementedTypes()), + false, + }, + "pki-format": { + pkiFormatFlag, + fmt.Sprintf("format of the signature and/or public key; options = %v", factory.SupportedFormats()), + false, + }, + "public-key": { + fileOrURLFlag, + "path or URL to public key file", + false, + }, + "artifact": { + fileOrURLFlag, + "path or URL to artifact file", + false, + }, + "artifact-hash": { + uuidFlag, + "hex encoded SHA256 hash of artifact (when using URL)", + false, + }, + "entry": { + fileOrURLFlag, + "path or URL to pre-formatted entry file", + false, + }, + } + + for flag, flagVal := range flags { + if err := addFlagToCmd(cmd, flagVal.required, flagVal.flagType, flag, flagVal.desc); err != nil { + return err + } + } + + return nil +} + +func validateArtifactPFlags(uuidValid, indexValid bool) error { + uuidGiven := false + if uuidValid && viper.GetString("uuid") != "" { + uuidGiven = true + } + indexGiven := false + if indexValid && viper.GetString("log-index") != "" { + indexGiven = true + } + // we will need artifact, public-key, signature + entry := viper.GetString("entry") + if entry == "" && viper.GetString("artifact") == "" { + if (uuidGiven && uuidValid) || (indexGiven && indexValid) { + return nil + } + return errors.New("either 'entry' or 'artifact' must be specified") + } + + typeStr := viper.GetString("type") + signature := viper.GetString("signature") + publicKey := viper.GetString("public-key") + + if entry == "" { + if signature == "" && typeStr == "rekord" { + return errors.New("--signature is required when --artifact is used") + } + if publicKey == "" && typeStr != "jar" && typeStr != "rfc3161" { + return errors.New("--public-key is required when --artifact is used") + } + } + + return nil +} + +func CreatePropsFromPflags() *types.ArtifactProperties { + props := &types.ArtifactProperties{} + + artifactString := viper.GetString("artifact") + if artifactString != "" { + if IsURL(artifactString) { + props.ArtifactPath, _ = url.Parse(artifactString) + } else { + props.ArtifactPath = &url.URL{Path: artifactString} + } + } + + props.ArtifactHash = viper.GetString("artifact-hash") + + signatureString := viper.GetString("signature") + if signatureString != "" { + if IsURL(signatureString) { + props.SignaturePath, _ = url.Parse(signatureString) + } else { + props.SignaturePath = &url.URL{Path: signatureString} + } + } + + publicKeyString := viper.GetString("public-key") + if publicKeyString != "" { + if IsURL(publicKeyString) { + props.PublicKeyPath, _ = url.Parse(publicKeyString) + } else { + props.PublicKeyPath = &url.URL{Path: publicKeyString} + } + } + + props.PKIFormat = viper.GetString("pki-format") + + return props +} + +//TODO: add tests for this +func ParseTypeFlag(typeStr string) (string, string, error) { + // typeStr can come in as: + // type -> use default version for this kind + // type:version_string -> attempt to use specified version string + + typeStrings := strings.SplitN(typeStr, ":", 2) + if _, ok := types.TypeMap.Load(typeStrings[0]); !ok { + return "", "", fmt.Errorf("unknown type %v", typeStrings[0]) + } + + switch len(typeStrings) { + case 1: + return typeStrings[0], "", nil + case 2: + return typeStrings[0], typeStrings[1], nil + } + return "", "", errors.New("malformed type string") +} diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index 58a5d3d64..7c0cdc41f 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -16,643 +16,225 @@ package app import ( - "context" - "encoding/hex" - "encoding/json" - "errors" "fmt" - "io/ioutil" - "net/http" - "net/url" - "os" - "path/filepath" + "log" "strconv" "strings" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" - "github.com/spf13/cobra" - "github.com/spf13/viper" + "github.com/spf13/pflag" "github.com/go-playground/validator" - "github.com/sigstore/rekor/pkg/generated/models" - intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" - jar_v001 "github.com/sigstore/rekor/pkg/types/jar/v0.0.1" - rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1" - rfc3161_v001 "github.com/sigstore/rekor/pkg/types/rfc3161/v0.0.1" - rpm_v001 "github.com/sigstore/rekor/pkg/types/rpm/v0.0.1" + "github.com/pkg/errors" + pkifactory "github.com/sigstore/rekor/pkg/pki/factory" ) -func addSearchPFlags(cmd *cobra.Command) error { - cmd.Flags().Var(&pkiFormatFlag{value: "pgp"}, "pki-format", "format of the signature and/or public key") - - cmd.Flags().Var(&fileOrURLFlag{}, "public-key", "path or URL to public key file") - - cmd.Flags().Var(&fileOrURLFlag{}, "artifact", "path or URL to artifact file") - - cmd.Flags().Var(&shaFlag{}, "sha", "the SHA256 sum of the artifact") - - cmd.Flags().Var(&emailFlag{}, "email", "email associated with the public key's subject") - return nil -} - -func validateSearchPFlags() error { - artifactStr := viper.GetString("artifact") - - publicKey := viper.GetString("public-key") - sha := viper.GetString("sha") - email := viper.GetString("email") - - if artifactStr == "" && publicKey == "" && sha == "" && email == "" { - return errors.New("either 'sha' or 'artifact' or 'public-key' or 'email' must be specified") - } - if publicKey != "" { - if viper.GetString("pki-format") == "" { - return errors.New("pki-format must be specified if searching by public-key") - } - } - return nil -} - -func addArtifactPFlags(cmd *cobra.Command) error { - cmd.Flags().Var(&fileOrURLFlag{}, "signature", "path or URL to detached signature file") - cmd.Flags().Var(&typeFlag{value: "rekord"}, "type", "type of entry") - cmd.Flags().Var(&pkiFormatFlag{value: "pgp"}, "pki-format", "format of the signature and/or public key") - - cmd.Flags().Var(&fileOrURLFlag{}, "public-key", "path or URL to public key file") - - cmd.Flags().Var(&fileOrURLFlag{}, "artifact", "path or URL to artifact file") - cmd.Flags().Var(&uuidFlag{}, "artifact-hash", "hex encoded SHA256 hash of artifact (when using URL)") - - cmd.Flags().Var(&fileOrURLFlag{}, "entry", "path or URL to pre-formatted entry file") - - return nil -} - -func validateArtifactPFlags(uuidValid, indexValid bool) error { - uuidGiven := false - if uuidValid { - uuid := uuidFlag{} - uuidStr := viper.GetString("uuid") - - if uuidStr != "" { - if err := uuid.Set(uuidStr); err != nil { - return err - } - uuidGiven = true - } - } - indexGiven := false - if indexValid { - logIndex := logIndexFlag{} - logIndexStr := viper.GetString("log-index") - - if logIndexStr != "" { - if err := logIndex.Set(logIndexStr); err != nil { - return err - } - indexGiven = true - } - } - // we will need artifact, public-key, signature - typeStr := viper.GetString("type") - entry := viper.GetString("entry") - - artifact := fileOrURLFlag{} - artifactStr := viper.GetString("artifact") - if artifactStr != "" { - if err := artifact.Set(artifactStr); err != nil { - return err - } - } - - signature := viper.GetString("signature") - publicKey := viper.GetString("public-key") - - if entry == "" && artifact.String() == "" { - if (uuidGiven && uuidValid) || (indexGiven && indexValid) { - return nil - } - return errors.New("either 'entry' or 'artifact' must be specified") - } - - if entry == "" { - if signature == "" && typeStr == "rekord" { - return errors.New("--signature is required when --artifact is used") - } - if publicKey == "" && typeStr != "jar" && typeStr != "rfc3161" { - return errors.New("--public-key is required when --artifact is used") - } - } - - return nil -} - -func CreateJarFromPFlags() (models.ProposedEntry, error) { - //TODO: how to select version of item to create - returnVal := models.Jar{} - re := new(jar_v001.V001Entry) - - jar := viper.GetString("entry") - if jar != "" { - var jarBytes []byte - jarURL, err := url.Parse(jar) - if err == nil && jarURL.IsAbs() { - /* #nosec G107 */ - jarResp, err := http.Get(jar) - if err != nil { - return nil, fmt.Errorf("error fetching 'jar': %w", err) - } - defer jarResp.Body.Close() - jarBytes, err = ioutil.ReadAll(jarResp.Body) - if err != nil { - return nil, fmt.Errorf("error fetching 'jar': %w", err) - } - } else { - jarBytes, err = ioutil.ReadFile(filepath.Clean(jar)) - if err != nil { - return nil, fmt.Errorf("error processing 'jar' file: %w", err) - } - } - if err := json.Unmarshal(jarBytes, &returnVal); err != nil { - return nil, fmt.Errorf("error parsing jar file: %w", err) - } - } else { - // we will need only the artifact; public-key & signature are embedded in JAR - re.JARModel = models.JarV001Schema{} - re.JARModel.Archive = &models.JarV001SchemaArchive{} - - artifact := viper.GetString("artifact") - dataURL, err := url.Parse(artifact) - if err == nil && dataURL.IsAbs() { - re.JARModel.Archive.URL = strfmt.URI(artifact) - re.JARModel.Archive.Hash = &models.JarV001SchemaArchiveHash{ - Algorithm: swag.String(models.JarV001SchemaArchiveHashAlgorithmSha256), - Value: swag.String(viper.GetString("artifact-hash")), - } - } else { - artifactBytes, err := ioutil.ReadFile(filepath.Clean(artifact)) - if err != nil { - return nil, fmt.Errorf("error reading artifact file: %w", err) - } - - //TODO: ensure this is a valid JAR file; look for META-INF/MANIFEST.MF? - re.JARModel.Archive.Content = strfmt.Base64(artifactBytes) - } - - if err := re.Validate(); err != nil { - return nil, err - } - - if re.HasExternalEntities() { - if err := re.FetchExternalEntities(context.Background()); err != nil { - return nil, fmt.Errorf("error retrieving external entities: %v", err) - } - } - - returnVal.APIVersion = swag.String(re.APIVersion()) - returnVal.Spec = re.JARModel - } - - return &returnVal, nil -} +type FlagType string + +const ( + uuidFlag FlagType = "uuid" + shaFlag FlagType = "sha" + emailFlag FlagType = "email" + logIndexFlag FlagType = "logIndex" + pkiFormatFlag FlagType = "pkiFormat" + typeFlag FlagType = "type" + fileFlag FlagType = "file" + urlFlag FlagType = "url" + fileOrURLFlag FlagType = "fileOrURL" + oidFlag FlagType = "oid" + formatFlag FlagType = "format" +) -func CreateIntotoFromPFlags() (models.ProposedEntry, error) { - //TODO: how to select version of item to create - returnVal := models.Intoto{} +type newPFlagValueFunc func() pflag.Value - intoto := viper.GetString("artifact") - b, err := ioutil.ReadFile(filepath.Clean(intoto)) - if err != nil { - return nil, err - } - publicKey := viper.GetString("public-key") - keyBytes, err := ioutil.ReadFile(filepath.Clean(publicKey)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - kb := strfmt.Base64(keyBytes) +var pflagValueFuncMap map[FlagType]newPFlagValueFunc - re := intoto_v001.V001Entry{ - IntotoObj: models.IntotoV001Schema{ - Content: &models.IntotoV001SchemaContent{ - Envelope: string(b), - }, - PublicKey: &kb, +// TODO: unit tests for all of this +func initializePFlagMap() { + pflagValueFuncMap = map[FlagType]newPFlagValueFunc{ + uuidFlag: func() pflag.Value { + // this corresponds to the merkle leaf hash of entries, which is represented by a 64 character hexadecimal string + return valueFactory(uuidFlag, validateString("required,len=64,hexadecimal"), "") }, - } - - returnVal.Spec = re.IntotoObj - returnVal.APIVersion = swag.String(re.APIVersion()) - - return &returnVal, nil -} - -func CreateRFC3161FromPFlags() (models.ProposedEntry, error) { - //TODO: how to select version of item to create - returnVal := models.Rfc3161{} - - rfc3161 := viper.GetString("artifact") - b, err := ioutil.ReadFile(filepath.Clean(rfc3161)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - - b64 := strfmt.Base64(b) - re := rfc3161_v001.V001Entry{ - Rfc3161Obj: models.Rfc3161V001Schema{ - Tsr: &models.Rfc3161V001SchemaTsr{ - Content: &b64, - }, + shaFlag: func() pflag.Value { + // this validates a valid sha256 checksum which is optionally prefixed with 'sha256:' + return valueFactory(shaFlag, validateSHA256Value, "") + }, + emailFlag: func() pflag.Value { + // this validates an email address + return valueFactory(emailFlag, validateString("required,email"), "") + }, + logIndexFlag: func() pflag.Value { + // this checks for a valid integer >= 0 + return valueFactory(logIndexFlag, validateLogIndex, "") + }, + pkiFormatFlag: func() pflag.Value { + // this ensures a PKI implementation exists for the requested format + return valueFactory(pkiFormatFlag, validateString(fmt.Sprintf("required,oneof=%v", strings.Join(pkifactory.SupportedFormats(), " "))), "pgp") + }, + typeFlag: func() pflag.Value { + // this ensures the type of the log entry matches a type supported in the CLI + return valueFactory(typeFlag, validateTypeFlag, "rekord") + }, + fileFlag: func() pflag.Value { + // this validates that the file exists and can be opened by the current uid + return valueFactory(fileFlag, validateString("required,file"), "") + }, + urlFlag: func() pflag.Value { + // this validates that the string is a valid http/https URL + return valueFactory(urlFlag, validateString("required,url,startswith=http|startswith=https"), "") + }, + fileOrURLFlag: func() pflag.Value { + // applies logic of fileFlag OR urlFlag validators from above + return valueFactory(fileOrURLFlag, validateFileOrURL, "") + }, + oidFlag: func() pflag.Value { + // this validates for an OID, which is a sequence of positive integers separated by periods + return valueFactory(oidFlag, validateOID, "") + }, + formatFlag: func() pflag.Value { + // this validates the output format requested + return valueFactory(formatFlag, validateString("required,oneof=json default"), "") }, } - - returnVal.Spec = re.Rfc3161Obj - returnVal.APIVersion = swag.String(re.APIVersion()) - - return &returnVal, nil } -func CreateRpmFromPFlags() (models.ProposedEntry, error) { - //TODO: how to select version of item to create - returnVal := models.Rpm{} - re := new(rpm_v001.V001Entry) - - rpm := viper.GetString("entry") - if rpm != "" { - var rpmBytes []byte - rpmURL, err := url.Parse(rpm) - if err == nil && rpmURL.IsAbs() { - /* #nosec G107 */ - rpmResp, err := http.Get(rpm) - if err != nil { - return nil, fmt.Errorf("error fetching 'rpm': %w", err) - } - defer rpmResp.Body.Close() - rpmBytes, err = ioutil.ReadAll(rpmResp.Body) - if err != nil { - return nil, fmt.Errorf("error fetching 'rpm': %w", err) - } - } else { - rpmBytes, err = ioutil.ReadFile(filepath.Clean(rpm)) - if err != nil { - return nil, fmt.Errorf("error processing 'rpm' file: %w", err) - } - } - if err := json.Unmarshal(rpmBytes, &returnVal); err != nil { - return nil, fmt.Errorf("error parsing rpm file: %w", err) +// NewFlagValue creates a new pflag.Value for the specified type with the specified default value. +// If a default value is not desired, pass "" for defaultVal. +func NewFlagValue(flagType FlagType, defaultVal string) pflag.Value { + valFunc := pflagValueFuncMap[flagType] + val := valFunc() + if defaultVal != "" { + if err := val.Set(defaultVal); err != nil { + log.Fatal(errors.Wrap(err, "initializing flag")) } - } else { - // we will need artifact, public-key, signature - re.RPMModel = models.RpmV001Schema{} - re.RPMModel.Package = &models.RpmV001SchemaPackage{} - - artifact := viper.GetString("artifact") - dataURL, err := url.Parse(artifact) - if err == nil && dataURL.IsAbs() { - re.RPMModel.Package.URL = strfmt.URI(artifact) - } else { - artifactBytes, err := ioutil.ReadFile(filepath.Clean(artifact)) - if err != nil { - return nil, fmt.Errorf("error reading artifact file: %w", err) - } - re.RPMModel.Package.Content = strfmt.Base64(artifactBytes) - } - - re.RPMModel.PublicKey = &models.RpmV001SchemaPublicKey{} - publicKey := viper.GetString("public-key") - keyURL, err := url.Parse(publicKey) - if err == nil && keyURL.IsAbs() { - re.RPMModel.PublicKey.URL = strfmt.URI(publicKey) - } else { - keyBytes, err := ioutil.ReadFile(filepath.Clean(publicKey)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - re.RPMModel.PublicKey.Content = strfmt.Base64(keyBytes) - } - - if err := re.Validate(); err != nil { - return nil, err - } - - if re.HasExternalEntities() { - if err := re.FetchExternalEntities(context.Background()); err != nil { - return nil, fmt.Errorf("error retrieving external entities: %v", err) - } - } - - returnVal.APIVersion = swag.String(re.APIVersion()) - returnVal.Spec = re.RPMModel } - - return &returnVal, nil + return val } -func CreateRekordFromPFlags() (models.ProposedEntry, error) { - //TODO: how to select version of item to create - returnVal := models.Rekord{} - re := new(rekord_v001.V001Entry) - - rekord := viper.GetString("entry") - if rekord != "" { - var rekordBytes []byte - rekordURL, err := url.Parse(rekord) - if err == nil && rekordURL.IsAbs() { - /* #nosec G107 */ - rekordResp, err := http.Get(rekord) - if err != nil { - return nil, fmt.Errorf("error fetching 'rekord': %w", err) - } - defer rekordResp.Body.Close() - rekordBytes, err = ioutil.ReadAll(rekordResp.Body) - if err != nil { - return nil, fmt.Errorf("error fetching 'rekord': %w", err) - } - } else { - rekordBytes, err = ioutil.ReadFile(filepath.Clean(rekord)) - if err != nil { - return nil, fmt.Errorf("error processing 'rekord' file: %w", err) - } - } - if err := json.Unmarshal(rekordBytes, &returnVal); err != nil { - return nil, fmt.Errorf("error parsing rekord file: %w", err) - } - } else { - // we will need artifact, public-key, signature - re.RekordObj.Data = &models.RekordV001SchemaData{} - - artifact := viper.GetString("artifact") - dataURL, err := url.Parse(artifact) - if err == nil && dataURL.IsAbs() { - re.RekordObj.Data.URL = strfmt.URI(artifact) - } else { - artifactBytes, err := ioutil.ReadFile(filepath.Clean(artifact)) - if err != nil { - return nil, fmt.Errorf("error reading artifact file: %w", err) - } - re.RekordObj.Data.Content = strfmt.Base64(artifactBytes) - } - - re.RekordObj.Signature = &models.RekordV001SchemaSignature{} - pkiFormat := viper.GetString("pki-format") - switch pkiFormat { - case "pgp": - re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatPgp - case "minisign": - re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatMinisign - case "x509": - re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatX509 - case "ssh": - re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatSSH - } - signature := viper.GetString("signature") - sigURL, err := url.Parse(signature) - if err == nil && sigURL.IsAbs() { - re.RekordObj.Signature.URL = strfmt.URI(signature) - } else { - signatureBytes, err := ioutil.ReadFile(filepath.Clean(signature)) - if err != nil { - return nil, fmt.Errorf("error reading signature file: %w", err) - } - re.RekordObj.Signature.Content = strfmt.Base64(signatureBytes) - } - - re.RekordObj.Signature.PublicKey = &models.RekordV001SchemaSignaturePublicKey{} - publicKey := viper.GetString("public-key") - keyURL, err := url.Parse(publicKey) - if err == nil && keyURL.IsAbs() { - re.RekordObj.Signature.PublicKey.URL = strfmt.URI(publicKey) - } else { - keyBytes, err := ioutil.ReadFile(filepath.Clean(publicKey)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - re.RekordObj.Signature.PublicKey.Content = strfmt.Base64(keyBytes) - } - - if err := re.Validate(); err != nil { - return nil, err - } - - if re.HasExternalEntities() { - if err := re.FetchExternalEntities(context.Background()); err != nil { - return nil, fmt.Errorf("error retrieving external entities: %v", err) - } - } +type validationFunc func(string) error - returnVal.APIVersion = swag.String(re.APIVersion()) - returnVal.Spec = re.RekordObj +func valueFactory(flagType FlagType, v validationFunc, defaultVal string) pflag.Value { + return &baseValue{ + flagType: flagType, + validationFunc: v, + value: defaultVal, } +} - return &returnVal, nil +// baseValue implements pflag.Value +type baseValue struct { + flagType FlagType + value string + validationFunc validationFunc } -type fileOrURLFlag struct { - value string - IsURL bool +// Type returns the type of this Value +func (b baseValue) Type() string { + return string(b.flagType) } -func (f *fileOrURLFlag) String() string { - return f.value +// String returns the string representation of this Value +func (b baseValue) String() string { + return b.value } -func (f *fileOrURLFlag) Set(s string) error { - if s == "" { - return errors.New("flag must be specified") - } - if _, err := os.Stat(filepath.Clean(s)); os.IsNotExist(err) { - u := urlFlag{} - if err := u.Set(s); err != nil { - return err - } - f.IsURL = true +// Set validates the provided string against the appropriate validation rule +// for b.flagType; if the string validates, it is stored in the Value and nil is returned. +// Otherwise the validation error is returned but the state of the Value is not changed. +func (b *baseValue) Set(s string) error { + if err := b.validationFunc(s); err != nil { + return err } - f.value = s + b.value = s return nil } -func (f *fileOrURLFlag) Type() string { - return "fileOrURLFlag" +// IsURL returns true if the supplied value is a valid URL and false otherwise +func IsURL(v string) bool { + valGen := pflagValueFuncMap[urlFlag] + return valGen().Set(v) == nil } -type typeFlag struct { - value string -} - -func (t *typeFlag) Type() string { - return "typeFormat" -} - -func (t *typeFlag) String() string { - return t.value -} +// validateSHA256Value ensures that the supplied string matches the following format: +// [sha256:]<64 hexadecimal characters> +// where [sha256:] is optional +func validateSHA256Value(v string) error { + var prefix, hash string -func (t *typeFlag) Set(s string) error { - set := map[string]struct{}{ - "rekord": {}, - "rpm": {}, - "jar": {}, - "intoto": {}, - "rfc3161": {}, + split := strings.Split(v, ":") + switch len(split) { + case 1: + hash = split[0] + case 2: + prefix = split[0] + hash = split[1] + default: + return errors.New("invalid format for SHA flag") } - if _, ok := set[s]; ok { - t.value = s - return nil - } - return fmt.Errorf("value specified is invalid: [%s] supported values are: [rekord, rpm, jar, intoto, rfc3161]", s) -} - -type pkiFormatFlag struct { - value string -} -func (f *pkiFormatFlag) Type() string { - return "pkiFormat" -} + s := struct { + Prefix string `validate:"omitempty,oneof=sha256"` + Hash string `validate:"required,len=64,hexadecimal"` + }{prefix, hash} -func (f *pkiFormatFlag) String() string { - return f.value + return useValidator(shaFlag, s) } -func (f *pkiFormatFlag) Set(s string) error { - set := map[string]struct{}{ - "pgp": {}, - "minisign": {}, - "x509": {}, - "ssh": {}, - } - if _, ok := set[s]; ok { - f.value = s +// validateFileOrURL ensures the provided string is either a valid file path that can be opened or a valid URL +func validateFileOrURL(v string) error { + valGen := pflagValueFuncMap[fileFlag] + if valGen().Set(v) == nil { return nil } - return fmt.Errorf("value specified is invalid: [%s] supported values are: [pgp, minisign, x509, ssh]", s) + valGen = pflagValueFuncMap[urlFlag] + return valGen().Set(v) } -type shaFlag struct { - hash string -} - -func (s *shaFlag) String() string { - return s.hash -} - -func (s *shaFlag) Set(v string) error { - if v == "" { - return errors.New("flag must be specified") - } - strToCheck := v - if strings.HasPrefix(v, "sha256:") { - strToCheck = strings.Replace(v, "sha256:", "", 1) - } - if _, err := hex.DecodeString(strToCheck); (err != nil) || (len(strToCheck) != 64) { - if err == nil { - err = errors.New("invalid length for value") - } - return fmt.Errorf("value specified is invalid: %w", err) - } - s.hash = v - return nil -} - -func (s *shaFlag) Type() string { - return "sha" -} - -type uuidFlag struct { - hash string -} - -func (u *uuidFlag) String() string { - return u.hash -} - -func (u *uuidFlag) Set(v string) error { - if v == "" { - return errors.New("flag must be specified") - } - if _, err := hex.DecodeString(v); (err != nil) || (len(v) != 64) { - if err == nil { - err = errors.New("invalid length for value") - } - return fmt.Errorf("value specified is invalid: %w", err) - } - u.hash = v - return nil -} - -func (u *uuidFlag) Type() string { - return "uuid" -} - -func addUUIDPFlags(cmd *cobra.Command, required bool) error { - cmd.Flags().Var(&uuidFlag{}, "uuid", "UUID of entry in transparency log (if known)") - if required { - if err := cmd.MarkFlagRequired("uuid"); err != nil { - return err - } - } - return nil -} - -type logIndexFlag struct { - index string -} - -func (l *logIndexFlag) String() string { - return l.index -} - -func (l *logIndexFlag) Set(v string) error { - if v == "" { - return errors.New("flag must be specified") - } - logIndexInt, err := strconv.ParseInt(v, 10, 0) +// validateLogIndex ensures that the supplied string is a valid log index (integer >= 0) +func validateLogIndex(v string) error { + i, err := strconv.Atoi(v) if err != nil { - return fmt.Errorf("error parsing --log-index: %w", err) - } else if logIndexInt < 0 { - return errors.New("--log-index must be greater than or equal to 0") + return err } - l.index = v - return nil -} + l := struct { + Index int `validate:"required,gte=0"` + }{i} -func (l *logIndexFlag) Type() string { - return "logIndex" + return useValidator(logIndexFlag, l) } -func addLogIndexFlag(cmd *cobra.Command, required bool) error { - cmd.Flags().Var(&logIndexFlag{}, "log-index", "the index of the entry in the transparency log") - if required { - if err := cmd.MarkFlagRequired("log-index"); err != nil { - return err - } - } - return nil -} +// validateOID ensures that the supplied string is a valid ASN.1 object identifier +func validateOID(v string) error { + o := struct { + Oid []string `validate:"dive,numeric"` + }{strings.Split(v, ".")} -type emailFlag struct { - Email string `validate:"email"` + return useValidator(oidFlag, o) } -func (e *emailFlag) String() string { - return e.Email +// validateTypeFlag ensures that the string is in the format type(\.version)? and +// that one of the types requested is implemented +func validateTypeFlag(v string) error { + _, _, err := ParseTypeFlag(v) + return err } -func (e *emailFlag) Set(v string) error { - if v == "" { - return errors.New("flag must be specified") +// validateString returns a function that validates an input string against the specified tag, +// as defined in the format supported by go-playground/validator +func validateString(tag string) validationFunc { + return func(v string) error { + validator := validator.New() + return validator.Var(v, tag) } +} - e.Email = v +// useValidator performs struct level validation on s as defined in the struct's tags using +// the go-playground/validator library +func useValidator(flagType FlagType, s interface{}) error { validate := validator.New() - if err := validate.Struct(e); err != nil { - return fmt.Errorf("error parsing --email: %s", err) + if err := validate.Struct(s); err != nil { + return fmt.Errorf("error parsing %v flag: %w", flagType, err) } return nil } - -func (e *emailFlag) Type() string { - return "email" -} diff --git a/cmd/rekor-cli/app/pflags_test.go b/cmd/rekor-cli/app/pflags_test.go index ca314fa23..89d4d6e5c 100644 --- a/cmd/rekor-cli/app/pflags_test.go +++ b/cmd/rekor-cli/app/pflags_test.go @@ -16,7 +16,9 @@ package app import ( + "context" "errors" + "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -25,7 +27,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/types" ) func TestArtifactPFlags(t *testing.T) { @@ -309,6 +311,7 @@ func TestArtifactPFlags(t *testing.T) { } for _, tc := range tests { + initializePFlagMap() var blankCmd = &cobra.Command{} if err := addArtifactPFlags(blankCmd); err != nil { t.Fatalf("unexpected error adding flags in '%v': %v", tc.caseDesc, err) @@ -357,15 +360,14 @@ func TestArtifactPFlags(t *testing.T) { t.Errorf("unexpected result validating '%v': %v", tc.caseDesc, err) continue } - if !tc.uuidRequired && !tc.logIndexRequired { - var createFn func() (models.ProposedEntry, error) - switch tc.typeStr { - case "", "rekord": - createFn = CreateRekordFromPFlags - case "rpm": - createFn = CreateRpmFromPFlags + if !tc.uuidRequired && !tc.logIndexRequired && tc.entry == "" { + typeStr, versionStr, err := ParseTypeFlag(viper.GetString("type")) + if err != nil { + t.Errorf("error parsing typeStr: %v", err) } - if _, err := createFn(); err != nil { + props := CreatePropsFromPflags() + fmt.Println(props) + if _, err := types.NewProposedEntry(context.Background(), typeStr, versionStr, *props); err != nil { t.Errorf("unexpected result in '%v' building entry: %v", tc.caseDesc, err) } } @@ -526,7 +528,7 @@ func TestSearchPFlags(t *testing.T) { caseDesc: "invalid email", email: "SignaMeseCat", expectParseSuccess: false, - expectValidateSuccess: true, + expectValidateSuccess: false, }, { caseDesc: "no flags when either artifact, sha, public key, or email are needed", @@ -536,6 +538,7 @@ func TestSearchPFlags(t *testing.T) { } for _, tc := range tests { + initializePFlagMap() var blankCmd = &cobra.Command{} if err := addSearchPFlags(blankCmd); err != nil { t.Fatalf("unexpected error adding flags in '%v': %v", tc.caseDesc, err) diff --git a/cmd/rekor-cli/app/root.go b/cmd/rekor-cli/app/root.go index 16b349bc5..d17e02484 100644 --- a/cmd/rekor-cli/app/root.go +++ b/cmd/rekor-cli/app/root.go @@ -16,7 +16,6 @@ package app import ( - "errors" "fmt" "net/url" "os" @@ -32,6 +31,13 @@ import ( "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/util" + + // these imports are to call the packages' init methods + _ "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" + _ "github.com/sigstore/rekor/pkg/types/jar/v0.0.1" + _ "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1" + _ "github.com/sigstore/rekor/pkg/types/rfc3161/v0.0.1" + _ "github.com/sigstore/rekor/pkg/types/rpm/v0.0.1" ) var rootCmd = &cobra.Command{ @@ -52,11 +58,12 @@ func Execute() { } func init() { + initializePFlagMap() rootCmd.PersistentFlags().String("config", "", "config file (default is $HOME/.rekor.yaml)") rootCmd.PersistentFlags().Bool("store_tree_state", true, "whether to store tree state in between invocations for additional verification") - rootCmd.PersistentFlags().Var(&urlFlag{url: "https://rekor.sigstore.dev"}, "rekor_server", "Server address:port") - rootCmd.PersistentFlags().Var(&formatFlag{format: "default"}, "format", "Command output format") + rootCmd.PersistentFlags().Var(NewFlagValue(urlFlag, "https://rekor.sigstore.dev"), "rekor_server", "Server address:port") + rootCmd.PersistentFlags().Var(NewFlagValue(formatFlag, "default"), "format", "Command output format") rootCmd.PersistentFlags().String("api-key", "", "API key for rekor.sigstore.dev") @@ -136,59 +143,3 @@ func GetRekorClient(rekorServerURL string) (*client.Rekor, error) { registry.Add("signedCheckpoint", &util.SignedCheckpoint{}, util.SignedCheckpointValidator) return client.New(rt, registry), nil } - -type urlFlag struct { - url string -} - -func (u *urlFlag) String() string { - return u.url -} - -func (u *urlFlag) Set(s string) error { - if s == "" { - return errors.New("flag must be specified") - } - url, err := url.Parse(s) - if err != nil { - return fmt.Errorf("malformed URL: %w", err) - } - if !url.IsAbs() { - return fmt.Errorf("URL must be absolute, got %s", s) - } - lowercaseScheme := strings.ToLower(url.Scheme) - if lowercaseScheme != "http" && lowercaseScheme != "https" { - return fmt.Errorf("URL must be a valid HTTP or HTTPS URL, got %s", s) - } - u.url = s - return nil -} - -func (u *urlFlag) Type() string { - return "url" -} - -type formatFlag struct { - format string -} - -func (f *formatFlag) String() string { - return f.format -} - -func (f *formatFlag) Set(s string) error { - choices := map[string]struct{}{"default": {}, "json": {}} - if s == "" { - f.format = "default" - return nil - } - if _, ok := choices[s]; ok { - f.format = s - return nil - } - return fmt.Errorf("invalid flag value: %s, valid values are [default, json]", s) -} - -func (f *formatFlag) Type() string { - return "format" -} diff --git a/cmd/rekor-cli/app/search.go b/cmd/rekor-cli/app/search.go index d96d45490..182482f52 100644 --- a/cmd/rekor-cli/app/search.go +++ b/cmd/rekor-cli/app/search.go @@ -18,6 +18,7 @@ package app import ( "crypto/sha256" "encoding/hex" + "errors" "fmt" "io" "io/ioutil" @@ -52,6 +53,37 @@ func (s *searchCmdOutput) String() string { return str } +func addSearchPFlags(cmd *cobra.Command) error { + cmd.Flags().Var(NewFlagValue(pkiFormatFlag, ""), "pki-format", "format of the signature and/or public key") + + cmd.Flags().Var(NewFlagValue(fileOrURLFlag, ""), "public-key", "path or URL to public key file") + + cmd.Flags().Var(NewFlagValue(fileOrURLFlag, ""), "artifact", "path or URL to artifact file") + + cmd.Flags().Var(NewFlagValue(shaFlag, ""), "sha", "the SHA256 sum of the artifact") + + cmd.Flags().Var(NewFlagValue(emailFlag, ""), "email", "email associated with the public key's subject") + return nil +} + +func validateSearchPFlags() error { + artifactStr := viper.GetString("artifact") + + publicKey := viper.GetString("public-key") + sha := viper.GetString("sha") + email := viper.GetString("email") + + if artifactStr == "" && publicKey == "" && sha == "" && email == "" { + return errors.New("either 'sha' or 'artifact' or 'public-key' or 'email' must be specified") + } + if publicKey != "" { + if viper.GetString("pki-format") == "" { + return errors.New("pki-format must be specified if searching by public-key") + } + } + return nil +} + // searchCmd represents the get command var searchCmd = &cobra.Command{ Use: "search", @@ -83,25 +115,21 @@ var searchCmd = &cobra.Command{ if sha != "" { params.Query.Hash = sha } else if artifactStr != "" { - artifact := fileOrURLFlag{} - if err := artifact.Set(artifactStr); err != nil { - return nil, err - } hasher := sha256.New() var tee io.Reader - if artifact.IsURL { + if IsURL(artifactStr) { /* #nosec G107 */ - resp, err := http.Get(artifact.String()) + resp, err := http.Get(artifactStr) if err != nil { - return nil, fmt.Errorf("error fetching '%v': %w", artifact.String(), err) + return nil, fmt.Errorf("error fetching '%v': %w", artifactStr, err) } defer resp.Body.Close() tee = io.TeeReader(resp.Body, hasher) } else { - file, err := os.Open(filepath.Clean(artifact.String())) + file, err := os.Open(filepath.Clean(artifactStr)) if err != nil { - return nil, fmt.Errorf("error opening file '%v': %w", artifact.String(), err) + return nil, fmt.Errorf("error opening file '%v': %w", artifactStr, err) } defer func() { if err := file.Close(); err != nil { @@ -112,7 +140,7 @@ var searchCmd = &cobra.Command{ tee = io.TeeReader(file, hasher) } if _, err := ioutil.ReadAll(tee); err != nil { - return nil, fmt.Errorf("error processing '%v': %w", artifact.String(), err) + return nil, fmt.Errorf("error processing '%v': %w", artifactStr, err) } hashVal := strings.ToLower(hex.EncodeToString(hasher.Sum(nil))) @@ -135,14 +163,11 @@ var searchCmd = &cobra.Command{ default: return nil, fmt.Errorf("unknown pki-format %v", pkiFormat) } - publicKey := fileOrURLFlag{} - if err := publicKey.Set(publicKeyStr); err != nil { - return nil, err - } - if publicKey.IsURL { - params.Query.PublicKey.URL = strfmt.URI(publicKey.String()) + publicKeyStr := viper.GetString("public-key") + if IsURL(publicKeyStr) { + params.Query.PublicKey.URL = strfmt.URI(publicKeyStr) } else { - keyBytes, err := ioutil.ReadFile(filepath.Clean(publicKey.String())) + keyBytes, err := ioutil.ReadFile(filepath.Clean(publicKeyStr)) if err != nil { return nil, fmt.Errorf("error reading public key file: %w", err) } @@ -174,6 +199,7 @@ var searchCmd = &cobra.Command{ } func init() { + initializePFlagMap() if err := addSearchPFlags(searchCmd); err != nil { log.Logger.Fatal("Error parsing cmd line args:", err) } diff --git a/cmd/rekor-cli/app/timestamp.go b/cmd/rekor-cli/app/timestamp.go index 138ba124c..494f57595 100644 --- a/cmd/rekor-cli/app/timestamp.go +++ b/cmd/rekor-cli/app/timestamp.go @@ -23,7 +23,6 @@ import ( "errors" "fmt" "io/ioutil" - "os" "path/filepath" "strconv" "strings" @@ -38,59 +37,11 @@ import ( "github.com/spf13/viper" ) -type fileFlag struct { - value string -} - -func (f *fileFlag) String() string { - return f.value -} - -func (f *fileFlag) Set(s string) error { - if s == "" { - return errors.New("flag must be specified") - } - if _, err := os.Stat(filepath.Clean(s)); os.IsNotExist(err) { - return err - } - f.value = s - return nil -} - -func (f *fileFlag) Type() string { - return "fileFlag" -} - -type oidFlag struct { - value asn1.ObjectIdentifier -} - -func (f *oidFlag) String() string { - return f.value.String() -} - -func (f *oidFlag) Set(s string) error { - parts := strings.Split(s, ".") - f.value = make(asn1.ObjectIdentifier, len(parts)) - for i, part := range parts { - num, err := strconv.Atoi(part) - if err != nil { - return errors.New("error parsing OID") - } - f.value[i] = num - } - return nil -} - -func (f *oidFlag) Type() string { - return "oidFlag" -} - func addTimestampFlags(cmd *cobra.Command) error { - cmd.Flags().Var(&fileFlag{}, "artifact", "path to an artifact to timestamp") - cmd.Flags().Var(&uuidFlag{}, "artifact-hash", "hex encoded SHA256 hash of the the artifact to timestamp") + cmd.Flags().Var(NewFlagValue(fileFlag, ""), "artifact", "path to an artifact to timestamp") + cmd.Flags().Var(NewFlagValue(shaFlag, ""), "artifact-hash", "hex encoded SHA256 hash of the the artifact to timestamp") cmd.Flags().Bool("nonce", true, "specify a pseudo-random nonce in the request") - cmd.Flags().Var(&oidFlag{}, "tsa-policy", "optional dotted OID notation for the policy that the TSA should use to create the response") + cmd.Flags().Var(NewFlagValue(oidFlag, ""), "tsa-policy", "optional dotted OID notation for the policy that the TSA should use to create the response") cmd.Flags().String("out", "response.tsr", "path to a file to write response.") @@ -107,21 +58,6 @@ func validateTimestampFlags() error { return errors.New("artifact or hash to timestamp must be specified") } - if digestStr != "" { - digest := uuidFlag{} - if err := digest.Set(digestStr); err != nil { - return err - } - } - - policyStr := viper.GetString("tsa-policy") - if policyStr != "" { - oid := oidFlag{} - if err := oid.Set(policyStr); err != nil { - return err - } - } - return nil } @@ -135,11 +71,12 @@ func createRequestFromFlags() (*pkcs9.TimeStampReq, error) { Hash: crypto.SHA256, } if policyStr != "" { - oid := oidFlag{} - if err := oid.Set(policyStr); err != nil { - return nil, err + var oidInts []int + for _, v := range strings.Split(policyStr, ".") { + i, _ := strconv.Atoi(v) + oidInts = append(oidInts, i) } - opts.TSAPolicyOid = oid.value + opts.TSAPolicyOid = oidInts } if viper.GetBool("nonce") { opts.Nonce = x509tools.MakeSerial() @@ -244,6 +181,7 @@ var timestampCmd = &cobra.Command{ } func init() { + initializePFlagMap() if err := addTimestampFlags(timestampCmd); err != nil { log.Logger.Fatal("Error parsing cmd line args: ", err) } diff --git a/cmd/rekor-cli/app/upload.go b/cmd/rekor-cli/app/upload.go index ad49db223..12f33bdcd 100644 --- a/cmd/rekor-cli/app/upload.go +++ b/cmd/rekor-cli/app/upload.go @@ -19,8 +19,13 @@ import ( "context" "crypto/ecdsa" "crypto/sha256" + "encoding/json" "fmt" + "io/ioutil" + "net/http" + "net/url" "os" + "path/filepath" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/go-openapi/swag" @@ -33,6 +38,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" + "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/util" ) @@ -71,39 +77,46 @@ var uploadCmd = &cobra.Command{ if err != nil { return nil, err } + var entry models.ProposedEntry params := entries.NewCreateLogEntryParams() - var entry models.ProposedEntry - switch viper.GetString("type") { - case "rekord": - entry, err = CreateRekordFromPFlags() - if err != nil { - return nil, err - } - case "rpm": - entry, err = CreateRpmFromPFlags() - if err != nil { - return nil, err + entryStr := viper.GetString("entry") + if entryStr != "" { + var entryBytes []byte + entryURL, err := url.Parse(entryStr) + if err == nil && entryURL.IsAbs() { + /* #nosec G107 */ + entryResp, err := http.Get(entryStr) + if err != nil { + return nil, fmt.Errorf("error fetching entry: %w", err) + } + defer entryResp.Body.Close() + entryBytes, err = ioutil.ReadAll(entryResp.Body) + if err != nil { + return nil, fmt.Errorf("error fetching entry: %w", err) + } + } else { + entryBytes, err = ioutil.ReadFile(filepath.Clean(entryStr)) + if err != nil { + return nil, fmt.Errorf("error processing entry file: %w", err) + } } - case "jar": - entry, err = CreateJarFromPFlags() - if err != nil { - return nil, err + if err := json.Unmarshal(entryBytes, &entry); err != nil { + return nil, fmt.Errorf("error parsing entry file: %w", err) } - case "intoto": - entry, err = CreateIntotoFromPFlags() + } else { + typeStr, versionStr, err := ParseTypeFlag(viper.GetString("type")) if err != nil { return nil, err } - case "rfc3161": - entry, err = CreateRFC3161FromPFlags() + + props := CreatePropsFromPflags() + + entry, err = types.NewProposedEntry(context.Background(), typeStr, versionStr, *props) if err != nil { return nil, err } - default: - return nil, errors.New("unknown type specified") } - params.SetProposedEntry(entry) resp, err := rekorClient.Entries.CreateLogEntry(params) @@ -174,6 +187,7 @@ func verifyLogEntry(ctx context.Context, rekorClient *client.Rekor, logEntry mod } func init() { + initializePFlagMap() if err := addArtifactPFlags(uploadCmd); err != nil { log.Logger.Fatal("Error parsing cmd line args:", err) } diff --git a/cmd/rekor-cli/app/verify.go b/cmd/rekor-cli/app/verify.go index dae3d3fff..d98dade57 100644 --- a/cmd/rekor-cli/app/verify.go +++ b/cmd/rekor-cli/app/verify.go @@ -16,8 +16,8 @@ package app import ( + "context" "encoding/hex" - "errors" "fmt" "math/bits" "strconv" @@ -31,6 +31,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" + "github.com/sigstore/rekor/pkg/types" ) type verifyCmdOutput struct { @@ -104,20 +105,16 @@ var verifyCmd = &cobra.Command{ } searchLogQuery.LogIndexes = []*int64{&logIndexInt} } else { - var entry models.ProposedEntry - switch viper.GetString("type") { - case "rekord": - entry, err = CreateRekordFromPFlags() - if err != nil { - return nil, err - } - case "rpm": - entry, err = CreateRpmFromPFlags() - if err != nil { - return nil, err - } - default: - return nil, errors.New("invalid type specified") + typeStr, versionStr, err := ParseTypeFlag(viper.GetString("type")) + if err != nil { + return nil, err + } + + props := CreatePropsFromPflags() + + entry, err := types.NewProposedEntry(context.Background(), typeStr, versionStr, *props) + if err != nil { + return nil, err } entries := []models.ProposedEntry{entry} @@ -166,6 +163,7 @@ var verifyCmd = &cobra.Command{ } func init() { + initializePFlagMap() if err := addArtifactPFlags(verifyCmd); err != nil { log.Logger.Fatal("Error parsing cmd line args:", err) } diff --git a/docker-compose.yml b/docker-compose.yml index cd75a61c5..b550a06e1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -93,7 +93,7 @@ services: # "--log_type=prod", ] volumes: - - "/var/run/attestations:/var/run/attestations" + - "/var/run/attestations:/var/run/attestations:z" restart: always # keep the server running ports: - "3000:3000" diff --git a/pkg/pki/factory/factory.go b/pkg/pki/factory/factory.go index 5fb99e90b..f60d0e43b 100644 --- a/pkg/pki/factory/factory.go +++ b/pkg/pki/factory/factory.go @@ -27,12 +27,22 @@ import ( "github.com/sigstore/rekor/pkg/pki/x509" ) +type PKIFormat string + +const ( + PGP PKIFormat = "pgp" + Minisign PKIFormat = "minisign" + SSH PKIFormat = "ssh" + X509 PKIFormat = "x509" + PKCS7 PKIFormat = "pkcs7" +) + type ArtifactFactory struct { impl pkiImpl } func NewArtifactFactory(format string) (*ArtifactFactory, error) { - if impl, ok := artifactFactoryMap[format]; ok { + if impl, ok := artifactFactoryMap[PKIFormat(format)]; ok { return &ArtifactFactory{impl: impl}, nil } return nil, fmt.Errorf("%v is not a supported PKI format", format) @@ -43,27 +53,27 @@ type pkiImpl struct { newSignature func(io.Reader) (pki.Signature, error) } -var artifactFactoryMap map[string]pkiImpl +var artifactFactoryMap map[PKIFormat]pkiImpl func init() { - artifactFactoryMap = map[string]pkiImpl{ - "pgp": { + artifactFactoryMap = map[PKIFormat]pkiImpl{ + PGP: { newPubKey: pgp.NewPublicKey, newSignature: pgp.NewSignature, }, - "minisign": { + Minisign: { newPubKey: minisign.NewPublicKey, newSignature: minisign.NewSignature, }, - "ssh": { + SSH: { newPubKey: ssh.NewPublicKey, newSignature: ssh.NewSignature, }, - "x509": { + X509: { newPubKey: x509.NewPublicKey, newSignature: x509.NewSignature, }, - "pkcs7": { + PKCS7: { newPubKey: pkcs7.NewPublicKey, newSignature: pkcs7.NewSignature, }, @@ -73,7 +83,7 @@ func init() { func SupportedFormats() []string { var formats []string for f := range artifactFactoryMap { - formats = append(formats, f) + formats = append(formats, string(f)) } return formats } diff --git a/pkg/types/entries.go b/pkg/types/entries.go index a22f79076..4fd6b23ed 100644 --- a/pkg/types/entries.go +++ b/pkg/types/entries.go @@ -20,6 +20,7 @@ import ( "encoding/base64" "errors" "fmt" + "net/url" "reflect" "github.com/mitchellh/mapstructure" @@ -36,11 +37,23 @@ type EntryImpl interface { Unmarshal(e models.ProposedEntry) error // unmarshal the abstract entry into the specific struct for this versioned type Validate() error // performs any cross-field validation that is not expressed in the OpenAPI spec Attestation() (string, []byte) + CreateFromPFlags(context.Context, ArtifactProperties) (models.ProposedEntry, error) } // EntryFactory describes a factory function that can generate structs for a specific versioned type type EntryFactory func() EntryImpl +func NewProposedEntry(ctx context.Context, kind, version string, props ArtifactProperties) (models.ProposedEntry, error) { + if tf, found := TypeMap.Load(kind); found { + t := tf.(func() TypeImpl)() + if t == nil { + return nil, fmt.Errorf("error generating object for kind '%v'", kind) + } + return t.CreateProposedEntry(ctx, version, props) + } + return nil, fmt.Errorf("could not create entry for kind '%v'", kind) +} + // NewEntry returns the specific instance for the type and version specified in the doc func NewEntry(pe models.ProposedEntry) (EntryImpl, error) { if pe == nil { @@ -88,3 +101,16 @@ func DecodeEntry(input, output interface{}) error { return dec.Decode(input) } + +// ArtifactProperties provide a consistent struct for passing values from +// CLI flags to the type+version specific CreateProposeEntry() methods +type ArtifactProperties struct { + ArtifactPath *url.URL + ArtifactHash string + ArtifactBytes []byte + SignaturePath *url.URL + SignatureBytes []byte + PublicKeyPath *url.URL + PublicKeyBytes []byte + PKIFormat string +} diff --git a/pkg/types/intoto/intoto.go b/pkg/types/intoto/intoto.go index 1b2138e27..c62b3606c 100644 --- a/pkg/types/intoto/intoto.go +++ b/pkg/types/intoto/intoto.go @@ -16,8 +16,9 @@ package intoto import ( - "errors" + "context" + "github.com/pkg/errors" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" ) @@ -55,3 +56,18 @@ func (it BaseIntotoType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImp return it.VersionedUnmarshal(in, *in.APIVersion) } + +func (it *BaseIntotoType) CreateProposedEntry(ctx context.Context, version string, props types.ArtifactProperties) (models.ProposedEntry, error) { + if version == "" { + version = it.DefaultVersion() + } + ei, err := it.VersionedUnmarshal(nil, version) + if err != nil { + return nil, errors.Wrap(err, "fetching Intoto version implementation") + } + return ei.CreateFromPFlags(ctx, props) +} + +func (it BaseIntotoType) DefaultVersion() string { + return "0.0.1" +} diff --git a/pkg/types/intoto/intoto_test.go b/pkg/types/intoto/intoto_test.go index 01b91d1c9..404097271 100644 --- a/pkg/types/intoto/intoto_test.go +++ b/pkg/types/intoto/intoto_test.go @@ -66,6 +66,10 @@ func (u UnmarshalTester) Attestation() (string, []byte) { return "", nil } +func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { + return nil, nil +} + type UnmarshalFailsTester struct { UnmarshalTester } diff --git a/pkg/types/intoto/v0.0.1/entry.go b/pkg/types/intoto/v0.0.1/entry.go index cd9f3b132..35ab9f0b6 100644 --- a/pkg/types/intoto/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.1/entry.go @@ -24,6 +24,9 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" + "io/ioutil" + "path/filepath" "github.com/in-toto/in-toto-golang/pkg/ssl" "github.com/spf13/viper" @@ -207,3 +210,44 @@ func (v *verifier) Verify(keyID string, data, sig []byte) (bool, error) { } return true, nil } + +func (v V001Entry) CreateFromPFlags(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { + returnVal := models.Intoto{} + + var err error + artifactBytes := props.ArtifactBytes + if artifactBytes == nil { + if props.ArtifactPath == nil { + return nil, errors.New("invalid path to artifact specified") + } + artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) + if err != nil { + return nil, err + } + } + publicKeyBytes := props.PublicKeyBytes + if publicKeyBytes == nil { + if props.PublicKeyPath == nil { + return nil, errors.New("invalid path to public key specified") + } + publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + } + kb := strfmt.Base64(publicKeyBytes) + + re := V001Entry{ + IntotoObj: models.IntotoV001Schema{ + Content: &models.IntotoV001SchemaContent{ + Envelope: string(artifactBytes), + }, + PublicKey: &kb, + }, + } + + returnVal.Spec = re.IntotoObj + returnVal.APIVersion = swag.String(re.APIVersion()) + + return &returnVal, nil +} diff --git a/pkg/types/jar/jar.go b/pkg/types/jar/jar.go index 346fb1e1d..8ea07f4f7 100644 --- a/pkg/types/jar/jar.go +++ b/pkg/types/jar/jar.go @@ -16,8 +16,9 @@ package jar import ( - "errors" + "context" + "github.com/pkg/errors" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" ) @@ -55,3 +56,18 @@ func (bjt *BaseJARType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImpl return bjt.VersionedUnmarshal(jar, *jar.APIVersion) } + +func (bjt *BaseJARType) CreateProposedEntry(ctx context.Context, version string, props types.ArtifactProperties) (models.ProposedEntry, error) { + if version == "" { + version = bjt.DefaultVersion() + } + ei, err := bjt.VersionedUnmarshal(nil, version) + if err != nil { + return nil, errors.Wrap(err, "fetching JAR version implementation") + } + return ei.CreateFromPFlags(ctx, props) +} + +func (bjt BaseJARType) DefaultVersion() string { + return "0.0.1" +} diff --git a/pkg/types/jar/jar_test.go b/pkg/types/jar/jar_test.go index 9c3a6091e..f49c39bf9 100644 --- a/pkg/types/jar/jar_test.go +++ b/pkg/types/jar/jar_test.go @@ -65,6 +65,10 @@ func (u UnmarshalTester) Attestation() (string, []byte) { return "", nil } +func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { + return nil, nil +} + type UnmarshalFailsTester struct { UnmarshalTester } diff --git a/pkg/types/jar/v0.0.1/entry.go b/pkg/types/jar/v0.0.1/entry.go index 6f2e239ca..c16865d67 100644 --- a/pkg/types/jar/v0.0.1/entry.go +++ b/pkg/types/jar/v0.0.1/entry.go @@ -27,6 +27,7 @@ import ( "io" "io/ioutil" "path" + "path/filepath" "strings" "github.com/sigstore/rekor/pkg/log" @@ -320,3 +321,53 @@ func extractPKCS7SignatureFromJAR(inz *zip.Reader) ([]byte, error) { func (v V001Entry) Attestation() (string, []byte) { return "", nil } + +func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { + returnVal := models.Jar{} + re := V001Entry{} + + // we will need only the artifact; public-key & signature are embedded in JAR + re.JARModel = models.JarV001Schema{} + re.JARModel.Archive = &models.JarV001SchemaArchive{} + + var err error + artifactBytes := props.ArtifactBytes + if artifactBytes == nil { + if props.ArtifactPath == nil { + return nil, errors.New("invalid path to artifact") + } + if props.ArtifactPath.IsAbs() { + re.JARModel.Archive.URL = strfmt.URI(props.ArtifactPath.String()) + if props.ArtifactHash != "" { + re.JARModel.Archive.Hash = &models.JarV001SchemaArchiveHash{ + Algorithm: swag.String(models.JarV001SchemaArchiveHashAlgorithmSha256), + Value: swag.String(props.ArtifactHash), + } + } + } else { + artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) + if err != nil { + return nil, fmt.Errorf("error reading artifact file: %w", err) + } + //TODO: ensure this is a valid JAR file; look for META-INF/MANIFEST.MF? + re.JARModel.Archive.Content = strfmt.Base64(artifactBytes) + } + } else { + re.JARModel.Archive.Content = strfmt.Base64(artifactBytes) + } + + if err := re.Validate(); err != nil { + return nil, err + } + + if re.HasExternalEntities() { + if err := re.FetchExternalEntities(ctx); err != nil { + return nil, fmt.Errorf("error retrieving external entities: %v", err) + } + } + + returnVal.APIVersion = swag.String(re.APIVersion()) + returnVal.Spec = re.JARModel + + return &returnVal, nil +} diff --git a/pkg/types/rekord/rekord.go b/pkg/types/rekord/rekord.go index 0741782d6..8ffeb022d 100644 --- a/pkg/types/rekord/rekord.go +++ b/pkg/types/rekord/rekord.go @@ -16,8 +16,9 @@ package rekord import ( - "errors" + "context" + "github.com/pkg/errors" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" ) @@ -55,3 +56,18 @@ func (rt BaseRekordType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImp return rt.VersionedUnmarshal(rekord, *rekord.APIVersion) } + +func (rt *BaseRekordType) CreateProposedEntry(ctx context.Context, version string, props types.ArtifactProperties) (models.ProposedEntry, error) { + if version == "" { + version = rt.DefaultVersion() + } + ei, err := rt.VersionedUnmarshal(nil, version) + if err != nil { + return nil, errors.Wrap(err, "fetching Rekord version implementation") + } + return ei.CreateFromPFlags(ctx, props) +} + +func (rt BaseRekordType) DefaultVersion() string { + return "0.0.1" +} diff --git a/pkg/types/rekord/rekord_test.go b/pkg/types/rekord/rekord_test.go index 9463bca6a..22b412db9 100644 --- a/pkg/types/rekord/rekord_test.go +++ b/pkg/types/rekord/rekord_test.go @@ -66,6 +66,10 @@ func (u UnmarshalTester) Attestation() (string, []byte) { return "", nil } +func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { + return nil, nil +} + type UnmarshalFailsTester struct { UnmarshalTester } diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index 36b7ca4af..848a388ee 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -23,6 +23,8 @@ import ( "errors" "fmt" "io" + "io/ioutil" + "path/filepath" "strings" "github.com/asaskevich/govalidator" @@ -387,3 +389,100 @@ func (v V001Entry) Validate() error { func (v V001Entry) Attestation() (string, []byte) { return "", nil } + +func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { + returnVal := models.Rekord{} + re := V001Entry{} + + // we will need artifact, public-key, signature + re.RekordObj.Data = &models.RekordV001SchemaData{} + + var err error + artifactBytes := props.ArtifactBytes + if artifactBytes == nil { + if props.ArtifactPath == nil { + return nil, errors.New("invalid path to artifact") + } + if props.ArtifactPath.IsAbs() { + re.RekordObj.Data.URL = strfmt.URI(props.ArtifactPath.String()) + if props.ArtifactHash != "" { + re.RekordObj.Data.Hash = &models.RekordV001SchemaDataHash{ + Algorithm: swag.String(models.RekordV001SchemaDataHashAlgorithmSha256), + Value: swag.String(props.ArtifactHash), + } + } + } else { + artifactBytes, err := ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) + if err != nil { + return nil, fmt.Errorf("error reading artifact file: %w", err) + } + re.RekordObj.Data.Content = strfmt.Base64(artifactBytes) + } + } else { + re.RekordObj.Data.Content = strfmt.Base64(artifactBytes) + } + + re.RekordObj.Signature = &models.RekordV001SchemaSignature{} + switch props.PKIFormat { + case "pgp": + re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatPgp + case "minisign": + re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatMinisign + case "x509": + re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatX509 + case "ssh": + re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatSSH + } + sigBytes := props.SignatureBytes + if sigBytes == nil { + if props.SignaturePath == nil { + return nil, errors.New("invalid path to signature specified") + } + if props.SignaturePath.IsAbs() { + re.RekordObj.Signature.URL = strfmt.URI(props.SignaturePath.String()) + } else { + sigBytes, err = ioutil.ReadFile(filepath.Clean(props.SignaturePath.Path)) + if err != nil { + return nil, fmt.Errorf("error reading signature file: %w", err) + } + re.RekordObj.Signature.Content = strfmt.Base64(sigBytes) + } + } else { + re.RekordObj.Signature.Content = strfmt.Base64(sigBytes) + } + + re.RekordObj.Signature.PublicKey = &models.RekordV001SchemaSignaturePublicKey{} + publicKeyBytes := props.PublicKeyBytes + if publicKeyBytes == nil { + if props.PublicKeyPath == nil { + return nil, errors.New("invalid path to public key specified") + } + if props.PublicKeyPath.IsAbs() { + re.RekordObj.Signature.PublicKey.URL = strfmt.URI(props.PublicKeyPath.String()) + } else { + publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + re.RekordObj.Signature.PublicKey.Content = strfmt.Base64(publicKeyBytes) + } + } else { + re.RekordObj.Signature.PublicKey.Content = strfmt.Base64(publicKeyBytes) + + } + + if err := re.Validate(); err != nil { + return nil, err + } + + if re.HasExternalEntities() { + if err := re.FetchExternalEntities(ctx); err != nil { + return nil, fmt.Errorf("error retrieving external entities: %v", err) + } + } + + returnVal.APIVersion = swag.String(re.APIVersion()) + returnVal.Spec = re.RekordObj + + return &returnVal, nil +} diff --git a/pkg/types/rfc3161/rfc3161.go b/pkg/types/rfc3161/rfc3161.go index f3e2ee99e..00363ffac 100644 --- a/pkg/types/rfc3161/rfc3161.go +++ b/pkg/types/rfc3161/rfc3161.go @@ -16,8 +16,9 @@ package rfc3161 import ( - "errors" + "context" + "github.com/pkg/errors" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" ) @@ -35,15 +36,15 @@ func init() { } func New() types.TypeImpl { - brt := BaseTimestampType{} - brt.Kind = KIND - brt.VersionMap = VersionMap - return &brt + btt := BaseTimestampType{} + btt.Kind = KIND + btt.VersionMap = VersionMap + return &btt } var VersionMap = types.NewSemVerEntryFactoryMap() -func (rt BaseTimestampType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImpl, error) { +func (btt BaseTimestampType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImpl, error) { if pe == nil { return nil, errors.New("proposed entry cannot be nil") } @@ -53,5 +54,20 @@ func (rt BaseTimestampType) UnmarshalEntry(pe models.ProposedEntry) (types.Entry return nil, errors.New("cannot unmarshal non-Timestamp types") } - return rt.VersionedUnmarshal(rfc3161, *rfc3161.APIVersion) + return btt.VersionedUnmarshal(rfc3161, *rfc3161.APIVersion) +} + +func (btt *BaseTimestampType) CreateProposedEntry(ctx context.Context, version string, props types.ArtifactProperties) (models.ProposedEntry, error) { + if version == "" { + version = btt.DefaultVersion() + } + ei, err := btt.VersionedUnmarshal(nil, version) + if err != nil { + return nil, errors.Wrap(err, "fetching RFC3161 version implementation") + } + return ei.CreateFromPFlags(ctx, props) +} + +func (btt BaseTimestampType) DefaultVersion() string { + return "0.0.1" } diff --git a/pkg/types/rfc3161/rfc3161_test.go b/pkg/types/rfc3161/rfc3161_test.go index 0ed66a198..18c0e4a39 100644 --- a/pkg/types/rfc3161/rfc3161_test.go +++ b/pkg/types/rfc3161/rfc3161_test.go @@ -69,6 +69,9 @@ func (u UnmarshalTester) Unmarshal(pe models.ProposedEntry) error { func (u UnmarshalFailsTester) Attestation() (string, []byte) { return "", nil } +func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { + return nil, nil +} type UnmarshalFailsTester struct { UnmarshalTester diff --git a/pkg/types/rfc3161/v0.0.1/entry.go b/pkg/types/rfc3161/v0.0.1/entry.go index bfad21435..4ee953dab 100644 --- a/pkg/types/rfc3161/v0.0.1/entry.go +++ b/pkg/types/rfc3161/v0.0.1/entry.go @@ -24,6 +24,8 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" + "path/filepath" "github.com/sigstore/rekor/pkg/types/rfc3161" @@ -159,14 +161,14 @@ func (v V001Entry) Validate() error { return err } if tsr.Status.Status != pkcs9.StatusGranted && tsr.Status.Status != pkcs9.StatusGrantedWithMods { - return fmt.Errorf("Tsr status not granted: %v", tsr.Status.Status) + return fmt.Errorf("tsr status not granted: %v", tsr.Status.Status) } if !tsr.TimeStampToken.ContentType.Equal(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}) { - return fmt.Errorf("Tsr wrong content type: %v", tsr.TimeStampToken.ContentType) + return fmt.Errorf("tsr wrong content type: %v", tsr.TimeStampToken.ContentType) } _, err = tsr.TimeStampToken.Content.Verify(nil, false) if err != nil { - return fmt.Errorf("Tsr verification error: %v", err) + return fmt.Errorf("tsr verification error: %v", err) } return nil @@ -175,3 +177,33 @@ func (v V001Entry) Validate() error { func (v V001Entry) Attestation() (string, []byte) { return "", nil } + +func (v V001Entry) CreateFromPFlags(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { + returnVal := models.Rfc3161{} + + var err error + artifactBytes := props.ArtifactBytes + if artifactBytes == nil { + if props.ArtifactPath == nil { + return nil, errors.New("invalid path to artifact specified") + } + artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + } + + b64 := strfmt.Base64(artifactBytes) + re := V001Entry{ + Rfc3161Obj: models.Rfc3161V001Schema{ + Tsr: &models.Rfc3161V001SchemaTsr{ + Content: &b64, + }, + }, + } + + returnVal.Spec = re.Rfc3161Obj + returnVal.APIVersion = swag.String(re.APIVersion()) + + return &returnVal, nil +} diff --git a/pkg/types/rfc3161/v0.0.1/entry_test.go b/pkg/types/rfc3161/v0.0.1/entry_test.go index a20856650..e2d2711a3 100644 --- a/pkg/types/rfc3161/v0.0.1/entry_test.go +++ b/pkg/types/rfc3161/v0.0.1/entry_test.go @@ -126,7 +126,7 @@ func TestCrossFieldValidation(t *testing.T) { hasExtEntities: false, expectUnmarshalSuccess: false, expectCanonicalizeSuccess: true, - expectValidationErrorMessage: "Tsr status not granted: 2", + expectValidationErrorMessage: "tsr status not granted: 2", }, { caseDesc: "invalid obj - bad content type", @@ -140,7 +140,7 @@ func TestCrossFieldValidation(t *testing.T) { hasExtEntities: false, expectUnmarshalSuccess: false, expectCanonicalizeSuccess: true, - expectValidationErrorMessage: "Tsr wrong content type: 0.0.0.0.42", + expectValidationErrorMessage: "tsr wrong content type: 0.0.0.0.42", }, { caseDesc: "invalid obj - bad content", @@ -154,7 +154,7 @@ func TestCrossFieldValidation(t *testing.T) { hasExtEntities: false, expectUnmarshalSuccess: false, expectCanonicalizeSuccess: true, - expectValidationErrorMessage: "Tsr verification error", + expectValidationErrorMessage: "tsr verification error", }, { caseDesc: "valid obj with extra data", diff --git a/pkg/types/rpm/rpm.go b/pkg/types/rpm/rpm.go index 42a671cc6..043acf499 100644 --- a/pkg/types/rpm/rpm.go +++ b/pkg/types/rpm/rpm.go @@ -16,8 +16,9 @@ package rpm import ( - "errors" + "context" + "github.com/pkg/errors" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" ) @@ -55,3 +56,18 @@ func (brt *BaseRPMType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImpl return brt.VersionedUnmarshal(rpm, *rpm.APIVersion) } + +func (brt *BaseRPMType) CreateProposedEntry(ctx context.Context, version string, props types.ArtifactProperties) (models.ProposedEntry, error) { + if version == "" { + version = brt.DefaultVersion() + } + ei, err := brt.VersionedUnmarshal(nil, version) + if err != nil { + return nil, errors.Wrap(err, "fetching RPM version implementation") + } + return ei.CreateFromPFlags(ctx, props) +} + +func (brt BaseRPMType) DefaultVersion() string { + return "0.0.1" +} diff --git a/pkg/types/rpm/rpm_test.go b/pkg/types/rpm/rpm_test.go index e1ff77bec..45de75e6b 100644 --- a/pkg/types/rpm/rpm_test.go +++ b/pkg/types/rpm/rpm_test.go @@ -66,6 +66,10 @@ func (u UnmarshalTester) Attestation() (string, []byte) { return "", nil } +func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { + return nil, nil +} + type UnmarshalFailsTester struct { UnmarshalTester } diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index 5bcb5231c..bd59dedb3 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -24,6 +24,7 @@ import ( "fmt" "io" "io/ioutil" + "path/filepath" "strconv" "strings" @@ -367,3 +368,65 @@ func (v V001Entry) Validate() error { func (v V001Entry) Attestation() (string, []byte) { return "", nil } + +func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { + returnVal := models.Rpm{} + re := V001Entry{} + + // we will need artifact, public-key, signature + re.RPMModel = models.RpmV001Schema{} + re.RPMModel.Package = &models.RpmV001SchemaPackage{} + + var err error + artifactBytes := props.ArtifactBytes + if artifactBytes == nil { + if props.ArtifactPath == nil { + return nil, errors.New("invalid path to artifact") + } + if props.ArtifactPath.IsAbs() { + re.RPMModel.Package.URL = strfmt.URI(props.ArtifactPath.String()) + } else { + artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) + if err != nil { + return nil, fmt.Errorf("error reading artifact file: %w", err) + } + re.RPMModel.Package.Content = strfmt.Base64(artifactBytes) + } + } else { + re.RPMModel.Package.Content = strfmt.Base64(artifactBytes) + } + + re.RPMModel.PublicKey = &models.RpmV001SchemaPublicKey{} + publicKeyBytes := props.PublicKeyBytes + if publicKeyBytes == nil { + if props.PublicKeyPath == nil { + return nil, errors.New("invalid path to artifact") + } + if props.PublicKeyPath.IsAbs() { + re.RPMModel.PublicKey.URL = strfmt.URI(props.PublicKeyPath.String()) + } else { + publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + re.RPMModel.PublicKey.Content = strfmt.Base64(publicKeyBytes) + } + } else { + re.RPMModel.PublicKey.Content = strfmt.Base64(publicKeyBytes) + } + + if err := re.Validate(); err != nil { + return nil, err + } + + if re.HasExternalEntities() { + if err := re.FetchExternalEntities(context.Background()); err != nil { + return nil, fmt.Errorf("error retrieving external entities: %v", err) + } + } + + returnVal.APIVersion = swag.String(re.APIVersion()) + returnVal.Spec = re.RPMModel + + return &returnVal, nil +} diff --git a/pkg/types/types.go b/pkg/types/types.go index 8095c39fd..85961f8b9 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -16,6 +16,7 @@ package types import ( + "context" "errors" "fmt" "sync" @@ -37,26 +38,43 @@ type RekorType struct { // TypeImpl is implemented by all types to support the polymorphic conversion of abstract // proposed entry to a working implementation for the versioned type requested, if supported type TypeImpl interface { + CreateProposedEntry(context.Context, string, ArtifactProperties) (models.ProposedEntry, error) + DefaultVersion() string + SupportedVersions() []string UnmarshalEntry(pe models.ProposedEntry) (EntryImpl, error) } // VersionedUnmarshal extracts the correct implementing factory function from the type's version map, // creates an entry of that versioned type and then calls that versioned type's unmarshal method func (rt *RekorType) VersionedUnmarshal(pe models.ProposedEntry, version string) (EntryImpl, error) { - if pe == nil { - return nil, errors.New("proposed entry cannot be nil") - } - ef, err := rt.VersionMap.GetEntryFactory(version) if err != nil { - return nil, fmt.Errorf("%s implementation for version '%v' not found: %w", pe.Kind(), version, err) + return nil, fmt.Errorf("%s implementation for version '%v' not found: %w", rt.Kind, version, err) } entry := ef() if entry == nil { return nil, errors.New("failure generating object") } - if err := entry.Unmarshal(pe); err != nil { - return nil, err + if pe == nil { + return entry, nil } - return entry, nil + return entry, entry.Unmarshal(pe) +} + +func (rt *RekorType) SupportedVersions() []string { + return rt.VersionMap.SupportedVersions() +} + +// ListImplementedTypes returns a list of all type strings currently known to +// be implemented +func ListImplementedTypes() []string { + retVal := []string{} + TypeMap.Range(func(k interface{}, v interface{}) bool { + tf := v.(func() TypeImpl) + for _, verStr := range tf().SupportedVersions() { + retVal = append(retVal, fmt.Sprintf("%v:%v", k.(string), verStr)) + } + return true + }) + return retVal } diff --git a/pkg/types/versionmap.go b/pkg/types/versionmap.go index 628576b2c..5d8c14055 100644 --- a/pkg/types/versionmap.go +++ b/pkg/types/versionmap.go @@ -29,6 +29,7 @@ type VersionEntryFactoryMap interface { GetEntryFactory(string) (EntryFactory, error) // return the entry factory for the specified version SetEntryFactory(string, EntryFactory) error // set the entry factory for the specified version Count() int // return the count of entry factories currently in the map + SupportedVersions() []string // return a list of versions currently stored in the map } // SemVerEntryFactoryMap implements a map that allows implementations to specify their supported versions using @@ -86,3 +87,11 @@ func (s *SemVerEntryFactoryMap) SetEntryFactory(constraint string, ef EntryFacto s.factoryMap[constraint] = ef return nil } + +func (s *SemVerEntryFactoryMap) SupportedVersions() []string { + var versions []string + for k := range s.factoryMap { + versions = append(versions, k) + } + return versions +} From 604ad854493fe76e9496f2cf847fc81473ca6565 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Sun, 20 Jun 2021 11:38:41 -0400 Subject: [PATCH 3/8] Add Alpine Package type This adds support for the alpine package format used by Alpine Linux, which is the concatenation of three tgz files (signature, control data, and then the actual package files). Signed-off-by: Bob Callaway --- cmd/rekor-cli/app/pflags_test.go | 31 +- cmd/rekor-cli/app/root.go | 1 + cmd/rekor-server/app/serve.go | 3 + go.mod | 1 + openapi.yaml | 21 +- pkg/generated/models/alpine.go | 210 ++++++++ pkg/generated/models/alpine_schema.go | 29 ++ pkg/generated/models/alpine_v001_schema.go | 450 ++++++++++++++++++ pkg/generated/models/proposed_entry.go | 6 + pkg/generated/models/rpm.go | 2 +- pkg/generated/restapi/embedded_spec.go | 274 ++++++++++- pkg/pki/x509/x509.go | 4 + pkg/types/alpine/alpine.go | 73 +++ pkg/types/alpine/alpine_schema.json | 12 + pkg/types/alpine/alpine_test.go | 132 +++++ pkg/types/alpine/apk.go | 245 ++++++++++ pkg/types/alpine/apk_test.go | 50 ++ .../alpine/v0.0.1/alpine_v0_0_1_schema.json | 86 ++++ pkg/types/alpine/v0.0.1/entry.go | 405 ++++++++++++++++ pkg/types/alpine/v0.0.1/entry_test.go | 394 +++++++++++++++ pkg/types/rpm/v0.0.1/entry.go | 7 +- tests/alpine.json | 1 + tests/apk.go | 92 ++++ tests/e2e_test.go | 19 +- tests/test_alpine.apk | Bin 0 -> 2874 bytes tests/test_alpine.pub | 9 + 26 files changed, 2544 insertions(+), 13 deletions(-) create mode 100644 pkg/generated/models/alpine.go create mode 100644 pkg/generated/models/alpine_schema.go create mode 100644 pkg/generated/models/alpine_v001_schema.go create mode 100644 pkg/types/alpine/alpine.go create mode 100644 pkg/types/alpine/alpine_schema.json create mode 100644 pkg/types/alpine/alpine_test.go create mode 100644 pkg/types/alpine/apk.go create mode 100644 pkg/types/alpine/apk_test.go create mode 100644 pkg/types/alpine/v0.0.1/alpine_v0_0_1_schema.json create mode 100644 pkg/types/alpine/v0.0.1/entry.go create mode 100644 pkg/types/alpine/v0.0.1/entry_test.go create mode 100644 tests/alpine.json create mode 100644 tests/apk.go create mode 100644 tests/test_alpine.apk create mode 100644 tests/test_alpine.pub diff --git a/cmd/rekor-cli/app/pflags_test.go b/cmd/rekor-cli/app/pflags_test.go index 89d4d6e5c..65c454d56 100644 --- a/cmd/rekor-cli/app/pflags_test.go +++ b/cmd/rekor-cli/app/pflags_test.go @@ -18,7 +18,6 @@ package app import ( "context" "errors" - "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -66,6 +65,12 @@ func TestArtifactPFlags(t *testing.T) { file, err = ioutil.ReadFile("../../../tests/test.rpm") case "/rpmPublicKey": file, err = ioutil.ReadFile("../../../tests/test_rpm_public_key.key") + case "/alpine": + file, err = ioutil.ReadFile("../../../tests/test_alpine.apk") + case "/alpinePublicKey": + file, err = ioutil.ReadFile("../../../tests/test_alpine.pub") + case "/alpineEntry": + file, err = ioutil.ReadFile("../../../tests/alpine.json") case "/not_found": err = errors.New("file not found") } @@ -112,6 +117,13 @@ func TestArtifactPFlags(t *testing.T) { expectParseSuccess: true, expectValidateSuccess: true, }, + { + caseDesc: "valid alpine URL", + entry: testServer.URL + "/alpineEntry", + typeStr: "alpine", + expectParseSuccess: true, + expectValidateSuccess: true, + }, { caseDesc: "valid rpm file, wrong type", typeStr: "rekord", @@ -147,6 +159,14 @@ func TestArtifactPFlags(t *testing.T) { expectParseSuccess: true, expectValidateSuccess: true, }, + { + caseDesc: "valid alpine - local artifact with required flags", + typeStr: "alpine", + artifact: "../../../tests/test_alpine.apk", + publicKey: "../../../tests/test_alpine.pub", + expectParseSuccess: true, + expectValidateSuccess: true, + }, { caseDesc: "nonexistant local artifact", artifact: "../../../tests/not_a_file", @@ -233,6 +253,14 @@ func TestArtifactPFlags(t *testing.T) { expectParseSuccess: true, expectValidateSuccess: true, }, + { + caseDesc: "valid alpine - remote artifact with required flags", + typeStr: "alpine", + artifact: testServer.URL + "/alpine", + publicKey: "../../../tests/test_alpine.pub", + expectParseSuccess: true, + expectValidateSuccess: true, + }, { caseDesc: "remote artifact with invalid URL", artifact: "hteeteep%**/test_file.txt", @@ -366,7 +394,6 @@ func TestArtifactPFlags(t *testing.T) { t.Errorf("error parsing typeStr: %v", err) } props := CreatePropsFromPflags() - fmt.Println(props) if _, err := types.NewProposedEntry(context.Background(), typeStr, versionStr, *props); err != nil { t.Errorf("unexpected result in '%v' building entry: %v", tc.caseDesc, err) } diff --git a/cmd/rekor-cli/app/root.go b/cmd/rekor-cli/app/root.go index d17e02484..56f76c192 100644 --- a/cmd/rekor-cli/app/root.go +++ b/cmd/rekor-cli/app/root.go @@ -33,6 +33,7 @@ import ( "github.com/sigstore/rekor/pkg/util" // these imports are to call the packages' init methods + _ "github.com/sigstore/rekor/pkg/types/alpine/v0.0.1" _ "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" _ "github.com/sigstore/rekor/pkg/types/jar/v0.0.1" _ "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1" diff --git a/cmd/rekor-server/app/serve.go b/cmd/rekor-server/app/serve.go index 70dcad763..278b926e8 100644 --- a/cmd/rekor-server/app/serve.go +++ b/cmd/rekor-server/app/serve.go @@ -28,6 +28,8 @@ import ( "github.com/sigstore/rekor/pkg/generated/restapi" "github.com/sigstore/rekor/pkg/generated/restapi/operations" "github.com/sigstore/rekor/pkg/log" + "github.com/sigstore/rekor/pkg/types/alpine" + alpine_v001 "github.com/sigstore/rekor/pkg/types/alpine/v0.0.1" "github.com/sigstore/rekor/pkg/types/intoto" intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" "github.com/sigstore/rekor/pkg/types/jar" @@ -79,6 +81,7 @@ var serveCmd = &cobra.Command{ jar.KIND: jar_v001.APIVERSION, intoto.KIND: intoto_v001.APIVERSION, rfc3161.KIND: rfc3161_v001.APIVERSION, + alpine.KIND: alpine_v001.APIVERSION, } for k, v := range pluggableTypeMap { diff --git a/go.mod b/go.mod index 0f4d79c40..5ad82708a 100644 --- a/go.mod +++ b/go.mod @@ -48,4 +48,5 @@ require ( google.golang.org/grpc v1.38.0 google.golang.org/protobuf v1.26.0 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/ini.v1 v1.62.0 ) diff --git a/openapi.yaml b/openapi.yaml index 5a86f3172..c363823ff 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -304,7 +304,7 @@ definitions: rpm: type: object - description: RPM object + description: RPM package allOf: - $ref: '#/definitions/ProposedEntry' - properties: @@ -318,6 +318,24 @@ definitions: - apiVersion - spec additionalProperties: false + + alpine: + type: object + description: Alpine package + allOf: + - $ref: '#/definitions/ProposedEntry' + - properties: + apiVersion: + type: string + pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + spec: + type: object + $ref: 'pkg/types/alpine/alpine_schema.json' + required: + - apiVersion + - spec + additionalProperties: false + intoto: type: object description: Intoto object @@ -334,6 +352,7 @@ definitions: - apiVersion - spec additionalProperties: false + jar: type: object description: Java Archive (JAR) diff --git a/pkg/generated/models/alpine.go b/pkg/generated/models/alpine.go new file mode 100644 index 000000000..5607679fd --- /dev/null +++ b/pkg/generated/models/alpine.go @@ -0,0 +1,210 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// Copyright 2021 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 models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// Alpine Alpine package +// +// swagger:model alpine +type Alpine struct { + + // api version + // Required: true + // Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + APIVersion *string `json:"apiVersion"` + + // spec + // Required: true + Spec AlpineSchema `json:"spec"` +} + +// Kind gets the kind of this subtype +func (m *Alpine) Kind() string { + return "alpine" +} + +// SetKind sets the kind of this subtype +func (m *Alpine) SetKind(val string) { +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *Alpine) UnmarshalJSON(raw []byte) error { + var data struct { + + // api version + // Required: true + // Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + APIVersion *string `json:"apiVersion"` + + // spec + // Required: true + Spec AlpineSchema `json:"spec"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + Kind string `json:"kind"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result Alpine + + if base.Kind != result.Kind() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid kind value: %q", base.Kind) + } + + result.APIVersion = data.APIVersion + result.Spec = data.Spec + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m Alpine) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // api version + // Required: true + // Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + APIVersion *string `json:"apiVersion"` + + // spec + // Required: true + Spec AlpineSchema `json:"spec"` + }{ + + APIVersion: m.APIVersion, + + Spec: m.Spec, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + Kind string `json:"kind"` + }{ + + Kind: m.Kind(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this alpine +func (m *Alpine) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAPIVersion(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSpec(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Alpine) validateAPIVersion(formats strfmt.Registry) error { + + if err := validate.Required("apiVersion", "body", m.APIVersion); err != nil { + return err + } + + if err := validate.Pattern("apiVersion", "body", *m.APIVersion, `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`); err != nil { + return err + } + + return nil +} + +func (m *Alpine) validateSpec(formats strfmt.Registry) error { + + if m.Spec == nil { + return errors.Required("spec", "body", nil) + } + + return nil +} + +// ContextValidate validate this alpine based on the context it is used +func (m *Alpine) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// MarshalBinary interface implementation +func (m *Alpine) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Alpine) UnmarshalBinary(b []byte) error { + var res Alpine + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/generated/models/alpine_schema.go b/pkg/generated/models/alpine_schema.go new file mode 100644 index 000000000..49dd12b6b --- /dev/null +++ b/pkg/generated/models/alpine_schema.go @@ -0,0 +1,29 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// Copyright 2021 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 models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +// AlpineSchema Alpine Package Schema +// +// Schema for Alpine package objects +// +// swagger:model alpineSchema +type AlpineSchema interface{} diff --git a/pkg/generated/models/alpine_v001_schema.go b/pkg/generated/models/alpine_v001_schema.go new file mode 100644 index 000000000..dbdd83851 --- /dev/null +++ b/pkg/generated/models/alpine_v001_schema.go @@ -0,0 +1,450 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// Copyright 2021 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 models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// AlpineV001Schema Alpine v0.0.1 Schema +// +// Schema for Alpine Package entries +// +// swagger:model alpineV001Schema +type AlpineV001Schema struct { + + // Arbitrary content to be included in the verifiable entry in the transparency log + ExtraData interface{} `json:"extraData,omitempty"` + + // package + // Required: true + Package *AlpineV001SchemaPackage `json:"package"` + + // public key + // Required: true + PublicKey *AlpineV001SchemaPublicKey `json:"publicKey"` +} + +// Validate validates this alpine v001 schema +func (m *AlpineV001Schema) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validatePackage(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePublicKey(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AlpineV001Schema) validatePackage(formats strfmt.Registry) error { + + if err := validate.Required("package", "body", m.Package); err != nil { + return err + } + + if m.Package != nil { + if err := m.Package.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("package") + } + return err + } + } + + return nil +} + +func (m *AlpineV001Schema) validatePublicKey(formats strfmt.Registry) error { + + if err := validate.Required("publicKey", "body", m.PublicKey); err != nil { + return err + } + + if m.PublicKey != nil { + if err := m.PublicKey.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("publicKey") + } + return err + } + } + + return nil +} + +// ContextValidate validate this alpine v001 schema based on the context it is used +func (m *AlpineV001Schema) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidatePackage(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidatePublicKey(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AlpineV001Schema) contextValidatePackage(ctx context.Context, formats strfmt.Registry) error { + + if m.Package != nil { + if err := m.Package.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("package") + } + return err + } + } + + return nil +} + +func (m *AlpineV001Schema) contextValidatePublicKey(ctx context.Context, formats strfmt.Registry) error { + + if m.PublicKey != nil { + if err := m.PublicKey.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("publicKey") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *AlpineV001Schema) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *AlpineV001Schema) UnmarshalBinary(b []byte) error { + var res AlpineV001Schema + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// AlpineV001SchemaPackage Information about the package associated with the entry +// +// swagger:model AlpineV001SchemaPackage +type AlpineV001SchemaPackage struct { + + // Specifies the package inline within the document + // Format: byte + Content strfmt.Base64 `json:"content,omitempty"` + + // hash + Hash *AlpineV001SchemaPackageHash `json:"hash,omitempty"` + + // Values of the .PKGINFO key / value pairs + Pkginfo map[string]string `json:"pkginfo,omitempty"` + + // Specifies the location of the package; if this is specified, a hash value must also be provided + // Format: uri + URL strfmt.URI `json:"url,omitempty"` +} + +// Validate validates this alpine v001 schema package +func (m *AlpineV001SchemaPackage) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateHash(formats); err != nil { + res = append(res, err) + } + + if err := m.validateURL(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AlpineV001SchemaPackage) validateHash(formats strfmt.Registry) error { + if swag.IsZero(m.Hash) { // not required + return nil + } + + if m.Hash != nil { + if err := m.Hash.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("package" + "." + "hash") + } + return err + } + } + + return nil +} + +func (m *AlpineV001SchemaPackage) validateURL(formats strfmt.Registry) error { + if swag.IsZero(m.URL) { // not required + return nil + } + + if err := validate.FormatOf("package"+"."+"url", "body", "uri", m.URL.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this alpine v001 schema package based on the context it is used +func (m *AlpineV001SchemaPackage) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateHash(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AlpineV001SchemaPackage) contextValidateHash(ctx context.Context, formats strfmt.Registry) error { + + if m.Hash != nil { + if err := m.Hash.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("package" + "." + "hash") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *AlpineV001SchemaPackage) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *AlpineV001SchemaPackage) UnmarshalBinary(b []byte) error { + var res AlpineV001SchemaPackage + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// AlpineV001SchemaPackageHash Specifies the hash algorithm and value for the package +// +// swagger:model AlpineV001SchemaPackageHash +type AlpineV001SchemaPackageHash struct { + + // The hashing function used to compute the hash value + // Required: true + // Enum: [sha256] + Algorithm *string `json:"algorithm"` + + // The hash value for the package + // Required: true + Value *string `json:"value"` +} + +// Validate validates this alpine v001 schema package hash +func (m *AlpineV001SchemaPackageHash) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAlgorithm(formats); err != nil { + res = append(res, err) + } + + if err := m.validateValue(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var alpineV001SchemaPackageHashTypeAlgorithmPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["sha256"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + alpineV001SchemaPackageHashTypeAlgorithmPropEnum = append(alpineV001SchemaPackageHashTypeAlgorithmPropEnum, v) + } +} + +const ( + + // AlpineV001SchemaPackageHashAlgorithmSha256 captures enum value "sha256" + AlpineV001SchemaPackageHashAlgorithmSha256 string = "sha256" +) + +// prop value enum +func (m *AlpineV001SchemaPackageHash) validateAlgorithmEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, alpineV001SchemaPackageHashTypeAlgorithmPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *AlpineV001SchemaPackageHash) validateAlgorithm(formats strfmt.Registry) error { + + if err := validate.Required("package"+"."+"hash"+"."+"algorithm", "body", m.Algorithm); err != nil { + return err + } + + // value enum + if err := m.validateAlgorithmEnum("package"+"."+"hash"+"."+"algorithm", "body", *m.Algorithm); err != nil { + return err + } + + return nil +} + +func (m *AlpineV001SchemaPackageHash) validateValue(formats strfmt.Registry) error { + + if err := validate.Required("package"+"."+"hash"+"."+"value", "body", m.Value); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this alpine v001 schema package hash based on context it is used +func (m *AlpineV001SchemaPackageHash) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *AlpineV001SchemaPackageHash) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *AlpineV001SchemaPackageHash) UnmarshalBinary(b []byte) error { + var res AlpineV001SchemaPackageHash + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// AlpineV001SchemaPublicKey The public key that can verify the package signature +// +// swagger:model AlpineV001SchemaPublicKey +type AlpineV001SchemaPublicKey struct { + + // Specifies the content of the public key inline within the document + // Format: byte + Content strfmt.Base64 `json:"content,omitempty"` + + // Specifies the location of the public key + // Format: uri + URL strfmt.URI `json:"url,omitempty"` +} + +// Validate validates this alpine v001 schema public key +func (m *AlpineV001SchemaPublicKey) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateURL(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AlpineV001SchemaPublicKey) validateURL(formats strfmt.Registry) error { + if swag.IsZero(m.URL) { // not required + return nil + } + + if err := validate.FormatOf("publicKey"+"."+"url", "body", "uri", m.URL.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this alpine v001 schema public key based on context it is used +func (m *AlpineV001SchemaPublicKey) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *AlpineV001SchemaPublicKey) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *AlpineV001SchemaPublicKey) UnmarshalBinary(b []byte) error { + var res AlpineV001SchemaPublicKey + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/generated/models/proposed_entry.go b/pkg/generated/models/proposed_entry.go index 70812c512..94a5d6fab 100644 --- a/pkg/generated/models/proposed_entry.go +++ b/pkg/generated/models/proposed_entry.go @@ -115,6 +115,12 @@ func unmarshalProposedEntry(data []byte, consumer runtime.Consumer) (ProposedEnt return nil, err } return &result, nil + case "alpine": + var result Alpine + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "intoto": var result Intoto if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/generated/models/rpm.go b/pkg/generated/models/rpm.go index d4fab013d..8b1f10c77 100644 --- a/pkg/generated/models/rpm.go +++ b/pkg/generated/models/rpm.go @@ -32,7 +32,7 @@ import ( "github.com/go-openapi/validate" ) -// Rpm RPM object +// Rpm RPM package // // swagger:model rpm type Rpm struct { diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index 1c5c29b73..d61429117 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -610,6 +610,32 @@ func init() { } } }, + "alpine": { + "description": "Alpine package", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ProposedEntry" + }, + { + "required": [ + "apiVersion", + "spec" + ], + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "spec": { + "type": "object", + "$ref": "pkg/types/alpine/alpine_schema.json" + } + }, + "additionalProperties": false + } + ] + }, "intoto": { "description": "Intoto object", "type": "object", @@ -715,7 +741,7 @@ func init() { ] }, "rpm": { - "description": "RPM object", + "description": "RPM package", "type": "object", "allOf": [ { @@ -1195,6 +1221,111 @@ func init() { } }, "definitions": { + "AlpineV001SchemaPackage": { + "description": "Information about the package associated with the entry", + "type": "object", + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ], + "properties": { + "content": { + "description": "Specifies the package inline within the document", + "type": "string", + "format": "byte" + }, + "hash": { + "description": "Specifies the hash algorithm and value for the package", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the package", + "type": "string" + } + } + }, + "pkginfo": { + "description": "Values of the .PKGINFO key / value pairs", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "url": { + "description": "Specifies the location of the package; if this is specified, a hash value must also be provided", + "type": "string", + "format": "uri" + } + } + }, + "AlpineV001SchemaPackageHash": { + "description": "Specifies the hash algorithm and value for the package", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the package", + "type": "string" + } + } + }, + "AlpineV001SchemaPublicKey": { + "description": "The public key that can verify the package signature", + "type": "object", + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ], + "properties": { + "content": { + "description": "Specifies the content of the public key inline within the document", + "type": "string", + "format": "byte" + }, + "url": { + "description": "Specifies the location of the public key", + "type": "string", + "format": "uri" + } + } + }, "ConsistencyProof": { "type": "object", "required": [ @@ -1899,6 +2030,145 @@ func init() { } } }, + "alpine": { + "description": "Alpine package", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ProposedEntry" + }, + { + "required": [ + "apiVersion", + "spec" + ], + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "spec": { + "$ref": "#/definitions/alpineSchema" + } + }, + "additionalProperties": false + } + ] + }, + "alpineSchema": { + "description": "Schema for Alpine package objects", + "type": "object", + "title": "Alpine Package Schema", + "oneOf": [ + { + "$ref": "#/definitions/alpineV001Schema" + } + ], + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://rekor.sigstore.dev/types/alpine/alpine_schema.json" + }, + "alpineV001Schema": { + "description": "Schema for Alpine Package entries", + "type": "object", + "title": "Alpine v0.0.1 Schema", + "required": [ + "publicKey", + "package" + ], + "properties": { + "extraData": { + "description": "Arbitrary content to be included in the verifiable entry in the transparency log", + "type": "object", + "additionalProperties": true + }, + "package": { + "description": "Information about the package associated with the entry", + "type": "object", + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ], + "properties": { + "content": { + "description": "Specifies the package inline within the document", + "type": "string", + "format": "byte" + }, + "hash": { + "description": "Specifies the hash algorithm and value for the package", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the package", + "type": "string" + } + } + }, + "pkginfo": { + "description": "Values of the .PKGINFO key / value pairs", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "url": { + "description": "Specifies the location of the package; if this is specified, a hash value must also be provided", + "type": "string", + "format": "uri" + } + } + }, + "publicKey": { + "description": "The public key that can verify the package signature", + "type": "object", + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ], + "properties": { + "content": { + "description": "Specifies the content of the public key inline within the document", + "type": "string", + "format": "byte" + }, + "url": { + "description": "Specifies the location of the public key", + "type": "string", + "format": "uri" + } + } + } + }, + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://rekor.sigstore.dev/types/alpine/alpine_v0_0_1_schema.json" + }, "intoto": { "description": "Intoto object", "type": "object", @@ -2364,7 +2634,7 @@ func init() { "$id": "http://rekor.sigstore.dev/types/timestamp/timestamp_v0_0_1_schema.json" }, "rpm": { - "description": "RPM object", + "description": "RPM package", "type": "object", "allOf": [ { diff --git a/pkg/pki/x509/x509.go b/pkg/pki/x509/x509.go index bf7bccda2..9fd315bfa 100644 --- a/pkg/pki/x509/x509.go +++ b/pkg/pki/x509/x509.go @@ -176,6 +176,10 @@ func (k PublicKey) CanonicalValue() ([]byte, error) { return buf.Bytes(), nil } +func (k PublicKey) CryptoPubKey() crypto.PublicKey { + return k.key +} + // EmailAddresses implements the pki.PublicKey interface func (k PublicKey) EmailAddresses() []string { var names []string diff --git a/pkg/types/alpine/alpine.go b/pkg/types/alpine/alpine.go new file mode 100644 index 000000000..e24925b68 --- /dev/null +++ b/pkg/types/alpine/alpine.go @@ -0,0 +1,73 @@ +// +// Copyright 2021 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 alpine + +import ( + "context" + + "github.com/pkg/errors" + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/types" +) + +const ( + KIND = "alpine" +) + +type BaseAlpineType struct { + types.RekorType +} + +func init() { + types.TypeMap.Store(KIND, New) +} + +func New() types.TypeImpl { + bat := BaseAlpineType{} + bat.Kind = KIND + bat.VersionMap = VersionMap + return &bat +} + +var VersionMap = types.NewSemVerEntryFactoryMap() + +func (bat *BaseAlpineType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImpl, error) { + if pe == nil { + return nil, errors.New("proposed entry cannot be nil") + } + + apk, ok := pe.(*models.Alpine) + if !ok { + return nil, errors.New("cannot unmarshal non-Alpine types") + } + + return bat.VersionedUnmarshal(apk, *apk.APIVersion) +} + +func (bat *BaseAlpineType) CreateProposedEntry(ctx context.Context, version string, props types.ArtifactProperties) (models.ProposedEntry, error) { + if version == "" { + version = bat.DefaultVersion() + } + ei, err := bat.VersionedUnmarshal(nil, version) + if err != nil { + return nil, errors.Wrap(err, "fetching Intoto version implementation") + } + return ei.CreateFromPFlags(ctx, props) +} + +func (bat BaseAlpineType) DefaultVersion() string { + return "0.0.1" +} diff --git a/pkg/types/alpine/alpine_schema.json b/pkg/types/alpine/alpine_schema.json new file mode 100644 index 000000000..b66657eea --- /dev/null +++ b/pkg/types/alpine/alpine_schema.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/alpine/alpine_schema.json", + "title": "Alpine Package Schema", + "description": "Schema for Alpine package objects", + "type": "object", + "oneOf": [ + { + "$ref": "v0.0.1/alpine_v0_0_1_schema.json" + } + ] +} diff --git a/pkg/types/alpine/alpine_test.go b/pkg/types/alpine/alpine_test.go new file mode 100644 index 000000000..6505dcb33 --- /dev/null +++ b/pkg/types/alpine/alpine_test.go @@ -0,0 +1,132 @@ +// +// Copyright 2021 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 alpine + +import ( + "context" + "errors" + "testing" + + "github.com/go-openapi/swag" + + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/types" +) + +type UnmarshalTester struct { + models.Alpine +} + +func (u UnmarshalTester) NewEntry() types.EntryImpl { + return &UnmarshalTester{} +} + +func (u UnmarshalTester) APIVersion() string { + return "2.0.1" +} + +func (u UnmarshalTester) IndexKeys() []string { + return []string{} +} + +func (u UnmarshalTester) Canonicalize(ctx context.Context) ([]byte, error) { + return nil, nil +} + +func (u UnmarshalTester) HasExternalEntities() bool { + return false +} + +func (u *UnmarshalTester) FetchExternalEntities(ctx context.Context) error { + return nil +} + +func (u UnmarshalTester) Unmarshal(pe models.ProposedEntry) error { + return nil +} + +func (u UnmarshalTester) Validate() error { + return nil +} + +func (u UnmarshalTester) Attestation() (string, []byte) { + return "", nil +} + +func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { + return nil, nil +} + +type UnmarshalFailsTester struct { + UnmarshalTester +} + +func (u UnmarshalFailsTester) NewEntry() types.EntryImpl { + return &UnmarshalFailsTester{} +} + +func (u UnmarshalFailsTester) Unmarshal(pe models.ProposedEntry) error { + return errors.New("error") +} + +func TestAlpineType(t *testing.T) { + // empty to start + if VersionMap.Count() != 0 { + t.Error("semver range was not blank at start of test") + } + + u := UnmarshalTester{} + // ensure semver range parser is working + invalidSemVerRange := "not a valid semver range" + err := VersionMap.SetEntryFactory(invalidSemVerRange, u.NewEntry) + if err == nil || VersionMap.Count() > 0 { + t.Error("invalid semver range was incorrectly added to SemVerToFacFnMap") + } + + // valid semver range can be parsed + err = VersionMap.SetEntryFactory(">= 1.2.3", u.NewEntry) + if err != nil || VersionMap.Count() != 1 { + t.Error("valid semver range was not added to SemVerToFacFnMap") + } + + u.Alpine.APIVersion = swag.String("2.0.1") + brt := New() + + // version requested matches implementation in map + if _, err := brt.UnmarshalEntry(&u.Alpine); err != nil { + t.Errorf("unexpected error in Unmarshal: %v", err) + } + + // version requested fails to match implementation in map + u.Alpine.APIVersion = swag.String("1.2.2") + if _, err := brt.UnmarshalEntry(&u.Alpine); err == nil { + t.Error("unexpected success in Unmarshal for non-matching version") + } + + // error in Unmarshal call is raised appropriately + u.Alpine.APIVersion = swag.String("2.2.0") + u2 := UnmarshalFailsTester{} + _ = VersionMap.SetEntryFactory(">= 1.2.3", u2.NewEntry) + if _, err := brt.UnmarshalEntry(&u.Alpine); err == nil { + t.Error("unexpected success in Unmarshal when error is thrown") + } + + // version requested fails to match implementation in map + u.Alpine.APIVersion = swag.String("not_a_version") + if _, err := brt.UnmarshalEntry(&u.Alpine); err == nil { + t.Error("unexpected success in Unmarshal for invalid version") + } +} diff --git a/pkg/types/alpine/apk.go b/pkg/types/alpine/apk.go new file mode 100644 index 000000000..2481693c0 --- /dev/null +++ b/pkg/types/alpine/apk.go @@ -0,0 +1,245 @@ +// +// Copyright 2021 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 alpine + +import ( + "archive/tar" + "bufio" + "bytes" + "compress/gzip" + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/sha1" // #nosec G505 + "crypto/sha256" + "encoding/hex" + "encoding/pem" + "fmt" + "hash" + "io" + "strings" + + "github.com/pkg/errors" + "gopkg.in/ini.v1" +) + +type Package struct { + Pkginfo map[string]string // KVP pairs + Signature []byte + Datahash []byte + controlSHA1Digest []byte +} + +type sha1Reader struct { + r *bufio.Reader + addToHash bool + hasher hash.Hash +} + +func newSHA1Reader(b *bufio.Reader) *sha1Reader { + // #nosec G401 + c := sha1Reader{ + r: b, + hasher: sha1.New(), + } + return &c +} + +func (s *sha1Reader) Read(p []byte) (int, error) { + n, err := s.r.Read(p) + if err == nil && n > 0 && s.addToHash { + s.hasher.Write(p) + } + return n, err +} + +func (s *sha1Reader) ReadByte() (byte, error) { + b, err := s.r.ReadByte() + if err == nil && s.addToHash { + s.hasher.Write([]byte{b}) + } + return b, err +} + +func (s sha1Reader) Sum() []byte { + return s.hasher.Sum(nil) +} + +func (s *sha1Reader) StartHashing() { + s.hasher.Reset() + s.addToHash = true +} + +func (s *sha1Reader) StopHashing() { + s.addToHash = false +} + +func (p *Package) Unmarshal(pkgReader io.Reader) error { + pkg := Package{} + // bufio.Reader is required if Multistream(false) is used + bufReader := bufio.NewReader(pkgReader) + sha1BufReader := newSHA1Reader(bufReader) + gzipReader, err := gzip.NewReader(sha1BufReader) + if err != nil { + return errors.Wrap(err, "create gzip reader") + } + defer func() { + _ = gzipReader.Close() + }() + + // APKs are concatenated gzip files so we want to know where the boundary is + gzipReader.Multistream(false) + + // GZIP headers/footers are left unmodified; Tar footers are removed on first two archives + // signature.tar.gz | control.tar.gz | data.tar.gz + sigBuf := bytes.Buffer{} + // #nosec G110 + if _, err := io.Copy(&sigBuf, gzipReader); err != nil { + return errors.Wrap(err, "reading signature.tar.gz") + } + + // the SHA1 sum used in the signature is over the entire file control.tar.gz so we need to + // intercept the buffered reading to compute the hash correctly + // + // we start sha1 hashing now since the Reset() call will begin reading control.tar.gz headers + sha1BufReader.StartHashing() + + // we reset the reader since we've found the end of signature.tar.gz + if err := gzipReader.Reset(sha1BufReader); err != nil && err != io.EOF { + return errors.Wrap(err, "resetting to control.tar.gz") + } + gzipReader.Multistream(false) + + controlTar := bytes.Buffer{} + // #nosec G110 + if _, err = io.Copy(&controlTar, gzipReader); err != nil { + return errors.Wrap(err, "reading control.tar.gz") + } + + // signature uses sha1 digest hardcoded in abuild-sign tool + pkg.controlSHA1Digest = sha1BufReader.Sum() + sha1BufReader.StopHashing() + + // the gzip reader is NOT reset again since that advances the underlying reader + // by reading the next GZIP header, which affects the datahash computation below + + sigReader := tar.NewReader(&sigBuf) + for { + header, err := sigReader.Next() + if err == io.EOF { + if pkg.Signature == nil { + return errors.New("no signature detected in alpine package") + } + break + } else if err != nil { + return errors.Wrap(err, "getting next entry in tar archive") + } + + if strings.HasPrefix(header.Name, ".SIGN") && pkg.Signature == nil { + sigBytes := make([]byte, header.Size) + if _, err = sigReader.Read(sigBytes); err != nil && err != io.EOF { + return errors.Wrap(err, "reading signature") + } + // we're not sure whether this is PEM encoded or not, so handle both cases + block, _ := pem.Decode(sigBytes) + if block == nil { + pkg.Signature = sigBytes + } else { + pkg.Signature = block.Bytes + } + } + } + + ctlReader := tar.NewReader(&controlTar) + for { + header, err := ctlReader.Next() + if err == io.EOF { + if pkg.Pkginfo == nil { + return errors.New(".PKGINFO file was not located") + } + break + } else if err != nil { + return errors.Wrap(err, "getting next entry in tar archive") + } + + if header.Name == ".PKGINFO" { + pkginfoContent := make([]byte, header.Size) + if _, err = ctlReader.Read(pkginfoContent); err != nil && err != io.EOF { + return errors.Wrap(err, "reading .PKGINFO") + } + + pkg.Pkginfo, err = parsePkginfo(pkginfoContent) + if err != nil { + return errors.Wrap(err, "parsing .PKGINFO") + } + pkg.Datahash, err = hex.DecodeString(pkg.Pkginfo["datahash"]) + if err != nil { + return errors.Wrap(err, "parsing datahash") + } + } + } + + // at this point, bufReader should point to first byte of data.tar.gz + // datahash value from .PKGINFO is sha256 sum of data.tar.gz + sha256 := sha256.New() + if _, err := io.Copy(sha256, bufReader); err != nil { + return errors.Wrap(err, "computing SHA256 sum of data.tar.gz") + } + computedSum := sha256.Sum(nil) + + if !bytes.Equal(computedSum, pkg.Datahash) { + return fmt.Errorf("checksum for data.tar.gz (%v) does not match value from .PKGINFO (%v)", hex.EncodeToString(computedSum), hex.EncodeToString(pkg.Datahash)) + } + *p = pkg + return nil +} + +// VerifySignature verifies the signature of the alpine package using the provided +// public key. It returns an error if verification fails, or nil if it is successful. +func (p Package) VerifySignature(pub crypto.PublicKey) error { + if p.Signature == nil { + return errors.New("no signature in alpine package object") + } + if p.controlSHA1Digest == nil { + return errors.New("no digest value for data.tar.gz known") + } + + switch pk := pub.(type) { + case *rsa.PublicKey: + return rsa.VerifyPKCS1v15(pk, crypto.SHA1, p.controlSHA1Digest, p.Signature) + case *ecdsa.PublicKey: + if !ecdsa.VerifyASN1(pk, p.controlSHA1Digest, p.Signature) { + return errors.New("failed to verify ECDSA signature") + } + default: + return errors.New("unknown key algorithm") + } + return nil +} + +// parsePkginfo parses the .PKGINFO file which is in a +// key[space]=[space]value\n +// format. it returns a map[string]string of the key/value pairs, or +// an error if parsing could not be completed successfully. +func parsePkginfo(input []byte) (map[string]string, error) { + cfg, err := ini.Load(input) + if err != nil { + return nil, err + } + + // .PKGINFO does not use sections, so using "" grabs the default values + return cfg.Section("").KeysHash(), nil +} diff --git a/pkg/types/alpine/apk_test.go b/pkg/types/alpine/apk_test.go new file mode 100644 index 000000000..14fcef7af --- /dev/null +++ b/pkg/types/alpine/apk_test.go @@ -0,0 +1,50 @@ +// +// Copyright 2021 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 alpine + +import ( + "os" + "testing" + + "github.com/sigstore/rekor/pkg/pki/x509" +) + +func TestAlpinePackage(t *testing.T) { + inputArchive, err := os.Open("../../../tests/test_alpine.apk") + if err != nil { + t.Fatalf("could not open archive %v", err) + } + + p := Package{} + err = p.Unmarshal(inputArchive) + if err != nil { + t.Fatalf("unmarshal error: %v", err) + } + + pubKey, err := os.Open("../../../tests/test_alpine.pub") + if err != nil { + t.Fatalf("could not open archive %v", err) + } + + pub, err := x509.NewPublicKey(pubKey) + if err != nil { + t.Fatalf("failed to parse public key: %v", err) + } + + if err = p.VerifySignature(pub.(*x509.PublicKey).CryptoPubKey()); err != nil { + t.Fatalf("signature verification failed: %v", err) + } +} diff --git a/pkg/types/alpine/v0.0.1/alpine_v0_0_1_schema.json b/pkg/types/alpine/v0.0.1/alpine_v0_0_1_schema.json new file mode 100644 index 000000000..7cbcf530f --- /dev/null +++ b/pkg/types/alpine/v0.0.1/alpine_v0_0_1_schema.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/alpine/alpine_v0_0_1_schema.json", + "title": "Alpine v0.0.1 Schema", + "description": "Schema for Alpine Package entries", + "type": "object", + "properties": { + "publicKey" : { + "description": "The public key that can verify the package signature", + "type": "object", + "properties": { + "url": { + "description": "Specifies the location of the public key", + "type": "string", + "format": "uri" + }, + "content": { + "description": "Specifies the content of the public key inline within the document", + "type": "string", + "format": "byte" + } + }, + "oneOf": [ + { + "required": [ "url" ] + }, + { + "required": [ "content" ] + } + ] + }, + "package": { + "description": "Information about the package associated with the entry", + "type": "object", + "properties": { + "pkginfo": { + "description": "Values of the .PKGINFO key / value pairs", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "hash": { + "description": "Specifies the hash algorithm and value for the package", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ "sha256" ] + }, + "value": { + "description": "The hash value for the package", + "type": "string" + } + }, + "required": [ "algorithm", "value" ] + }, + "url": { + "description": "Specifies the location of the package; if this is specified, a hash value must also be provided", + "type": "string", + "format": "uri" + }, + "content": { + "description": "Specifies the package inline within the document", + "type": "string", + "format": "byte" + } + }, + "oneOf": [ + { + "required": [ "url" ] + }, + { + "required": [ "content" ] + } + ] + }, + "extraData": { + "description": "Arbitrary content to be included in the verifiable entry in the transparency log", + "type": "object", + "additionalProperties": true + } + }, + "required": [ "publicKey", "package" ] +} diff --git a/pkg/types/alpine/v0.0.1/entry.go b/pkg/types/alpine/v0.0.1/entry.go new file mode 100644 index 000000000..a61b8d03d --- /dev/null +++ b/pkg/types/alpine/v0.0.1/entry.go @@ -0,0 +1,405 @@ +// +// Copyright 2021 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 alpine + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/asaskevich/govalidator" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "golang.org/x/sync/errgroup" + + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/log" + "github.com/sigstore/rekor/pkg/pki" + pkifactory "github.com/sigstore/rekor/pkg/pki/factory" + "github.com/sigstore/rekor/pkg/pki/x509" + "github.com/sigstore/rekor/pkg/types" + "github.com/sigstore/rekor/pkg/types/alpine" + "github.com/sigstore/rekor/pkg/util" +) + +const ( + APIVERSION = "0.0.1" +) + +func init() { + if err := alpine.VersionMap.SetEntryFactory(APIVERSION, NewEntry); err != nil { + log.Logger.Panic(err) + } +} + +type V001Entry struct { + AlpineModel models.AlpineV001Schema + fetchedExternalEntities bool + keyObj pki.PublicKey + apkObj *alpine.Package +} + +func (v V001Entry) APIVersion() string { + return APIVERSION +} + +func NewEntry() types.EntryImpl { + return &V001Entry{} +} + +func (v V001Entry) IndexKeys() []string { + var result []string + + if v.HasExternalEntities() { + if err := v.FetchExternalEntities(context.Background()); err != nil { + log.Logger.Error(err) + return result + } + } + + key, err := v.keyObj.CanonicalValue() + if err != nil { + log.Logger.Error(err) + } else { + keyHash := sha256.Sum256(key) + result = append(result, strings.ToLower(hex.EncodeToString(keyHash[:]))) + } + + result = append(result, v.keyObj.EmailAddresses()...) + + if v.AlpineModel.Package.Hash != nil { + hashKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.AlpineModel.Package.Hash.Algorithm, *v.AlpineModel.Package.Hash.Value)) + result = append(result, hashKey) + } + + return result +} + +func (v *V001Entry) Unmarshal(pe models.ProposedEntry) error { + apk, ok := pe.(*models.Alpine) + if !ok { + return errors.New("cannot unmarshal non Alpine v0.0.1 type") + } + + if err := types.DecodeEntry(apk.Spec, &v.AlpineModel); err != nil { + return err + } + + // field validation + if err := v.AlpineModel.Validate(strfmt.Default); err != nil { + return err + } + return nil + +} + +func (v V001Entry) HasExternalEntities() bool { + if v.fetchedExternalEntities { + return false + } + + if v.AlpineModel.Package != nil && v.AlpineModel.Package.URL.String() != "" { + return true + } + if v.AlpineModel.PublicKey != nil && v.AlpineModel.PublicKey.URL.String() != "" { + return true + } + return false +} + +func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { + if v.fetchedExternalEntities { + return nil + } + + if err := v.Validate(); err != nil { + return err + } + + g, ctx := errgroup.WithContext(ctx) + + hashR, hashW := io.Pipe() + apkR, apkW := io.Pipe() + defer hashR.Close() + defer apkR.Close() + + closePipesOnError := func(err error) error { + pipeReaders := []*io.PipeReader{hashR, apkR} + pipeWriters := []*io.PipeWriter{hashW, apkW} + for idx := range pipeReaders { + if e := pipeReaders[idx].CloseWithError(err); e != nil { + log.Logger.Error(fmt.Errorf("error closing pipe: %w", e)) + } + if e := pipeWriters[idx].CloseWithError(err); e != nil { + log.Logger.Error(fmt.Errorf("error closing pipe: %w", e)) + } + } + return err + } + + oldSHA := "" + if v.AlpineModel.Package.Hash != nil && v.AlpineModel.Package.Hash.Value != nil { + oldSHA = swag.StringValue(v.AlpineModel.Package.Hash.Value) + } + artifactFactory, _ := pkifactory.NewArtifactFactory(string(pkifactory.X509)) + + g.Go(func() error { + defer hashW.Close() + defer apkW.Close() + + dataReadCloser, err := util.FileOrURLReadCloser(ctx, v.AlpineModel.Package.URL.String(), v.AlpineModel.Package.Content) + if err != nil { + return closePipesOnError(err) + } + defer dataReadCloser.Close() + + /* #nosec G110 */ + if _, err := io.Copy(io.MultiWriter(hashW, apkW), dataReadCloser); err != nil { + return closePipesOnError(err) + } + return nil + }) + + hashResult := make(chan string) + + g.Go(func() error { + defer close(hashResult) + hasher := sha256.New() + + if _, err := io.Copy(hasher, hashR); err != nil { + return closePipesOnError(err) + } + + computedSHA := hex.EncodeToString(hasher.Sum(nil)) + if oldSHA != "" && computedSHA != oldSHA { + return closePipesOnError(fmt.Errorf("SHA mismatch: %s != %s", computedSHA, oldSHA)) + } + + select { + case <-ctx.Done(): + return ctx.Err() + case hashResult <- computedSHA: + return nil + } + }) + + keyResult := make(chan *x509.PublicKey) + + g.Go(func() error { + defer close(keyResult) + keyReadCloser, err := util.FileOrURLReadCloser(ctx, v.AlpineModel.PublicKey.URL.String(), + v.AlpineModel.PublicKey.Content) + if err != nil { + return closePipesOnError(err) + } + defer keyReadCloser.Close() + + v.keyObj, err = artifactFactory.NewPublicKey(keyReadCloser) + if err != nil { + return closePipesOnError(err) + } + + select { + case <-ctx.Done(): + return ctx.Err() + case keyResult <- v.keyObj.(*x509.PublicKey): + return nil + } + }) + + g.Go(func() error { + apk := alpine.Package{} + if err := apk.Unmarshal(apkR); err != nil { + return closePipesOnError(err) + } + + key := <-keyResult + if key == nil { + return closePipesOnError(errors.New("error processing public key")) + } + + if err := apk.VerifySignature(key.CryptoPubKey()); err != nil { + return closePipesOnError(err) + } + + v.apkObj = &apk + + select { + case <-ctx.Done(): + return ctx.Err() + default: + return nil + } + }) + + computedSHA := <-hashResult + + if err := g.Wait(); err != nil { + return err + } + + // if we get here, all goroutines succeeded without error + if oldSHA == "" { + v.AlpineModel.Package.Hash = &models.AlpineV001SchemaPackageHash{} + v.AlpineModel.Package.Hash.Algorithm = swag.String(models.AlpineV001SchemaPackageHashAlgorithmSha256) + v.AlpineModel.Package.Hash.Value = swag.String(computedSHA) + } + + v.fetchedExternalEntities = true + return nil +} + +func (v *V001Entry) Canonicalize(ctx context.Context) ([]byte, error) { + if err := v.FetchExternalEntities(ctx); err != nil { + return nil, err + } + if v.keyObj == nil { + return nil, errors.New("key object not initialized before canonicalization") + } + + canonicalEntry := models.AlpineV001Schema{} + canonicalEntry.ExtraData = v.AlpineModel.ExtraData + + var err error + + // need to canonicalize key content + canonicalEntry.PublicKey = &models.AlpineV001SchemaPublicKey{} + canonicalEntry.PublicKey.Content, err = v.keyObj.CanonicalValue() + if err != nil { + return nil, err + } + + canonicalEntry.Package = &models.AlpineV001SchemaPackage{} + canonicalEntry.Package.Hash = &models.AlpineV001SchemaPackageHash{} + canonicalEntry.Package.Hash.Algorithm = v.AlpineModel.Package.Hash.Algorithm + canonicalEntry.Package.Hash.Value = v.AlpineModel.Package.Hash.Value + // data content is not set deliberately + + // set .PKGINFO headers + canonicalEntry.Package.Pkginfo = v.apkObj.Pkginfo + + // ExtraData is copied through unfiltered + canonicalEntry.ExtraData = v.AlpineModel.ExtraData + + // wrap in valid object with kind and apiVersion set + apk := models.Alpine{} + apk.APIVersion = swag.String(APIVERSION) + apk.Spec = &canonicalEntry + + return json.Marshal(&apk) +} + +// Validate performs cross-field validation for fields in object +func (v V001Entry) Validate() error { + key := v.AlpineModel.PublicKey + if key == nil { + return errors.New("missing public key") + } + if len(key.Content) == 0 && key.URL.String() == "" { + return errors.New("one of 'content' or 'url' must be specified for publicKey") + } + + pkg := v.AlpineModel.Package + if pkg == nil { + return errors.New("missing package") + } + + if len(pkg.Content) == 0 && pkg.URL.String() == "" { + return errors.New("one of 'content' or 'url' must be specified for package") + } + + hash := pkg.Hash + if hash != nil { + if !govalidator.IsHash(swag.StringValue(hash.Value), swag.StringValue(hash.Algorithm)) { + return errors.New("invalid value for hash") + } + } + + return nil +} + +func (v V001Entry) Attestation() (string, []byte) { + return "", nil +} + +func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { + returnVal := models.Alpine{} + re := V001Entry{} + + // we will need artifact, public-key, signature + re.AlpineModel = models.AlpineV001Schema{} + re.AlpineModel.Package = &models.AlpineV001SchemaPackage{} + + var err error + artifactBytes := props.ArtifactBytes + if artifactBytes == nil { + if props.ArtifactPath.IsAbs() { + re.AlpineModel.Package.URL = strfmt.URI(props.ArtifactPath.String()) + if props.ArtifactHash != "" { + re.AlpineModel.Package.Hash = &models.AlpineV001SchemaPackageHash{ + Algorithm: swag.String(models.AlpineV001SchemaPackageHashAlgorithmSha256), + Value: swag.String(props.ArtifactHash), + } + } + } else { + artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) + if err != nil { + return nil, fmt.Errorf("error reading artifact file: %w", err) + } + re.AlpineModel.Package.Content = strfmt.Base64(artifactBytes) + } + } else { + re.AlpineModel.Package.Content = strfmt.Base64(artifactBytes) + } + + re.AlpineModel.PublicKey = &models.AlpineV001SchemaPublicKey{} + publicKeyBytes := props.PublicKeyBytes + if publicKeyBytes == nil { + if props.PublicKeyPath.IsAbs() { + re.AlpineModel.PublicKey.URL = strfmt.URI(props.PublicKeyPath.String()) + } else { + publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + re.AlpineModel.PublicKey.Content = strfmt.Base64(publicKeyBytes) + } + } else { + re.AlpineModel.PublicKey.Content = strfmt.Base64(publicKeyBytes) + } + + if err := re.Validate(); err != nil { + return nil, err + } + + if re.HasExternalEntities() { + if err := re.FetchExternalEntities(ctx); err != nil { + return nil, fmt.Errorf("error retrieving external entities: %v", err) + } + } + + returnVal.APIVersion = swag.String(re.APIVersion()) + returnVal.Spec = re.AlpineModel + + return &returnVal, nil +} diff --git a/pkg/types/alpine/v0.0.1/entry_test.go b/pkg/types/alpine/v0.0.1/entry_test.go new file mode 100644 index 000000000..d7998a190 --- /dev/null +++ b/pkg/types/alpine/v0.0.1/entry_test.go @@ -0,0 +1,394 @@ +// +// Copyright 2021 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 alpine + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "io/ioutil" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "go.uber.org/goleak" + + "github.com/sigstore/rekor/pkg/generated/models" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + +func TestNewEntryReturnType(t *testing.T) { + entry := NewEntry() + if reflect.TypeOf(entry) != reflect.ValueOf(&V001Entry{}).Type() { + t.Errorf("invalid type returned from NewEntry: %T", entry) + } +} + +func TestCrossFieldValidation(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + hasExtEntities bool + expectUnmarshalSuccess bool + expectCanonicalizeSuccess bool + } + + keyBytes, _ := ioutil.ReadFile("../../../../tests/test_alpine.pub") + dataBytes, _ := ioutil.ReadFile("../../../../tests/test_alpine.apk") + + h := sha256.Sum256(dataBytes) + dataSHA := hex.EncodeToString(h[:]) + + testServer := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + file := &keyBytes + var err error + + switch r.URL.Path { + case "/key": + file = &keyBytes + case "/data": + file = &dataBytes + default: + err = errors.New("unknown URL") + } + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + w.WriteHeader(http.StatusOK) + _, _ = w.Write(*file) + })) + defer testServer.Close() + + testCases := []TestCase{ + { + caseDesc: "empty obj", + entry: V001Entry{}, + expectUnmarshalSuccess: false, + }, + { + caseDesc: "public key without url or content", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{}, + }, + }, + expectUnmarshalSuccess: false, + }, + { + caseDesc: "public key without package", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/key"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: false, + }, + { + caseDesc: "public key with empty package", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/key"), + }, + Package: &models.AlpineV001SchemaPackage{}, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: false, + }, + { + caseDesc: "public key with data & url but no hash", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/key"), + }, + Package: &models.AlpineV001SchemaPackage{ + URL: strfmt.URI(testServer.URL + "/data"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: true, + }, + { + caseDesc: "public key with data & url and empty hash", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/key"), + }, + Package: &models.AlpineV001SchemaPackage{ + Hash: &models.AlpineV001SchemaPackageHash{}, + URL: strfmt.URI(testServer.URL + "/data"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: false, + }, + { + caseDesc: "public key with data & url and hash missing value", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/key"), + }, + Package: &models.AlpineV001SchemaPackage{ + Hash: &models.AlpineV001SchemaPackageHash{ + Algorithm: swag.String(models.AlpineV001SchemaPackageHashAlgorithmSha256), + }, + URL: strfmt.URI(testServer.URL + "/data"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: false, + }, + { + caseDesc: "public key with data & url with 404 error on key", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/404"), + }, + Package: &models.AlpineV001SchemaPackage{ + Hash: &models.AlpineV001SchemaPackageHash{ + Algorithm: swag.String(models.AlpineV001SchemaPackageHashAlgorithmSha256), + Value: swag.String(dataSHA), + }, + URL: strfmt.URI(testServer.URL + "/data"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: false, + }, + { + caseDesc: "public key with data & url with 404 error on data", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/key"), + }, + Package: &models.AlpineV001SchemaPackage{ + Hash: &models.AlpineV001SchemaPackageHash{ + Algorithm: swag.String(models.AlpineV001SchemaPackageHashAlgorithmSha256), + Value: swag.String(dataSHA), + }, + URL: strfmt.URI(testServer.URL + "/404"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: false, + }, + { + caseDesc: "public key with invalid key content & with data with content", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + Content: strfmt.Base64(dataBytes), + }, + Package: &models.AlpineV001SchemaPackage{ + Content: strfmt.Base64(dataBytes), + }, + }, + }, + hasExtEntities: false, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: false, + }, + { + caseDesc: "public key with data & url and incorrect hash value", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/key"), + }, + Package: &models.AlpineV001SchemaPackage{ + Hash: &models.AlpineV001SchemaPackageHash{ + Algorithm: swag.String(models.AlpineV001SchemaPackageHashAlgorithmSha256), + Value: swag.String("3030303030303030303030303030303030303030303030303030303030303030"), + }, + URL: strfmt.URI(testServer.URL + "/data"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: false, + }, + { + caseDesc: "public key with data & url and complete hash value", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/key"), + }, + Package: &models.AlpineV001SchemaPackage{ + Hash: &models.AlpineV001SchemaPackageHash{ + Algorithm: swag.String(models.AlpineV001SchemaPackageHashAlgorithmSha256), + Value: swag.String(dataSHA), + }, + URL: strfmt.URI(testServer.URL + "/data"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: true, + }, + { + caseDesc: "public key with url key & with data with url and complete hash value", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/key"), + }, + Package: &models.AlpineV001SchemaPackage{ + Hash: &models.AlpineV001SchemaPackageHash{ + Algorithm: swag.String(models.AlpineV001SchemaPackageHashAlgorithmSha256), + Value: swag.String(dataSHA), + }, + URL: strfmt.URI(testServer.URL + "/data"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: true, + }, + { + caseDesc: "public key with key content & with data with url and complete hash value", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + Content: strfmt.Base64(keyBytes), + }, + Package: &models.AlpineV001SchemaPackage{ + Hash: &models.AlpineV001SchemaPackageHash{ + Algorithm: swag.String(models.AlpineV001SchemaPackageHashAlgorithmSha256), + Value: swag.String(dataSHA), + }, + URL: strfmt.URI(testServer.URL + "/data"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: true, + }, + { + caseDesc: "public key with key content & with data with url and complete hash value", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + Content: strfmt.Base64(keyBytes), + }, + Package: &models.AlpineV001SchemaPackage{ + Hash: &models.AlpineV001SchemaPackageHash{ + Algorithm: swag.String(models.AlpineV001SchemaPackageHashAlgorithmSha256), + Value: swag.String(dataSHA), + }, + URL: strfmt.URI(testServer.URL + "/data"), + }, + }, + }, + hasExtEntities: true, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: true, + }, + { + caseDesc: "public key with key content & with data with content", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + Content: strfmt.Base64(keyBytes), + }, + Package: &models.AlpineV001SchemaPackage{ + Content: strfmt.Base64(dataBytes), + }, + }, + }, + hasExtEntities: false, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: true, + }, + { + caseDesc: "valid obj with extradata", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + PublicKey: &models.AlpineV001SchemaPublicKey{ + Content: strfmt.Base64(keyBytes), + }, + Package: &models.AlpineV001SchemaPackage{ + Content: strfmt.Base64(dataBytes), + }, + ExtraData: []byte("{\"something\": \"here\""), + }, + }, + hasExtEntities: false, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: true, + }, + } + + for _, tc := range testCases { + if err := tc.entry.Validate(); (err == nil) != tc.expectUnmarshalSuccess { + t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) + } + + v := &V001Entry{} + r := models.Alpine{ + APIVersion: swag.String(tc.entry.APIVersion()), + Spec: tc.entry.AlpineModel, + } + + unmarshalAndValidate := func() error { + if err := v.Unmarshal(&r); err != nil { + return err + } + return v.Validate() + } + if err := unmarshalAndValidate(); (err == nil) != tc.expectUnmarshalSuccess { + t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) + } + + if tc.entry.HasExternalEntities() != tc.hasExtEntities { + t.Errorf("unexpected result from HasExternalEntities for '%v'", tc.caseDesc) + } + + if _, err := tc.entry.Canonicalize(context.TODO()); (err == nil) != tc.expectCanonicalizeSuccess { + t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) + } + } +} diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index bd59dedb3..08fd5eae2 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -328,12 +328,7 @@ func (v *V001Entry) Canonicalize(ctx context.Context) ([]byte, error) { rpm.APIVersion = swag.String(APIVERSION) rpm.Spec = &canonicalEntry - bytes, err := json.Marshal(&rpm) - if err != nil { - return nil, err - } - - return bytes, nil + return json.Marshal(&rpm) } // Validate performs cross-field validation for fields in object diff --git a/tests/alpine.json b/tests/alpine.json new file mode 100644 index 000000000..5d09985c3 --- /dev/null +++ b/tests/alpine.json @@ -0,0 +1 @@ +{"kind":"alpine","apiVersion":"0.0.1","spec":{"package":{"content":"H4sIAAAAAAACA9ML9nT30wsKdtRLzCnIzEvVTUktS81xyMksLimGCuVk5pVW6OUXpeuaJJolGliYGOgVFSfqFZQmMRAHDIDAzMQETAMBOm1gAmQbGpuZGBqamRoYAdUZGRkZGjMoGDDQAZQWlyQWKSgwFOXnl+BTR0h+iAKdHmeWA1fqndIatpxf7SoutPN562YOt6ezMvPdFnu+ietbYO70aUFNfGVK/tm0qfNYlM4mT1n7JTxteteNd6fdbyx5tJDN88WW/azSGpPyXpU3LUvc8LfltGCuw2vzud6sM5RfSWQuK4u7tCTvhJyCzc7LDZc2yz9UdJWakf+WT0r6TNKPzKll+jLib4/KsPi/W58ZzxX/cVPeffNXO8zO+7fumx5lGyXds736+sH9HzcIn/8jmr4hMbnGSlgvhnsSwy+lqzKK34S2SDXN8YraHnZOOvub6/4vb0xjlp1a4BpZe/zs1v0Pb+iU2R+IUu1a9+3ctdI6hpRA67j78lwr5FoPahwOeir5pTfM+TbDCAcAaiYgQwAEAAAfiwgAAAAAAAID7VLBitswEM1ZXyHYc7yWbMty2C67LXQpC+1SWnosE2mSCMuykeWQ7Nd37JQWemhPLSz0gRjrzfNIb0bZ0+PDu/dvP6z+InKCKsslEn6NQshiJQpVCqGqXJJO5KoSK56v/gGmMUHkfBX7Pv1O96f8C8UVf8CAERJavj1z2E7OW15kMsvXUbArPo0u7PkOWpw7wI8YR9cHLjIpKfuFfrsfqH+Ci2Yj5aaq+edPb7jMZc6Gdh+gQ/6KwwDmgHJtkp9ZKkKkzMqsLNZxEVocDXH3i5CbPqTYez6a6IbEpugpd0hpGDfX13O02aVk1sf9NVsubckDqUSlq1rppq4ZKVrYL2e9nhX9M33fgB9cwLXFI/o778Y0ZhfKuzCd5oK3bHTPSy1V6JJBNAfanLT6qkrWR7d34acnZvquc4kIq/Km2tXWzM+40lvIC2llrSShtKZEK6HWxrAOXEi0lps9AkSP/KNLRwgY+E27EFn8TtyRL2in0LZTtnO3zDuDYcQfvVrTpJjFAYMlzp/DicYCU+o7SM6A92duMaGhAW/YEPujo1aT0nR2c7EwD2U+5ADj7LOqEXFb2HrX6EaX2ta5FKWud6gLQf0AUZrKWGsLKBqorKilNEpXIJWosFBs9R8vB98A9nUl/AAGAAAfiwgAAAAAAAID7VjbbhvJEWUew6c87AfU0gq0Tngb3mWBiBlLWhFLSQaHWstIAm1z2OQ0NDM96e4RSUT5gvxOPmDzmh/Ka6p6htSN8sYbW4gRNmDPraq6qk7VqabKlbdscczZhCtddlr1Vq2SaJX7pKuKq9Vo2Cuuh9eqU6/lnHqr4TitZrXm5KqO06w2crDIPcNKtGEKXcn9f656FUIjQt51mp1mu9XZa7fLNafWaXTa9XYev7JHXxstp1NrtR366j2tm9uuL2Bhs1c+9x7U4+1m88n+p/sH/d9qYf83n7P/lZTmQ3I/9f1hcF8Q/o8mgB6L6Fn5/yH+tTp+3vL/M6za3ib+J4Z36j9B/6jqPam6ZdYvp/+p3T/nEPh4/q+1nPaW/58V/0dDgMXM87lngs/P/+32Cv9Gq11H/Bu1Zn3L/890/v+5FN/qQO/td6XR2dnALVOpXOkkLLvHPac7RgCdRqNaGzc9p+pV2y3P6YyrThXvPVZrNPZanYY33f5I+N/p/0/X7h/L/0j6rfv9jz8ia8j/1JNYYbXXua9yf//1V69/+8df/Xj9j7/9IvfjP3f6/uBfW/7/79eLryuEvvbzL/IvYCA8Hmk+ASPB+Bx6tijAlVMzZ4rDkUyiCfKFjOCbnnv0EvCRK5ARB6kglIqjEU9GRolxYvBVkBoENlOchzwyugzgcm6tn56N+m8OYSoCDhOhUyXcfC4MuoMiQsNcqiuYoiU2mQjamAUgInwRpm4oPmNqIqIZbhsvlZj5BuQ8wjHmi7iMVkYUhnu08kSnZu2eGOR7mWQx3Ak3y0IRvkcztEmtXEVL35BIIftYeLkPS1QO2RIiaSDR/I5lvvB4bNBR9CqMA8Eij9+Gtd4Bc/E+syHHhqE4s2GAnN4VQ4a26NDyjYlfVSrz+bycdmxZqlllFVxlgAk9dQ9L1mNUOY8CrjVm6c+JUJja8RJYjA55bIxuBmxOuFlwLObowVxhmqNZEXQGOlq5C85ttlbeYdB3BTBfLIJCz4W+W4Df99y+W0Qb7/qj47PzEbzrDYe901H/0IWzIbw5Oz3oj/pnp/h0BL3T9/Bd//SgCBxzhdvwRazIf3RSUB75hCBd1c/KASoPetYx98RUeBhXNEvYjMNMXnMVUXXEXIVCE5oa3ZuglUCEwtgi0o+DKlP2UCirCFvSMgDtKYG4TrgWsyhNGQsCOaeIOdNLgjtE82gcW0JEhqsp8yiFKJkZCcid1C7i/w7TbXhEwJww5YEb8PBKFsHZ22tXqp1KrY7KWRnzhTBoBXdHQE2iyAME6JUtjYuLi7QAJ9IjSCIJgYxmGJYnlUJixzdzlGDGxml945qgXqtPk8hL80G97mMcQVoyVHSYsl9WoQQSM5m2ni1tTpDrxPMQpmkSBEsUc1AMLzW8JJpg4EpJhW/q+MbaQt0kmNjGGSNuyMBoBgUaTwnIOLYCzQ9agEmiKLsMKDR8hRqtzRqZwGad9n+sM1MIL8Z9R7mDygj1VMySLFF6GRm2WGWBesFHxMMkMAITiMmeJZYabdpn4ppHReyiYGmBslowVTK0j5cB0+YSbRDnCiqEWJJLWErDBNtuPcbB50FcsL2RgkCsibv33pwcdAs7TiHfG377Pd69LlifbjYscEe94Yia9Kj/7fmwR40K7uEbe92ogYZKGxZ8cG3SSKueIo6Z8al9kG1VhgpOLKaWRQzJCxJL/jJOC1dMIeJUi/g9fzwavT3o7lbWZxyrvWvDjYV3BUmMjbu81QAeXQslI0IDrpkSxJI6j0YNwgulKdzaQlGU0PvkY5QHKD/+lp8Kuxdb04JtP5mYOCG0IR1jVFCGL3DHbNxk7H88OhlA2q5oJFFBWhtEsMZy0i3VIPoHK04ixINltCiCL+f8msYzcSnFrCTWa6jtwKHBimr5wfvTi26BFKA0ScI4rQba4Hw4WOddc4WWdlFVTi6x0k2iIbvEWFxoqD+183AiqVvQgs+uORVp0SZoJUwOE01kj3MRBLa7rDd5LLfRuYv7dgvZnAukxwJfavOqU62kTpRS3dRPl5uU9VZoWUq+n2+sEoXkbEc/x9gXIkxC1I2ScEzZmWYnEJ6yu1Q6JXXMZUz06QukAMwdFUkZiRh3E5rOOTQp0T2b8Xsdr9ON6UwQUoE9tE/zEDnTB0ZY4Hy7phDt1KVxnFKD8RX9Hp9kuS/nzwf9k/7o8qR3cXnUHxy63UJiZxiUXChF8MPq6Riffih8yk7cyA0Ah6cHH8UNa7zWKPw8DKgh/wCFxc7DjBTg6y6+L8CfYN2XAI/EqC/zh8Ph2bBbXdsiNizAfXWw+ilPlvyC1fOwkmCHWBQLK29J/4bm0002AW5WI2F9U6LPL1NXLCVB6QrsfvZd6sjO7+zD/n5qUuvgBv+tzOP/JdcdpEa450t7HlhJZux3f+rrJM4mw63S24D6ADiepFM LVPZpgc/YtL0WIUbPRxg3FB16ltLaXNhhqjG7wwd61KhfCew2iqu1CQR6f1cmKeygE2eSRJHwc6aHeAG2PwKdqGSVcROBf6C1YFnmv30kPTX1TPe7a5M3vLOZrMrud/c888OyHsecs28fN7us2Nf5rd/xtiu7dqu7dqu7dquL2j9G8BVlPwAKAAA"},"publicKey":{"content":"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUExeUhKeFFnc0hRUkVjbFF1NE9oZQpxeFR4ZDF0SGNObnZuUVR1L1VyVGt5OHdXdmdYVCtqcHZlcm9lV1duem1zWWxESTkzZUxJMk9SYWt4YjNnQTJPClEwUnk0d3M4dmhheExRR0M3NHVRUjUrL3lZckx1VEt5ZEZ6dVBhUzFkSzE5cUpQWEI4R01kbUZPaWpuWFg0U0EKaml4dUhMZTFXVzdrWlZ0akw3bnVmdnBYa1dCR2pzZnJ2c2tkTkEvNU1meEFlQmJxUGdhcTBRTUVmeE1BbjYvUgpMNWtOZXBpL1ZyNFMzOVh2ZjJEeldrVExFSzhwY25qTmt0OS9hYWZoV3FGVlc3bTNIQ0FJSTZoL3FsUU5RS1NvCkd1SDM0UThHc0ZHMzBpelVFTlY5YXZZN2hTTHE3bmdnc3ZrbmxOQlp0RlVjbUdvUXJ0eDNGbXlZc0lDOC9SK0IKeXdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="}}} diff --git a/tests/apk.go b/tests/apk.go new file mode 100644 index 000000000..dd85dc49a --- /dev/null +++ b/tests/apk.go @@ -0,0 +1,92 @@ +// +build e2e + +// +// Copyright 2021 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 e2e + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "crypto" + crand "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "encoding/hex" + "io" + "io/ioutil" + "strings" + "testing" +) + +func createSignedApk(t *testing.T, artifactPath string) { + t.Helper() + + data := randomData(t, 100) + dataTarBuf := bytes.Buffer{} + dataTar := tar.NewWriter(&dataTarBuf) + dataTar.WriteHeader(&tar.Header{Name: "random.txt", Size: int64(len(data))}) + dataTar.Write(data) + dataTar.Close() + + dataTGZBuf := bytes.Buffer{} + dataGZ, _ := gzip.NewWriterLevel(&dataTGZBuf, gzip.BestCompression) + dataGZ.Write(dataTarBuf.Bytes()) + dataGZ.Close() + + datahash := sha256.Sum256(dataTGZBuf.Bytes()) + + ctlData := strings.Builder{} + ctlData.WriteString("name = " + randomRpmSuffix()) + ctlData.WriteRune('\n') + ctlData.WriteString("datahash = " + hex.EncodeToString(datahash[:])) + ctlData.WriteRune('\n') + ctlTarBuf := bytes.Buffer{} + ctlTar := tar.NewWriter(&ctlTarBuf) + ctlTar.WriteHeader(&tar.Header{Name: ".PKGINFO", Size: int64(ctlData.Len())}) + ctlTar.Write([]byte(ctlData.String())) + ctlTar.Flush() + // do not close so uncompressed stream appears as contiguous tar archive + + ctlTGZBuf := bytes.Buffer{} + ctlGZ, _ := gzip.NewWriterLevel(&ctlTGZBuf, gzip.BestCompression) + ctlGZ.Write(ctlTarBuf.Bytes()) + ctlGZ.Close() + + sha1sum := sha1.Sum(ctlTGZBuf.Bytes()) + sig, _ := rsa.SignPKCS1v15(crand.Reader, certPrivateKey, crypto.SHA1, sha1sum[:]) + + sigTarBuf := bytes.Buffer{} + sigTar := tar.NewWriter(&sigTarBuf) + sigTar.WriteHeader(&tar.Header{Name: ".SIGN.RSA.fixed.pub", Size: int64(len(sig))}) + sigTar.Write(sig) + sigTar.Flush() + // do not close so uncompressed stream appears as contiguous tar archive + + sigTGZBuf := bytes.Buffer{} + sigGZ, _ := gzip.NewWriterLevel(&sigTGZBuf, gzip.BestCompression) + sigGZ.Write(sigTarBuf.Bytes()) + sigGZ.Close() + + apkBuf := bytes.Buffer{} + if _, err := io.Copy(&apkBuf, io.MultiReader(&sigTGZBuf, &ctlTGZBuf, &dataTGZBuf)); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(artifactPath, apkBuf.Bytes(), 777); err != nil { + t.Fatal(err) + } +} diff --git a/tests/e2e_test.go b/tests/e2e_test.go index ce9ffd34e..f8dda21ad 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -288,6 +288,24 @@ func TestJAR(t *testing.T) { outputContains(t, out, "Entry already exists") } +func TestAPK(t *testing.T) { + td := t.TempDir() + artifactPath := filepath.Join(td, "artifact.apk") + + createSignedApk(t, artifactPath) + + pubPath := filepath.Join(t.TempDir(), "pubKey.asc") + if err := ioutil.WriteFile(pubPath, []byte(pubKey), 0644); err != nil { + t.Fatal(err) + } + + // If we do it twice, it should already exist + out := runCli(t, "upload", "--artifact", artifactPath, "--type", "alpine", "--public-key", pubPath) + outputContains(t, out, "Created entry at") + out = runCli(t, "upload", "--artifact", artifactPath, "--type", "alpine", "--public-key", pubPath) + outputContains(t, out, "Entry already exists") +} + func TestIntoto(t *testing.T) { td := t.TempDir() attestationPath := filepath.Join(td, "attestation.json") @@ -394,7 +412,6 @@ func TestTimestampArtifact(t *testing.T) { } sha := sha256.Sum256(artifactBytes) - var out string out = runCli(t, "upload", "--type", "rfc3161", "--artifact", tsrPath) diff --git a/tests/test_alpine.apk b/tests/test_alpine.apk new file mode 100644 index 0000000000000000000000000000000000000000..272ac3f874495975d4fa597373d930e898066818 GIT binary patch literal 2874 zcmV-A3&r#wiwFP!000021JeukbobK>3U<^>%qhssOVv$DElbUD$jK}&DTWH=WagDt z=;ars>zX8*B^p?m80ZxhC+ZcHCNU5P41mDQ!~{$O0Xl790%RK+o0%9Inwc6H0o559 z85tTgC>Su%0hAV(Bo-+sFcjtIm*7)JNPdU{ogQbF16S&u(i*m$U+pR`wDWoEW)8Qf zvoiDD7JI&l>sw&%^l3p&d}T`h*|e$iSd`8tPg(mVJZ*Z{jd!QrZ!CGVkj?YOmi?^K z8k6!~l{YO*-0-*Mv|z5oYxB9@tTU8fNn|c7i@UTW?}VI!&CbgWmp02kRCJY^k^h!Y zO8QLFkIboM`ZD5gkIJz4zgwRf&lUf1Q{H{^S3Atk`?v0!9%UOP-Lt*=`oaAlHwd5q zBbvS;F}cP{STBZq62mX0t1^n;gtkaE&GCxb9(GPT`!x=*LUoc z7J6t;EiCkb80*dRNN+~j)8oUt_isO}{vvXMD$6AjevLxNVkHVy3Zbf8l1}8R5-Xms z{sE>jLF9ZD`#$|n_ow^|#T9?RfDsZ}zCCl;PR?6iWHQUwQLrmMjV|o@4qBW$z>gS5 z*9R^#DJlDfe!Cy2e}aoxXfkFhISvIL;i}x0Oo*lZIYi^Dng{p%B(Eb zaS>V^4RQC2kefcF*n2*j=!V3m=qYzdX&5)x*K@SlrDWE}{WV)IZOH?vM5U`*>87q3 zDO%VsXT6>k{WE>vfFE4IwZ$X)JMYFcW*5r4FrDUtw`_FJOD$FCl0h^F+MKlds!G;J zw|C*ECuaJtbBPG6@}}C=*38ei(p$(2X=SY>p|qw%E1}lLunt^Ew3eGa0z{Aeqf18! z82C-Q5Lx_A+>tM6r_i=jwsW`4JA+}wAH7=Bq?pA4gC)+N!->WMQ|dcN&Vcu4ZZV;u z0dLq4`=g^)lhSl{bFnZFWj^Ep+in}l5oI33r#$Qj z)Y1yHPIw#MWr2tpOIi_2Nd|{n%SZxkbJ*lGG&4Qv?h#2u`GWjTK49~jAIfX0dWO`E z^2Qrmh!Hkm&#kJus!r9ZrplGKY;nxlgezTkTJ=_~l4zCtq^pXn)o9o&u7}sEQ?KQm zdaL0$t!B06dKE$=Lf&20jUmGo_Q){&y`szKco7=80U}3XYKKCBfGe%|u`GpyR)CjKXecj`Yu|ETIcsk&v>FQ@;h-9P6L z_;~)WwVd{r|DP5AM?-+c%=5Wtg3r(Y?e+@)Myp*%{2R4qearvPbpHRj6yNGV@9lS` z(b?I+EmNsn=_D+>$33SzMgW~gqgop`J*Vnb+bz%OjH^x+?>$y)H1=AZhBw{*Nci9B z|4-NMAItwp`mOu@k3S-7$p6%nSYfr-xvz46|Mgd|fBVN@{doP4zx^Wj<3D%${@`C* z{{QdSi?1t$zV`Dk@-JW@JRWI20VBcq9D24Wz?D-oXNtp-Ori<$g^b{vo_q8X@R2K! z5eKOtl!~JfPew+GaboaQkQxLwQ=Er9GP(@la&E1ko{jo%4q+++4ilkMCER>247!6N zLc?6DE104sHkk<8m<1prd=aK@C_ZCqBBB{?8!wfZ`3B@X!ZGHHxQtdt6oz}W@Sa`V)OH>Zo5+Z3J(fp2OzVLW#&{6a% zj03?UkKcy1-T`y*KC>amhM_pKVSt?86+?!$=+N?wiCCcH!nA-6YSd0#NY>aLOY)-gYa4hEN!CYaQqgfGj20B`~3(jq} zR^DOIcLgwCEP_JMg}z&W-}l_UTSOc0`lI8s^AWu34TrtcQUB0|vmv}WJ3Z)+`e zbOgQA_wZf+^q>eFbA{XRMXbnwbW%_r`Gg={)z8WSDi3=gX z^Mx>I1$Re4O`^yH1n8A5jf;zmv;Zg4BS<0%fsAGtmM4`$V#haglwiijX?^3GpkAv_ zlgLZ+LA~x{96?$#RCJiKUsj<65;K{$o-J)okn6Z;TwD1sDXw`8y0+UZPG}qo+6H!@u_`~ZB>Q} zPAsODMGVOc5=;IvP77B|A>vev_&t9-8ofQ}?pD@k9IL%+!;OV^1xbvJyS&u^9$gD1 zBSHhNnG(d3I*&#T!V9G-+}c7>l+gR+W0VIdKmL}V3cD9u(}Hb3nV2{sv>_d1R8WR5 zaK~)iWbA)DIvGIPu4p7tL0TI`u*P!IE!80U2P;k@#DitDD1tBN{F=ryaVf>DB-U%F z?ZBbIifZ}6`_qeVffS%LNy0d-2HN0!I9TUhbA^_6QB_VZvC>VVA-lv_Txh62wexTy zsaw#3&#pNYY0)NVB}@(zO=ORA5d^lotT%Zqx6!$a`*sW2oC~DK0$*zLs#C3`PLxvJ z)K8b2l=W6$o9ggDtekgp(PTa^10EedsAH@x8 zyw)iEJPfEI{{V%>&clop;Oj2FFTkH*-IoB5#HcUxhr{97uv=Z*A~r0*eRZ((FH=`6B|+&5?0)uL>d4OT?8E+kYEtXq z2LG+hQ`x2mKVs7KScMxs z9AMl~b?RG7X>(zQsK)OM9qUq|d?&ElDz4Nd5$XM0n5VLUO`b^-N5RgT9pDDo{0erV zl2yb`1^$B7foGci^pNz|)id7hu1wzY&StYKx!>IL&ko3Y9&*jReBSP}V_)XCf5zCZ Y?b@#G+OF-|t}kf)8^BeR`~WBb02Q5_ng9R* literal 0 HcmV?d00001 diff --git a/tests/test_alpine.pub b/tests/test_alpine.pub new file mode 100644 index 000000000..bb4bdc80f --- /dev/null +++ b/tests/test_alpine.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1yHJxQgsHQREclQu4Ohe +qxTxd1tHcNnvnQTu/UrTky8wWvgXT+jpveroeWWnzmsYlDI93eLI2ORakxb3gA2O +Q0Ry4ws8vhaxLQGC74uQR5+/yYrLuTKydFzuPaS1dK19qJPXB8GMdmFOijnXX4SA +jixuHLe1WW7kZVtjL7nufvpXkWBGjsfrvskdNA/5MfxAeBbqPgaq0QMEfxMAn6/R +L5kNepi/Vr4S39Xvf2DzWkTLEK8pcnjNkt9/aafhWqFVW7m3HCAII6h/qlQNQKSo +GuH34Q8GsFG30izUENV9avY7hSLq7nggsvknlNBZtFUcmGoQrtx3FmyYsIC8/R+B +ywIDAQAB +-----END PUBLIC KEY----- From 89294e3d2c02909b1af88d5711781f0f5df53b12 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Fri, 25 Jun 2021 08:27:45 -0400 Subject: [PATCH 4/8] use shaFlag for --artifact-hash Signed-off-by: Bob Callaway --- cmd/rekor-cli/app/pflag_groups.go | 2 +- cmd/rekor-cli/app/pflags.go | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/rekor-cli/app/pflag_groups.go b/cmd/rekor-cli/app/pflag_groups.go index 4108f0f9b..23399f89e 100644 --- a/cmd/rekor-cli/app/pflag_groups.go +++ b/cmd/rekor-cli/app/pflag_groups.go @@ -76,7 +76,7 @@ func addArtifactPFlags(cmd *cobra.Command) error { false, }, "artifact-hash": { - uuidFlag, + shaFlag, "hex encoded SHA256 hash of artifact (when using URL)", false, }, diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index 7c0cdc41f..ee452c2c9 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -161,15 +161,13 @@ func IsURL(v string) bool { func validateSHA256Value(v string) error { var prefix, hash string - split := strings.Split(v, ":") + split := strings.SplitN(v, ":", 2) switch len(split) { case 1: hash = split[0] case 2: prefix = split[0] hash = split[1] - default: - return errors.New("invalid format for SHA flag") } s := struct { From 03855823b84af96f8d15460f0349404304f78633 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Mon, 28 Jun 2021 08:08:57 -0400 Subject: [PATCH 5/8] change arg type to PKIFormat Signed-off-by: Bob Callaway --- pkg/api/index.go | 2 +- pkg/pki/factory/factory.go | 4 ++-- pkg/pki/factory/factory_test.go | 2 +- pkg/types/alpine/v0.0.1/entry.go | 2 +- pkg/types/rekord/v0.0.1/entry.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index f5ed59624..27f8136f5 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -45,7 +45,7 @@ func SearchIndexHandler(params index.SearchIndexParams) middleware.Responder { result = append(result, resultUUIDs...) } if params.Query.PublicKey != nil { - af, err := pkifactory.NewArtifactFactory(swag.StringValue(params.Query.PublicKey.Format)) + af, err := pkifactory.NewArtifactFactory(pkifactory.PKIFormat(swag.StringValue(params.Query.PublicKey.Format))) if err != nil { return handleRekorAPIError(params, http.StatusBadRequest, err, unsupportedPKIFormat) } diff --git a/pkg/pki/factory/factory.go b/pkg/pki/factory/factory.go index f60d0e43b..13ca9377c 100644 --- a/pkg/pki/factory/factory.go +++ b/pkg/pki/factory/factory.go @@ -41,8 +41,8 @@ type ArtifactFactory struct { impl pkiImpl } -func NewArtifactFactory(format string) (*ArtifactFactory, error) { - if impl, ok := artifactFactoryMap[PKIFormat(format)]; ok { +func NewArtifactFactory(format PKIFormat) (*ArtifactFactory, error) { + if impl, ok := artifactFactoryMap[format]; ok { return &ArtifactFactory{impl: impl}, nil } return nil, fmt.Errorf("%v is not a supported PKI format", format) diff --git a/pkg/pki/factory/factory_test.go b/pkg/pki/factory/factory_test.go index 7e914b411..f883e74b2 100644 --- a/pkg/pki/factory/factory_test.go +++ b/pkg/pki/factory/factory_test.go @@ -95,7 +95,7 @@ func TestFactoryNewKey(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { - factory, err := NewArtifactFactory(tc.format) + factory, err := NewArtifactFactory(PKIFormat(tc.format)) if tc.expectValidFormat != (err == nil) { t.Fatalf("unexpected error initializing factory for %v", tc.format) } diff --git a/pkg/types/alpine/v0.0.1/entry.go b/pkg/types/alpine/v0.0.1/entry.go index a61b8d03d..825294a76 100644 --- a/pkg/types/alpine/v0.0.1/entry.go +++ b/pkg/types/alpine/v0.0.1/entry.go @@ -161,7 +161,7 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { if v.AlpineModel.Package.Hash != nil && v.AlpineModel.Package.Hash.Value != nil { oldSHA = swag.StringValue(v.AlpineModel.Package.Hash.Value) } - artifactFactory, _ := pkifactory.NewArtifactFactory(string(pkifactory.X509)) + artifactFactory, _ := pkifactory.NewArtifactFactory(pkifactory.X509) g.Go(func() error { defer hashW.Close() diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index 848a388ee..640b683cc 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -164,7 +164,7 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { if v.RekordObj.Data.Hash != nil && v.RekordObj.Data.Hash.Value != nil { oldSHA = swag.StringValue(v.RekordObj.Data.Hash.Value) } - artifactFactory, err := pkifactory.NewArtifactFactory(v.RekordObj.Signature.Format) + artifactFactory, err := pkifactory.NewArtifactFactory(pkifactory.PKIFormat(v.RekordObj.Signature.Format)) if err != nil { return err } From d229e7b047e98463a348ebb2e4491c13d902d8cb Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Mon, 28 Jun 2021 09:20:58 -0400 Subject: [PATCH 6/8] defer type-specific validation logic to type code (instead of in CLI); also use CliLogger throughout CLI Signed-off-by: Bob Callaway --- cmd/rekor-cli/app/format/wrap.go | 6 +++--- cmd/rekor-cli/app/get.go | 6 +++--- cmd/rekor-cli/app/pflag_groups.go | 19 +++---------------- cmd/rekor-cli/app/root.go | 10 ++++------ cmd/rekor-cli/app/search.go | 8 ++++---- cmd/rekor-cli/app/timestamp.go | 6 +++--- cmd/rekor-cli/app/upload.go | 6 +++--- cmd/rekor-cli/app/verify.go | 7 ++++--- pkg/log/log.go | 3 ++- pkg/types/intoto/v0.0.1/entry.go | 11 +++++++---- pkg/types/jar/v0.0.1/entry.go | 6 +++--- pkg/types/rekord/v0.0.1/entry.go | 7 +++---- pkg/types/rfc3161/v0.0.1/entry.go | 7 +++++-- pkg/types/rpm/v0.0.1/entry.go | 8 ++++---- 14 files changed, 51 insertions(+), 59 deletions(-) diff --git a/cmd/rekor-cli/app/format/wrap.go b/cmd/rekor-cli/app/format/wrap.go index 99cf58b0e..5e81c37f6 100644 --- a/cmd/rekor-cli/app/format/wrap.go +++ b/cmd/rekor-cli/app/format/wrap.go @@ -18,8 +18,8 @@ package format import ( "encoding/json" "fmt" - "log" + "github.com/sigstore/rekor/pkg/log" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -32,7 +32,7 @@ func WrapCmd(f formatCmd) CobraCmd { return func(cmd *cobra.Command, args []string) { obj, err := f(args) if err != nil { - log.Fatal(err) + log.CliLogger.Fatal(err) } // TODO: add flags to control output formatting (JSON, plaintext, etc.) @@ -53,7 +53,7 @@ func WrapCmd(f formatCmd) CobraCmd { func toJSON(i interface{}) string { b, err := json.Marshal(i) if err != nil { - log.Fatal(err) + log.CliLogger.Fatal(err) } return string(b) } diff --git a/cmd/rekor-cli/app/get.go b/cmd/rekor-cli/app/get.go index 979900628..ca032ca16 100644 --- a/cmd/rekor-cli/app/get.go +++ b/cmd/rekor-cli/app/get.go @@ -66,7 +66,7 @@ var getCmd = &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { // these are bound here so that they are not overwritten by other commands if err := viper.BindPFlags(cmd.Flags()); err != nil { - log.Logger.Fatal("Error initializing cmd line args: ", err) + log.CliLogger.Fatal("Error initializing cmd line args: ", err) } }, Run: format.WrapCmd(func(args []string) (interface{}, error) { @@ -153,10 +153,10 @@ func parseEntry(uuid string, e models.LogEntryAnon) (interface{}, error) { func init() { initializePFlagMap() if err := addUUIDPFlags(getCmd, false); err != nil { - log.Logger.Fatal("Error parsing cmd line args: ", err) + log.CliLogger.Fatal("Error parsing cmd line args: ", err) } if err := addLogIndexFlag(getCmd, false); err != nil { - log.Logger.Fatal("Error parsing cmd line args: ", err) + log.CliLogger.Fatal("Error parsing cmd line args: ", err) } rootCmd.AddCommand(getCmd) diff --git a/cmd/rekor-cli/app/pflag_groups.go b/cmd/rekor-cli/app/pflag_groups.go index 23399f89e..440c3b0cd 100644 --- a/cmd/rekor-cli/app/pflag_groups.go +++ b/cmd/rekor-cli/app/pflag_groups.go @@ -105,28 +105,15 @@ func validateArtifactPFlags(uuidValid, indexValid bool) error { if indexValid && viper.GetString("log-index") != "" { indexGiven = true } - // we will need artifact, public-key, signature - entry := viper.GetString("entry") - if entry == "" && viper.GetString("artifact") == "" { + + // if neither --entry or --artifact were given, then a reference to a uuid or index is needed + if viper.GetString("entry") == "" && viper.GetString("artifact") == "" { if (uuidGiven && uuidValid) || (indexGiven && indexValid) { return nil } return errors.New("either 'entry' or 'artifact' must be specified") } - typeStr := viper.GetString("type") - signature := viper.GetString("signature") - publicKey := viper.GetString("public-key") - - if entry == "" { - if signature == "" && typeStr == "rekord" { - return errors.New("--signature is required when --artifact is used") - } - if publicKey == "" && typeStr != "jar" && typeStr != "rfc3161" { - return errors.New("--public-key is required when --artifact is used") - } - } - return nil } diff --git a/cmd/rekor-cli/app/root.go b/cmd/rekor-cli/app/root.go index 56f76c192..362a63973 100644 --- a/cmd/rekor-cli/app/root.go +++ b/cmd/rekor-cli/app/root.go @@ -18,7 +18,6 @@ package app import ( "fmt" "net/url" - "os" "strings" "github.com/go-openapi/runtime" @@ -30,6 +29,7 @@ import ( "github.com/spf13/viper" "github.com/sigstore/rekor/pkg/generated/client" + "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/util" // these imports are to call the packages' init methods @@ -53,8 +53,7 @@ var rootCmd = &cobra.Command{ // Execute runs the base CLI func Execute() { if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) + log.CliLogger.Fatal(err) } } @@ -70,8 +69,7 @@ func init() { // these are bound here and not in PreRun so that all child commands can use them if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { - fmt.Println(err) - os.Exit(1) + log.CliLogger.Fatal(err) } } @@ -116,7 +114,7 @@ func initConfig(cmd *cobra.Command) error { return err } } else if viper.GetString("format") == "default" { - fmt.Println("Using config file:", viper.ConfigFileUsed()) + log.CliLogger.Infof("Using config file:", viper.ConfigFileUsed()) } return nil diff --git a/cmd/rekor-cli/app/search.go b/cmd/rekor-cli/app/search.go index 182482f52..a0acf2c58 100644 --- a/cmd/rekor-cli/app/search.go +++ b/cmd/rekor-cli/app/search.go @@ -92,16 +92,16 @@ var searchCmd = &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { // these are bound here so that they are not overwritten by other commands if err := viper.BindPFlags(cmd.Flags()); err != nil { - log.Logger.Fatal("Error initializing cmd line args: ", err) + log.CliLogger.Fatal("Error initializing cmd line args: ", err) } if err := validateSearchPFlags(); err != nil { - log.Logger.Error(err) + log.CliLogger.Error(err) _ = cmd.Help() os.Exit(1) } }, Run: format.WrapCmd(func(args []string) (interface{}, error) { - log := log.Logger + log := log.CliLogger rekorClient, err := GetRekorClient(viper.GetString("rekor_server")) if err != nil { return nil, err @@ -201,7 +201,7 @@ var searchCmd = &cobra.Command{ func init() { initializePFlagMap() if err := addSearchPFlags(searchCmd); err != nil { - log.Logger.Fatal("Error parsing cmd line args:", err) + log.CliLogger.Fatal("Error parsing cmd line args:", err) } rootCmd.AddCommand(searchCmd) diff --git a/cmd/rekor-cli/app/timestamp.go b/cmd/rekor-cli/app/timestamp.go index da438cf17..981162e03 100644 --- a/cmd/rekor-cli/app/timestamp.go +++ b/cmd/rekor-cli/app/timestamp.go @@ -129,10 +129,10 @@ var timestampCmd = &cobra.Command{ Long: "Generates and uploads (WIP) an RFC 3161 timestamp response to the log. The timestamp response can be verified locally using Rekor's timestamping cert chain.", PreRunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.Flags()); err != nil { - log.Logger.Fatal("Error initializing cmd line args: ", err) + log.CliLogger.Fatal("Error initializing cmd line args: ", err) } if err := validateTimestampFlags(); err != nil { - log.Logger.Error(err) + log.CliLogger.Error(err) _ = cmd.Help() return err } @@ -192,7 +192,7 @@ var timestampCmd = &cobra.Command{ func init() { initializePFlagMap() if err := addTimestampFlags(timestampCmd); err != nil { - log.Logger.Fatal("Error parsing cmd line args: ", err) + log.CliLogger.Fatal("Error parsing cmd line args: ", err) } rootCmd.AddCommand(timestampCmd) diff --git a/cmd/rekor-cli/app/upload.go b/cmd/rekor-cli/app/upload.go index 3a39441e0..2b626bf1f 100644 --- a/cmd/rekor-cli/app/upload.go +++ b/cmd/rekor-cli/app/upload.go @@ -62,10 +62,10 @@ var uploadCmd = &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { // these are bound here so that they are not overwritten by other commands if err := viper.BindPFlags(cmd.Flags()); err != nil { - log.Logger.Fatal("Error initializing cmd line args: ", err) + log.CliLogger.Fatal("Error initializing cmd line args: ", err) } if err := validateArtifactPFlags(false, false); err != nil { - log.Logger.Error(err) + log.CliLogger.Error(err) _ = cmd.Help() os.Exit(1) } @@ -193,7 +193,7 @@ func verifyLogEntry(ctx context.Context, rekorClient *client.Rekor, logEntry mod func init() { initializePFlagMap() if err := addArtifactPFlags(uploadCmd); err != nil { - log.Logger.Fatal("Error parsing cmd line args:", err) + log.CliLogger.Fatal("Error parsing cmd line args:", err) } rootCmd.AddCommand(uploadCmd) diff --git a/cmd/rekor-cli/app/verify.go b/cmd/rekor-cli/app/verify.go index d98dade57..ab22d11dc 100644 --- a/cmd/rekor-cli/app/verify.go +++ b/cmd/rekor-cli/app/verify.go @@ -79,6 +79,7 @@ var verifyCmd = &cobra.Command{ return fmt.Errorf("error initializing cmd line args: %s", err) } if err := validateArtifactPFlags(true, true); err != nil { + log.CliLogger.Error(err) _ = cmd.Help() return err } @@ -165,13 +166,13 @@ var verifyCmd = &cobra.Command{ func init() { initializePFlagMap() if err := addArtifactPFlags(verifyCmd); err != nil { - log.Logger.Fatal("Error parsing cmd line args:", err) + log.CliLogger.Fatal("Error parsing cmd line args:", err) } if err := addUUIDPFlags(verifyCmd, false); err != nil { - log.Logger.Fatal("Error parsing cmd line args:", err) + log.CliLogger.Fatal("Error parsing cmd line args:", err) } if err := addLogIndexFlag(verifyCmd, false); err != nil { - log.Logger.Fatal("Error parsing cmd line args:", err) + log.CliLogger.Fatal("Error parsing cmd line args:", err) } rootCmd.AddCommand(verifyCmd) diff --git a/pkg/log/log.go b/pkg/log/log.go index ed3c6841a..5cc68ec70 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -54,8 +54,9 @@ var CliLogger = createCliLogger() func createCliLogger() *zap.SugaredLogger { cfg := zap.NewDevelopmentConfig() cfg.EncoderConfig.TimeKey = "" - cfg.EncoderConfig.LevelKey = "" + // cfg.EncoderConfig.LevelKey = "" cfg.DisableCaller = true + cfg.DisableStacktrace = true logger, err := cfg.Build() if err != nil { log.Fatalln("createLogger", err) diff --git a/pkg/types/intoto/v0.0.1/entry.go b/pkg/types/intoto/v0.0.1/entry.go index 35ab9f0b6..ac6624c2b 100644 --- a/pkg/types/intoto/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.1/entry.go @@ -92,7 +92,7 @@ func (v *V001Entry) Unmarshal(pe models.ProposedEntry) error { } // Only support x509 signatures for intoto attestations - af, err := pkifactory.NewArtifactFactory("x509") + af, err := pkifactory.NewArtifactFactory(pkifactory.X509) if err != nil { return err } @@ -196,7 +196,7 @@ func (v *verifier) Sign(d []byte) ([]byte, string, error) { } func (v *verifier) Verify(keyID string, data, sig []byte) (bool, error) { - af, err := pkifactory.NewArtifactFactory("x509") + af, err := pkifactory.NewArtifactFactory(pkifactory.X509) if err != nil { return false, err } @@ -218,7 +218,10 @@ func (v V001Entry) CreateFromPFlags(_ context.Context, props types.ArtifactPrope artifactBytes := props.ArtifactBytes if artifactBytes == nil { if props.ArtifactPath == nil { - return nil, errors.New("invalid path to artifact specified") + return nil, errors.New("path to artifact file must be specified") + } + if props.ArtifactPath.IsAbs() { + return nil, errors.New("intoto envelopes cannot be fetched over HTTP(S)") } artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) if err != nil { @@ -228,7 +231,7 @@ func (v V001Entry) CreateFromPFlags(_ context.Context, props types.ArtifactPrope publicKeyBytes := props.PublicKeyBytes if publicKeyBytes == nil { if props.PublicKeyPath == nil { - return nil, errors.New("invalid path to public key specified") + return nil, errors.New("public key must be provided to verify signature") } publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) if err != nil { diff --git a/pkg/types/jar/v0.0.1/entry.go b/pkg/types/jar/v0.0.1/entry.go index c16865d67..eab4bd502 100644 --- a/pkg/types/jar/v0.0.1/entry.go +++ b/pkg/types/jar/v0.0.1/entry.go @@ -180,7 +180,7 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { } v.jarObj = jarObj[0] - af, err := pkifactory.NewArtifactFactory("pkcs7") + af, err := pkifactory.NewArtifactFactory(pkifactory.PKCS7) if err != nil { return err } @@ -334,7 +334,7 @@ func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactPro artifactBytes := props.ArtifactBytes if artifactBytes == nil { if props.ArtifactPath == nil { - return nil, errors.New("invalid path to artifact") + return nil, errors.New("path to JAR archive (file or URL) must be specified") } if props.ArtifactPath.IsAbs() { re.JARModel.Archive.URL = strfmt.URI(props.ArtifactPath.String()) @@ -347,7 +347,7 @@ func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactPro } else { artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) if err != nil { - return nil, fmt.Errorf("error reading artifact file: %w", err) + return nil, fmt.Errorf("error reading JAR file: %w", err) } //TODO: ensure this is a valid JAR file; look for META-INF/MANIFEST.MF? re.JARModel.Archive.Content = strfmt.Base64(artifactBytes) diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index 640b683cc..8596b2afe 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -401,7 +401,7 @@ func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactPro artifactBytes := props.ArtifactBytes if artifactBytes == nil { if props.ArtifactPath == nil { - return nil, errors.New("invalid path to artifact") + return nil, errors.New("path to artifact (file or URL) must be specified") } if props.ArtifactPath.IsAbs() { re.RekordObj.Data.URL = strfmt.URI(props.ArtifactPath.String()) @@ -436,7 +436,7 @@ func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactPro sigBytes := props.SignatureBytes if sigBytes == nil { if props.SignaturePath == nil { - return nil, errors.New("invalid path to signature specified") + return nil, errors.New("a detached signature must be provided") } if props.SignaturePath.IsAbs() { re.RekordObj.Signature.URL = strfmt.URI(props.SignaturePath.String()) @@ -455,7 +455,7 @@ func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactPro publicKeyBytes := props.PublicKeyBytes if publicKeyBytes == nil { if props.PublicKeyPath == nil { - return nil, errors.New("invalid path to public key specified") + return nil, errors.New("public key must be provided to verify detached signature") } if props.PublicKeyPath.IsAbs() { re.RekordObj.Signature.PublicKey.URL = strfmt.URI(props.PublicKeyPath.String()) @@ -468,7 +468,6 @@ func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactPro } } else { re.RekordObj.Signature.PublicKey.Content = strfmt.Base64(publicKeyBytes) - } if err := re.Validate(); err != nil { diff --git a/pkg/types/rfc3161/v0.0.1/entry.go b/pkg/types/rfc3161/v0.0.1/entry.go index a80dff0ad..697627ba7 100644 --- a/pkg/types/rfc3161/v0.0.1/entry.go +++ b/pkg/types/rfc3161/v0.0.1/entry.go @@ -201,11 +201,14 @@ func (v V001Entry) CreateFromPFlags(_ context.Context, props types.ArtifactPrope artifactBytes := props.ArtifactBytes if artifactBytes == nil { if props.ArtifactPath == nil { - return nil, errors.New("invalid path to artifact specified") + return nil, errors.New("path to artifact file must be specified") + } + if props.ArtifactPath.IsAbs() { + return nil, errors.New("RFC3161 timestamps cannot be fetched over HTTP(S)") } artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) + return nil, fmt.Errorf("error reading artifact file: %w", err) } } diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index 08fd5eae2..1b256b543 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -165,7 +165,7 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { if v.RPMModel.Package.Hash != nil && v.RPMModel.Package.Hash.Value != nil { oldSHA = swag.StringValue(v.RPMModel.Package.Hash.Value) } - artifactFactory, err := pkifactory.NewArtifactFactory("pgp") + artifactFactory, err := pkifactory.NewArtifactFactory(pkifactory.PGP) if err != nil { return err } @@ -376,14 +376,14 @@ func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactPro artifactBytes := props.ArtifactBytes if artifactBytes == nil { if props.ArtifactPath == nil { - return nil, errors.New("invalid path to artifact") + return nil, errors.New("path to RPM file (file or URL) must be specified") } if props.ArtifactPath.IsAbs() { re.RPMModel.Package.URL = strfmt.URI(props.ArtifactPath.String()) } else { artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) if err != nil { - return nil, fmt.Errorf("error reading artifact file: %w", err) + return nil, fmt.Errorf("error reading RPM file: %w", err) } re.RPMModel.Package.Content = strfmt.Base64(artifactBytes) } @@ -395,7 +395,7 @@ func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactPro publicKeyBytes := props.PublicKeyBytes if publicKeyBytes == nil { if props.PublicKeyPath == nil { - return nil, errors.New("invalid path to artifact") + return nil, errors.New("public key must be provided to verify RPM signature") } if props.PublicKeyPath.IsAbs() { re.RPMModel.PublicKey.URL = strfmt.URI(props.PublicKeyPath.String()) From 75445ded90f8bf20a9d97935545cf4eb374aa034 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Mon, 12 Jul 2021 14:02:44 -0400 Subject: [PATCH 7/8] refactor factory code Signed-off-by: Bob Callaway --- cmd/rekor-cli/app/pflag_groups.go | 4 +- cmd/rekor-cli/app/pflags.go | 4 +- pkg/api/index.go | 4 +- pkg/pki/factory.go | 116 ++++++++++++++++++++++++++ pkg/pki/factory/factory.go | 97 --------------------- pkg/pki/{factory => }/factory_test.go | 28 +++---- pkg/pki/minisign/minisign.go | 7 +- pkg/pki/pgp/pgp.go | 11 ++- pkg/pki/pkcs7/pkcs7.go | 7 +- pkg/pki/pki.go | 2 +- pkg/pki/ssh/ssh.go | 7 +- pkg/pki/x509/x509.go | 7 +- pkg/types/alpine/apk_test.go | 2 +- pkg/types/alpine/v0.0.1/entry.go | 3 +- pkg/types/helm/provenance_test.go | 4 +- pkg/types/helm/v0.0.1/entry.go | 3 +- pkg/types/intoto/v0.0.1/entry.go | 3 +- pkg/types/jar/v0.0.1/entry.go | 3 +- pkg/types/rekord/v0.0.1/entry.go | 3 +- pkg/types/rpm/v0.0.1/entry.go | 3 +- 20 files changed, 163 insertions(+), 155 deletions(-) create mode 100644 pkg/pki/factory.go delete mode 100644 pkg/pki/factory/factory.go rename pkg/pki/{factory => }/factory_test.go (77%) diff --git a/cmd/rekor-cli/app/pflag_groups.go b/cmd/rekor-cli/app/pflag_groups.go index 440c3b0cd..1ddb45b70 100644 --- a/cmd/rekor-cli/app/pflag_groups.go +++ b/cmd/rekor-cli/app/pflag_groups.go @@ -21,7 +21,7 @@ import ( "net/url" "strings" - "github.com/sigstore/rekor/pkg/pki/factory" + "github.com/sigstore/rekor/pkg/pki" "github.com/sigstore/rekor/pkg/types" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -62,7 +62,7 @@ func addArtifactPFlags(cmd *cobra.Command) error { }, "pki-format": { pkiFormatFlag, - fmt.Sprintf("format of the signature and/or public key; options = %v", factory.SupportedFormats()), + fmt.Sprintf("format of the signature and/or public key; options = %v", pki.SupportedFormats()), false, }, "public-key": { diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index ee452c2c9..4922057eb 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -21,11 +21,11 @@ import ( "strconv" "strings" + "github.com/sigstore/rekor/pkg/pki" "github.com/spf13/pflag" "github.com/go-playground/validator" "github.com/pkg/errors" - pkifactory "github.com/sigstore/rekor/pkg/pki/factory" ) type FlagType string @@ -69,7 +69,7 @@ func initializePFlagMap() { }, pkiFormatFlag: func() pflag.Value { // this ensures a PKI implementation exists for the requested format - return valueFactory(pkiFormatFlag, validateString(fmt.Sprintf("required,oneof=%v", strings.Join(pkifactory.SupportedFormats(), " "))), "pgp") + return valueFactory(pkiFormatFlag, validateString(fmt.Sprintf("required,oneof=%v", strings.Join(pki.SupportedFormats(), " "))), "pgp") }, typeFlag: func() pflag.Value { // this ensures the type of the log entry matches a type supported in the CLI diff --git a/pkg/api/index.go b/pkg/api/index.go index 27f8136f5..8aa088a70 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -28,7 +28,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/generated/restapi/operations/index" - pkifactory "github.com/sigstore/rekor/pkg/pki/factory" + "github.com/sigstore/rekor/pkg/pki" "github.com/sigstore/rekor/pkg/util" ) @@ -45,7 +45,7 @@ func SearchIndexHandler(params index.SearchIndexParams) middleware.Responder { result = append(result, resultUUIDs...) } if params.Query.PublicKey != nil { - af, err := pkifactory.NewArtifactFactory(pkifactory.PKIFormat(swag.StringValue(params.Query.PublicKey.Format))) + af, err := pki.NewArtifactFactory(pki.Format(swag.StringValue(params.Query.PublicKey.Format))) if err != nil { return handleRekorAPIError(params, http.StatusBadRequest, err, unsupportedPKIFormat) } diff --git a/pkg/pki/factory.go b/pkg/pki/factory.go new file mode 100644 index 000000000..33df90bbd --- /dev/null +++ b/pkg/pki/factory.go @@ -0,0 +1,116 @@ +// +// Copyright 2021 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 pki + +import ( + "fmt" + "io" + + "github.com/sigstore/rekor/pkg/pki/minisign" + "github.com/sigstore/rekor/pkg/pki/pgp" + "github.com/sigstore/rekor/pkg/pki/pkcs7" + "github.com/sigstore/rekor/pkg/pki/ssh" + "github.com/sigstore/rekor/pkg/pki/x509" +) + +type Format string + +const ( + PGP Format = "pgp" + Minisign Format = "minisign" + SSH Format = "ssh" + X509 Format = "x509" + PKCS7 Format = "pkcs7" +) + +type ArtifactFactory struct { + impl pkiImpl +} + +func NewArtifactFactory(format Format) (*ArtifactFactory, error) { + if impl, ok := artifactFactoryMap[format]; ok { + return &ArtifactFactory{impl: impl}, nil + } + return nil, fmt.Errorf("%v is not a supported PKI format", format) +} + +type pkiImpl struct { + newPubKey func(io.Reader) (PublicKey, error) + newSignature func(io.Reader) (Signature, error) +} + +var artifactFactoryMap map[Format]pkiImpl + +func init() { + artifactFactoryMap = map[Format]pkiImpl{ + PGP: { + newPubKey: func(r io.Reader) (PublicKey, error) { + return pgp.NewPublicKey(r) + }, + newSignature: func(r io.Reader) (Signature, error) { + return pgp.NewSignature(r) + }, + }, + Minisign: { + newPubKey: func(r io.Reader) (PublicKey, error) { + return minisign.NewPublicKey(r) + }, + newSignature: func(r io.Reader) (Signature, error) { + return minisign.NewSignature(r) + }, + }, + SSH: { + newPubKey: func(r io.Reader) (PublicKey, error) { + return ssh.NewPublicKey(r) + }, + newSignature: func(r io.Reader) (Signature, error) { + return ssh.NewSignature(r) + }, + }, + X509: { + newPubKey: func(r io.Reader) (PublicKey, error) { + return x509.NewPublicKey(r) + }, + newSignature: func(r io.Reader) (Signature, error) { + return x509.NewSignature(r) + }, + }, + PKCS7: { + newPubKey: func(r io.Reader) (PublicKey, error) { + return pkcs7.NewPublicKey(r) + }, + newSignature: func(r io.Reader) (Signature, error) { + return pkcs7.NewSignature(r) + }, + }, + } +} + +func SupportedFormats() []string { + var formats []string + for f := range artifactFactoryMap { + formats = append(formats, string(f)) + } + return formats +} + +func (a ArtifactFactory) NewPublicKey(r io.Reader) (PublicKey, error) { + return a.impl.newPubKey(r) +} + +func (a ArtifactFactory) NewSignature(r io.Reader) (Signature, error) { + return a.impl.newSignature(r) +} diff --git a/pkg/pki/factory/factory.go b/pkg/pki/factory/factory.go deleted file mode 100644 index 13ca9377c..000000000 --- a/pkg/pki/factory/factory.go +++ /dev/null @@ -1,97 +0,0 @@ -// -// Copyright 2021 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 factory - -import ( - "fmt" - "io" - - "github.com/sigstore/rekor/pkg/pki" - "github.com/sigstore/rekor/pkg/pki/minisign" - "github.com/sigstore/rekor/pkg/pki/pgp" - "github.com/sigstore/rekor/pkg/pki/pkcs7" - "github.com/sigstore/rekor/pkg/pki/ssh" - "github.com/sigstore/rekor/pkg/pki/x509" -) - -type PKIFormat string - -const ( - PGP PKIFormat = "pgp" - Minisign PKIFormat = "minisign" - SSH PKIFormat = "ssh" - X509 PKIFormat = "x509" - PKCS7 PKIFormat = "pkcs7" -) - -type ArtifactFactory struct { - impl pkiImpl -} - -func NewArtifactFactory(format PKIFormat) (*ArtifactFactory, error) { - if impl, ok := artifactFactoryMap[format]; ok { - return &ArtifactFactory{impl: impl}, nil - } - return nil, fmt.Errorf("%v is not a supported PKI format", format) -} - -type pkiImpl struct { - newPubKey func(io.Reader) (pki.PublicKey, error) - newSignature func(io.Reader) (pki.Signature, error) -} - -var artifactFactoryMap map[PKIFormat]pkiImpl - -func init() { - artifactFactoryMap = map[PKIFormat]pkiImpl{ - PGP: { - newPubKey: pgp.NewPublicKey, - newSignature: pgp.NewSignature, - }, - Minisign: { - newPubKey: minisign.NewPublicKey, - newSignature: minisign.NewSignature, - }, - SSH: { - newPubKey: ssh.NewPublicKey, - newSignature: ssh.NewSignature, - }, - X509: { - newPubKey: x509.NewPublicKey, - newSignature: x509.NewSignature, - }, - PKCS7: { - newPubKey: pkcs7.NewPublicKey, - newSignature: pkcs7.NewSignature, - }, - } -} - -func SupportedFormats() []string { - var formats []string - for f := range artifactFactoryMap { - formats = append(formats, string(f)) - } - return formats -} - -func (a ArtifactFactory) NewPublicKey(r io.Reader) (pki.PublicKey, error) { - return a.impl.newPubKey(r) -} - -func (a ArtifactFactory) NewSignature(r io.Reader) (pki.Signature, error) { - return a.impl.newSignature(r) -} diff --git a/pkg/pki/factory/factory_test.go b/pkg/pki/factory_test.go similarity index 77% rename from pkg/pki/factory/factory_test.go rename to pkg/pki/factory_test.go index f883e74b2..a31742755 100644 --- a/pkg/pki/factory/factory_test.go +++ b/pkg/pki/factory_test.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package factory +package pki import ( "os" @@ -40,48 +40,48 @@ func TestFactoryNewKey(t *testing.T) { { name: "valid pgp", format: "pgp", - keyFile: "../pgp/testdata/valid_armored_public.pgp", - sigFile: "../pgp/testdata/hello_world.txt.asc.sig", + keyFile: "pgp/testdata/valid_armored_public.pgp", + sigFile: "pgp/testdata/hello_world.txt.asc.sig", expectSuccess: true, expectValidFormat: true, }, { name: "valid minisign", format: "minisign", - keyFile: "../minisign/testdata/minisign.pub", - sigFile: "../minisign/testdata/hello_world.txt.minisig", + keyFile: "minisign/testdata/minisign.pub", + sigFile: "minisign/testdata/hello_world.txt.minisig", expectSuccess: true, expectValidFormat: true, }, { name: "valid x509", format: "x509", - keyFile: "../x509/testdata/ec.pub", - sigFile: "../x509/testdata/hello_world.txt.sig", + keyFile: "x509/testdata/ec.pub", + sigFile: "x509/testdata/hello_world.txt.sig", expectSuccess: true, expectValidFormat: true, }, { name: "valid ssh", format: "ssh", - keyFile: "../ssh/testdata/id_rsa.pub", - sigFile: "../ssh/testdata/hello_world.txt.sig", + keyFile: "ssh/testdata/id_rsa.pub", + sigFile: "ssh/testdata/hello_world.txt.sig", expectSuccess: true, expectValidFormat: true, }, { name: "invalid ssh signature", format: "ssh", - keyFile: "../ssh/testdata/id_rsa.pub", - sigFile: "../ssh/testdata/hello_world.txt", + keyFile: "ssh/testdata/id_rsa.pub", + sigFile: "ssh/testdata/hello_world.txt", expectSuccess: false, expectValidFormat: true, }, { name: "invalid ssh key", format: "ssh", - keyFile: "../ssh/testdata/hello_world.txt", - sigFile: "../ssh/testdata/hello_world.txt.sig", + keyFile: "ssh/testdata/hello_world.txt", + sigFile: "ssh/testdata/hello_world.txt.sig", expectSuccess: false, expectValidFormat: true, }, @@ -95,7 +95,7 @@ func TestFactoryNewKey(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { - factory, err := NewArtifactFactory(PKIFormat(tc.format)) + factory, err := NewArtifactFactory(Format(tc.format)) if tc.expectValidFormat != (err == nil) { t.Fatalf("unexpected error initializing factory for %v", tc.format) } diff --git a/pkg/pki/minisign/minisign.go b/pkg/pki/minisign/minisign.go index 726dc8c63..2697d96b3 100644 --- a/pkg/pki/minisign/minisign.go +++ b/pkg/pki/minisign/minisign.go @@ -23,7 +23,6 @@ import ( "strings" minisign "github.com/jedisct1/go-minisign" - "github.com/sigstore/rekor/pkg/pki" sigsig "github.com/sigstore/sigstore/pkg/signature" ) @@ -33,7 +32,7 @@ type Signature struct { } // NewSignature creates and validates a minisign signature object -func NewSignature(r io.Reader) (pki.Signature, error) { +func NewSignature(r io.Reader) (*Signature, error) { var s Signature var inputBuffer bytes.Buffer @@ -86,7 +85,7 @@ func (s Signature) CanonicalValue() ([]byte, error) { } // Verify implements the pki.Signature interface -func (s Signature) Verify(r io.Reader, k pki.PublicKey) error { +func (s Signature) Verify(r io.Reader, k interface{}) error { if s.signature == nil { return fmt.Errorf("minisign signature has not been initialized") } @@ -112,7 +111,7 @@ type PublicKey struct { } // NewPublicKey implements the pki.PublicKey interface -func NewPublicKey(r io.Reader) (pki.PublicKey, error) { +func NewPublicKey(r io.Reader) (*PublicKey, error) { var k PublicKey var inputBuffer bytes.Buffer diff --git a/pkg/pki/pgp/pgp.go b/pkg/pki/pgp/pgp.go index 2ce9eedfd..7e3c7bba0 100644 --- a/pkg/pki/pgp/pgp.go +++ b/pkg/pki/pgp/pgp.go @@ -25,7 +25,6 @@ import ( "net/http" "github.com/go-playground/validator" - "github.com/sigstore/rekor/pkg/pki" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/packet" @@ -38,7 +37,7 @@ type Signature struct { } // NewSignature creates and validates a PGP signature object -func NewSignature(r io.Reader) (pki.Signature, error) { +func NewSignature(r io.Reader) (*Signature, error) { var s Signature var inputBuffer bytes.Buffer @@ -81,7 +80,7 @@ func NewSignature(r io.Reader) (pki.Signature, error) { } // FetchSignature implements pki.Signature interface -func FetchSignature(ctx context.Context, url string) (pki.Signature, error) { +func FetchSignature(ctx context.Context, url string) (*Signature, error) { req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, fmt.Errorf("error initializing fetch for PGP signature: %w", err) @@ -131,7 +130,7 @@ func (s Signature) CanonicalValue() ([]byte, error) { } // Verify implements the pki.Signature interface -func (s Signature) Verify(r io.Reader, k pki.PublicKey) error { +func (s Signature) Verify(r io.Reader, k interface{}) error { if len(s.signature) == 0 { return fmt.Errorf("PGP signature has not been initialized") } @@ -162,7 +161,7 @@ type PublicKey struct { } // NewPublicKey implements the pki.PublicKey interface -func NewPublicKey(r io.Reader) (pki.PublicKey, error) { +func NewPublicKey(r io.Reader) (*PublicKey, error) { var k PublicKey var inputBuffer bytes.Buffer @@ -222,7 +221,7 @@ func NewPublicKey(r io.Reader) (pki.PublicKey, error) { } // FetchPublicKey implements pki.PublicKey interface -func FetchPublicKey(ctx context.Context, url string) (pki.PublicKey, error) { +func FetchPublicKey(ctx context.Context, url string) (*PublicKey, error) { //TODO: detect if url is hkp and adjust accordingly req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { diff --git a/pkg/pki/pkcs7/pkcs7.go b/pkg/pki/pkcs7/pkcs7.go index 8d0a45958..8e842621b 100644 --- a/pkg/pki/pkcs7/pkcs7.go +++ b/pkg/pki/pkcs7/pkcs7.go @@ -29,7 +29,6 @@ import ( "strings" "github.com/sassoftware/relic/lib/pkcs7" - "github.com/sigstore/rekor/pkg/pki" ) // EmailAddressOID defined by https://oidref.com/1.2.840.113549.1.9.1 @@ -42,7 +41,7 @@ type Signature struct { } // NewSignature creates and validates an PKCS7 signature object -func NewSignature(r io.Reader) (pki.Signature, error) { +func NewSignature(r io.Reader) (*Signature, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err @@ -106,7 +105,7 @@ func (s Signature) CanonicalValue() ([]byte, error) { } // Verify implements the pki.Signature interface -func (s Signature) Verify(r io.Reader, k pki.PublicKey) error { +func (s Signature) Verify(r io.Reader, k interface{}) error { if len(*s.raw) == 0 { return fmt.Errorf("PKCS7 signature has not been initialized") } @@ -141,7 +140,7 @@ type PublicKey struct { } // NewPublicKey implements the pki.PublicKey interface -func NewPublicKey(r io.Reader) (pki.PublicKey, error) { +func NewPublicKey(r io.Reader) (*PublicKey, error) { rawPub, err := ioutil.ReadAll(r) if err != nil { return nil, err diff --git a/pkg/pki/pki.go b/pkg/pki/pki.go index 3e110467e..cca198716 100644 --- a/pkg/pki/pki.go +++ b/pkg/pki/pki.go @@ -28,5 +28,5 @@ type PublicKey interface { // Signature Generic object representing a signature (regardless of format & algorithm) type Signature interface { CanonicalValue() ([]byte, error) - Verify(r io.Reader, k PublicKey) error + Verify(r io.Reader, k interface{}) error } diff --git a/pkg/pki/ssh/ssh.go b/pkg/pki/ssh/ssh.go index 0c9c66645..faa4d6d25 100644 --- a/pkg/pki/ssh/ssh.go +++ b/pkg/pki/ssh/ssh.go @@ -20,7 +20,6 @@ import ( "io" "io/ioutil" - "github.com/sigstore/rekor/pkg/pki" "golang.org/x/crypto/ssh" ) @@ -31,7 +30,7 @@ type Signature struct { } // NewSignature creates and Validates an ssh signature object -func NewSignature(r io.Reader) (pki.Signature, error) { +func NewSignature(r io.Reader) (*Signature, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err @@ -49,7 +48,7 @@ func (s Signature) CanonicalValue() ([]byte, error) { } // Verify implements the pki.Signature interface -func (s Signature) Verify(r io.Reader, k pki.PublicKey) error { +func (s Signature) Verify(r io.Reader, k interface{}) error { if s.signature == nil { return fmt.Errorf("ssh signature has not been initialized") } @@ -76,7 +75,7 @@ type PublicKey struct { } // NewPublicKey implements the pki.PublicKey interface -func NewPublicKey(r io.Reader) (pki.PublicKey, error) { +func NewPublicKey(r io.Reader) (*PublicKey, error) { rawPub, err := ioutil.ReadAll(r) if err != nil { return nil, err diff --git a/pkg/pki/x509/x509.go b/pkg/pki/x509/x509.go index e460df6a7..1534d147d 100644 --- a/pkg/pki/x509/x509.go +++ b/pkg/pki/x509/x509.go @@ -28,7 +28,6 @@ import ( "strings" "github.com/go-playground/validator" - "github.com/sigstore/rekor/pkg/pki" "github.com/sigstore/sigstore/pkg/cryptoutils" sigsig "github.com/sigstore/sigstore/pkg/signature" ) @@ -41,7 +40,7 @@ type Signature struct { } // NewSignature creates and validates an x509 signature object -func NewSignature(r io.Reader) (pki.Signature, error) { +func NewSignature(r io.Reader) (*Signature, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err @@ -57,7 +56,7 @@ func (s Signature) CanonicalValue() ([]byte, error) { } // Verify implements the pki.Signature interface -func (s Signature) Verify(r io.Reader, k pki.PublicKey) error { +func (s Signature) Verify(r io.Reader, k interface{}) error { if len(s.signature) == 0 { //lint:ignore ST1005 X509 is proper use of term return fmt.Errorf("X509 signature has not been initialized") @@ -92,7 +91,7 @@ type cert struct { } // NewPublicKey implements the pki.PublicKey interface -func NewPublicKey(r io.Reader) (pki.PublicKey, error) { +func NewPublicKey(r io.Reader) (*PublicKey, error) { rawPub, err := ioutil.ReadAll(r) if err != nil { return nil, err diff --git a/pkg/types/alpine/apk_test.go b/pkg/types/alpine/apk_test.go index 14fcef7af..08185c1a0 100644 --- a/pkg/types/alpine/apk_test.go +++ b/pkg/types/alpine/apk_test.go @@ -44,7 +44,7 @@ func TestAlpinePackage(t *testing.T) { t.Fatalf("failed to parse public key: %v", err) } - if err = p.VerifySignature(pub.(*x509.PublicKey).CryptoPubKey()); err != nil { + if err = p.VerifySignature(pub.CryptoPubKey()); err != nil { t.Fatalf("signature verification failed: %v", err) } } diff --git a/pkg/types/alpine/v0.0.1/entry.go b/pkg/types/alpine/v0.0.1/entry.go index 825294a76..3355b0ec4 100644 --- a/pkg/types/alpine/v0.0.1/entry.go +++ b/pkg/types/alpine/v0.0.1/entry.go @@ -35,7 +35,6 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki" - pkifactory "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/pki/x509" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/alpine" @@ -161,7 +160,7 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { if v.AlpineModel.Package.Hash != nil && v.AlpineModel.Package.Hash.Value != nil { oldSHA = swag.StringValue(v.AlpineModel.Package.Hash.Value) } - artifactFactory, _ := pkifactory.NewArtifactFactory(pkifactory.X509) + artifactFactory, _ := pki.NewArtifactFactory(pki.X509) g.Go(func() error { defer hashW.Close() diff --git a/pkg/types/helm/provenance_test.go b/pkg/types/helm/provenance_test.go index a389b8bad..b126543d5 100644 --- a/pkg/types/helm/provenance_test.go +++ b/pkg/types/helm/provenance_test.go @@ -20,7 +20,7 @@ import ( "os" "testing" - "github.com/sigstore/rekor/pkg/pki/factory" + "github.com/sigstore/rekor/pkg/pki" "github.com/sigstore/rekor/pkg/pki/pgp" ) @@ -57,7 +57,7 @@ func TestProvenance(t *testing.T) { t.Fatalf("failed to parse public key: %v", err) } - artifactFactory, err := factory.NewArtifactFactory(factory.PGP) + artifactFactory, err := pki.NewArtifactFactory(pki.PGP) if err != nil { t.Fatalf("Failed to create PGP pki factory %v", err) } diff --git a/pkg/types/helm/v0.0.1/entry.go b/pkg/types/helm/v0.0.1/entry.go index 93c2f2261..93faa9407 100644 --- a/pkg/types/helm/v0.0.1/entry.go +++ b/pkg/types/helm/v0.0.1/entry.go @@ -34,7 +34,6 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki" - "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/pki/pgp" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/helm" @@ -147,7 +146,7 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { return err } - artifactFactory, err := factory.NewArtifactFactory(factory.PGP) + artifactFactory, err := pki.NewArtifactFactory(pki.PGP) if err != nil { return err } diff --git a/pkg/types/intoto/v0.0.1/entry.go b/pkg/types/intoto/v0.0.1/entry.go index 530f6b4be..5acc32daa 100644 --- a/pkg/types/intoto/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.1/entry.go @@ -38,7 +38,6 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki" - pkifactory "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/pki/x509" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/intoto" @@ -125,7 +124,7 @@ func (v *V001Entry) Unmarshal(pe models.ProposedEntry) error { } // Only support x509 signatures for intoto attestations - af, err := pkifactory.NewArtifactFactory(pkifactory.X509) + af, err := pki.NewArtifactFactory(pki.X509) if err != nil { return err } diff --git a/pkg/types/jar/v0.0.1/entry.go b/pkg/types/jar/v0.0.1/entry.go index eab4bd502..ee52e0b59 100644 --- a/pkg/types/jar/v0.0.1/entry.go +++ b/pkg/types/jar/v0.0.1/entry.go @@ -32,7 +32,6 @@ import ( "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki" - pkifactory "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/jar" "github.com/sigstore/rekor/pkg/util" @@ -180,7 +179,7 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { } v.jarObj = jarObj[0] - af, err := pkifactory.NewArtifactFactory(pkifactory.PKCS7) + af, err := pki.NewArtifactFactory(pki.PKCS7) if err != nil { return err } diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index 8596b2afe..b80fe65cb 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -35,7 +35,6 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki" - pkifactory "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/rekord" "github.com/sigstore/rekor/pkg/util" @@ -164,7 +163,7 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { if v.RekordObj.Data.Hash != nil && v.RekordObj.Data.Hash.Value != nil { oldSHA = swag.StringValue(v.RekordObj.Data.Hash.Value) } - artifactFactory, err := pkifactory.NewArtifactFactory(pkifactory.PKIFormat(v.RekordObj.Signature.Format)) + artifactFactory, err := pki.NewArtifactFactory(pki.Format(v.RekordObj.Signature.Format)) if err != nil { return err } diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index 1b256b543..595dc4d59 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -37,7 +37,6 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki" - pkifactory "github.com/sigstore/rekor/pkg/pki/factory" "github.com/sigstore/rekor/pkg/pki/pgp" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/rpm" @@ -165,7 +164,7 @@ func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { if v.RPMModel.Package.Hash != nil && v.RPMModel.Package.Hash.Value != nil { oldSHA = swag.StringValue(v.RPMModel.Package.Hash.Value) } - artifactFactory, err := pkifactory.NewArtifactFactory(pkifactory.PGP) + artifactFactory, err := pki.NewArtifactFactory(pki.PGP) if err != nil { return err } From 97f62d8dcea4693a4bf89d27d1c58cb111380e19 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Tue, 13 Jul 2021 13:16:07 -0400 Subject: [PATCH 8/8] review comments Signed-off-by: Bob Callaway --- cmd/rekor-cli/app/pflag_groups.go | 10 ++++++---- cmd/rekor-cli/app/pflags.go | 4 ++-- cmd/rekor-cli/app/search.go | 4 ++-- pkg/log/log.go | 2 +- pkg/types/alpine/alpine.go | 2 +- pkg/types/alpine/alpine_test.go | 2 +- pkg/types/alpine/v0.0.1/entry.go | 2 +- pkg/types/entries.go | 2 +- pkg/types/helm/helm.go | 2 +- pkg/types/helm/helm_test.go | 2 +- pkg/types/helm/v0.0.1/entry.go | 2 +- pkg/types/intoto/intoto.go | 2 +- pkg/types/intoto/intoto_test.go | 2 +- pkg/types/intoto/v0.0.1/entry.go | 2 +- pkg/types/jar/jar.go | 2 +- pkg/types/jar/jar_test.go | 2 +- pkg/types/jar/v0.0.1/entry.go | 2 +- pkg/types/rekord/rekord.go | 2 +- pkg/types/rekord/rekord_test.go | 2 +- pkg/types/rekord/v0.0.1/entry.go | 2 +- pkg/types/rfc3161/rfc3161.go | 2 +- pkg/types/rfc3161/rfc3161_test.go | 2 +- pkg/types/rfc3161/v0.0.1/entry.go | 2 +- pkg/types/rpm/rpm.go | 2 +- pkg/types/rpm/rpm_test.go | 2 +- pkg/types/rpm/v0.0.1/entry.go | 2 +- 26 files changed, 33 insertions(+), 31 deletions(-) diff --git a/cmd/rekor-cli/app/pflag_groups.go b/cmd/rekor-cli/app/pflag_groups.go index 1ddb45b70..a005e26c3 100644 --- a/cmd/rekor-cli/app/pflag_groups.go +++ b/cmd/rekor-cli/app/pflag_groups.go @@ -27,7 +27,7 @@ import ( "github.com/spf13/viper" ) -// addFlagToCmd adds the +// addFlagToCmd adds the specified command of a specified type to the command's flag set func addFlagToCmd(cmd *cobra.Command, required bool, flagType FlagType, flag, desc string) error { cmd.Flags().Var(NewFlagValue(flagType, ""), flag, desc) if required { @@ -36,10 +36,12 @@ func addFlagToCmd(cmd *cobra.Command, required bool, flagType FlagType, flag, de return nil } +// addLogIndexFlag adds the "log-index" command to the command's flag set func addLogIndexFlag(cmd *cobra.Command, required bool) error { return addFlagToCmd(cmd, required, logIndexFlag, "log-index", "the index of the entry in the transparency log") } +// addUUIDPFlags adds the "uuid" command to the command's flag set func addUUIDPFlags(cmd *cobra.Command, required bool) error { return addFlagToCmd(cmd, required, uuidFlag, "uuid", "UUID of entry in transparency log (if known)") } @@ -122,7 +124,7 @@ func CreatePropsFromPflags() *types.ArtifactProperties { artifactString := viper.GetString("artifact") if artifactString != "" { - if IsURL(artifactString) { + if isURL(artifactString) { props.ArtifactPath, _ = url.Parse(artifactString) } else { props.ArtifactPath = &url.URL{Path: artifactString} @@ -133,7 +135,7 @@ func CreatePropsFromPflags() *types.ArtifactProperties { signatureString := viper.GetString("signature") if signatureString != "" { - if IsURL(signatureString) { + if isURL(signatureString) { props.SignaturePath, _ = url.Parse(signatureString) } else { props.SignaturePath = &url.URL{Path: signatureString} @@ -142,7 +144,7 @@ func CreatePropsFromPflags() *types.ArtifactProperties { publicKeyString := viper.GetString("public-key") if publicKeyString != "" { - if IsURL(publicKeyString) { + if isURL(publicKeyString) { props.PublicKeyPath, _ = url.Parse(publicKeyString) } else { props.PublicKeyPath = &url.URL{Path: publicKeyString} diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index 4922057eb..0b55ad29c 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -149,8 +149,8 @@ func (b *baseValue) Set(s string) error { return nil } -// IsURL returns true if the supplied value is a valid URL and false otherwise -func IsURL(v string) bool { +// isURL returns true if the supplied value is a valid URL and false otherwise +func isURL(v string) bool { valGen := pflagValueFuncMap[urlFlag] return valGen().Set(v) == nil } diff --git a/cmd/rekor-cli/app/search.go b/cmd/rekor-cli/app/search.go index 977ac0f6f..3bd49d92c 100644 --- a/cmd/rekor-cli/app/search.go +++ b/cmd/rekor-cli/app/search.go @@ -119,7 +119,7 @@ var searchCmd = &cobra.Command{ hasher := sha256.New() var tee io.Reader - if IsURL(artifactStr) { + if isURL(artifactStr) { /* #nosec G107 */ resp, err := http.Get(artifactStr) if err != nil { @@ -165,7 +165,7 @@ var searchCmd = &cobra.Command{ return nil, fmt.Errorf("unknown pki-format %v", pkiFormat) } publicKeyStr := viper.GetString("public-key") - if IsURL(publicKeyStr) { + if isURL(publicKeyStr) { params.Query.PublicKey.URL = strfmt.URI(publicKeyStr) } else { keyBytes, err := ioutil.ReadFile(filepath.Clean(publicKeyStr)) diff --git a/pkg/log/log.go b/pkg/log/log.go index 5cc68ec70..62ad8bbfc 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -54,7 +54,7 @@ var CliLogger = createCliLogger() func createCliLogger() *zap.SugaredLogger { cfg := zap.NewDevelopmentConfig() cfg.EncoderConfig.TimeKey = "" - // cfg.EncoderConfig.LevelKey = "" + cfg.EncoderConfig.LevelKey = "" cfg.DisableCaller = true cfg.DisableStacktrace = true logger, err := cfg.Build() diff --git a/pkg/types/alpine/alpine.go b/pkg/types/alpine/alpine.go index e24925b68..2aafa48dc 100644 --- a/pkg/types/alpine/alpine.go +++ b/pkg/types/alpine/alpine.go @@ -65,7 +65,7 @@ func (bat *BaseAlpineType) CreateProposedEntry(ctx context.Context, version stri if err != nil { return nil, errors.Wrap(err, "fetching Intoto version implementation") } - return ei.CreateFromPFlags(ctx, props) + return ei.CreateFromArtifactProperties(ctx, props) } func (bat BaseAlpineType) DefaultVersion() string { diff --git a/pkg/types/alpine/alpine_test.go b/pkg/types/alpine/alpine_test.go index 6505dcb33..331306b0e 100644 --- a/pkg/types/alpine/alpine_test.go +++ b/pkg/types/alpine/alpine_test.go @@ -66,7 +66,7 @@ func (u UnmarshalTester) Attestation() (string, []byte) { return "", nil } -func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { +func (u UnmarshalTester) CreateFromArtifactProperties(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { return nil, nil } diff --git a/pkg/types/alpine/v0.0.1/entry.go b/pkg/types/alpine/v0.0.1/entry.go index 3355b0ec4..eaaf58136 100644 --- a/pkg/types/alpine/v0.0.1/entry.go +++ b/pkg/types/alpine/v0.0.1/entry.go @@ -341,7 +341,7 @@ func (v V001Entry) Attestation() (string, []byte) { return "", nil } -func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { +func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { returnVal := models.Alpine{} re := V001Entry{} diff --git a/pkg/types/entries.go b/pkg/types/entries.go index 4fd6b23ed..7eafef9f4 100644 --- a/pkg/types/entries.go +++ b/pkg/types/entries.go @@ -37,7 +37,7 @@ type EntryImpl interface { Unmarshal(e models.ProposedEntry) error // unmarshal the abstract entry into the specific struct for this versioned type Validate() error // performs any cross-field validation that is not expressed in the OpenAPI spec Attestation() (string, []byte) - CreateFromPFlags(context.Context, ArtifactProperties) (models.ProposedEntry, error) + CreateFromArtifactProperties(context.Context, ArtifactProperties) (models.ProposedEntry, error) } // EntryFactory describes a factory function that can generate structs for a specific versioned type diff --git a/pkg/types/helm/helm.go b/pkg/types/helm/helm.go index 67ceccee0..3cfbc2fe8 100644 --- a/pkg/types/helm/helm.go +++ b/pkg/types/helm/helm.go @@ -65,7 +65,7 @@ func (it *BaseHelmType) CreateProposedEntry(ctx context.Context, version string, if err != nil { return nil, errors.Wrap(err, "fetching Rekord version implementation") } - return ei.CreateFromPFlags(ctx, props) + return ei.CreateFromArtifactProperties(ctx, props) } func (it BaseHelmType) DefaultVersion() string { diff --git a/pkg/types/helm/helm_test.go b/pkg/types/helm/helm_test.go index b901e2b01..b4402b95e 100644 --- a/pkg/types/helm/helm_test.go +++ b/pkg/types/helm/helm_test.go @@ -66,7 +66,7 @@ func (u UnmarshalTester) Attestation() (string, []byte) { return "", nil } -func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { +func (u UnmarshalTester) CreateFromArtifactProperties(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { return nil, nil } diff --git a/pkg/types/helm/v0.0.1/entry.go b/pkg/types/helm/v0.0.1/entry.go index 93faa9407..fe630ba86 100644 --- a/pkg/types/helm/v0.0.1/entry.go +++ b/pkg/types/helm/v0.0.1/entry.go @@ -353,7 +353,7 @@ func (v V001Entry) Attestation() (string, []byte) { return "", nil } -func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { +func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { //TODO: how to select version of item to create returnVal := models.Helm{} re := V001Entry{} diff --git a/pkg/types/intoto/intoto.go b/pkg/types/intoto/intoto.go index c62b3606c..5016ce9d1 100644 --- a/pkg/types/intoto/intoto.go +++ b/pkg/types/intoto/intoto.go @@ -65,7 +65,7 @@ func (it *BaseIntotoType) CreateProposedEntry(ctx context.Context, version strin if err != nil { return nil, errors.Wrap(err, "fetching Intoto version implementation") } - return ei.CreateFromPFlags(ctx, props) + return ei.CreateFromArtifactProperties(ctx, props) } func (it BaseIntotoType) DefaultVersion() string { diff --git a/pkg/types/intoto/intoto_test.go b/pkg/types/intoto/intoto_test.go index 404097271..35b1d0481 100644 --- a/pkg/types/intoto/intoto_test.go +++ b/pkg/types/intoto/intoto_test.go @@ -66,7 +66,7 @@ func (u UnmarshalTester) Attestation() (string, []byte) { return "", nil } -func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { +func (u UnmarshalTester) CreateFromArtifactProperties(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { return nil, nil } diff --git a/pkg/types/intoto/v0.0.1/entry.go b/pkg/types/intoto/v0.0.1/entry.go index 5acc32daa..b1926efd4 100644 --- a/pkg/types/intoto/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.1/entry.go @@ -251,7 +251,7 @@ func (v *verifier) Verify(keyID string, data, sig []byte) error { return v.v.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data)) } -func (v V001Entry) CreateFromPFlags(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { +func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { returnVal := models.Intoto{} var err error diff --git a/pkg/types/jar/jar.go b/pkg/types/jar/jar.go index 8ea07f4f7..057080002 100644 --- a/pkg/types/jar/jar.go +++ b/pkg/types/jar/jar.go @@ -65,7 +65,7 @@ func (bjt *BaseJARType) CreateProposedEntry(ctx context.Context, version string, if err != nil { return nil, errors.Wrap(err, "fetching JAR version implementation") } - return ei.CreateFromPFlags(ctx, props) + return ei.CreateFromArtifactProperties(ctx, props) } func (bjt BaseJARType) DefaultVersion() string { diff --git a/pkg/types/jar/jar_test.go b/pkg/types/jar/jar_test.go index f49c39bf9..683a31ce5 100644 --- a/pkg/types/jar/jar_test.go +++ b/pkg/types/jar/jar_test.go @@ -65,7 +65,7 @@ func (u UnmarshalTester) Attestation() (string, []byte) { return "", nil } -func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { +func (u UnmarshalTester) CreateFromArtifactProperties(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { return nil, nil } diff --git a/pkg/types/jar/v0.0.1/entry.go b/pkg/types/jar/v0.0.1/entry.go index ee52e0b59..560ce73b8 100644 --- a/pkg/types/jar/v0.0.1/entry.go +++ b/pkg/types/jar/v0.0.1/entry.go @@ -321,7 +321,7 @@ func (v V001Entry) Attestation() (string, []byte) { return "", nil } -func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { +func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { returnVal := models.Jar{} re := V001Entry{} diff --git a/pkg/types/rekord/rekord.go b/pkg/types/rekord/rekord.go index 8ffeb022d..3708cb9d2 100644 --- a/pkg/types/rekord/rekord.go +++ b/pkg/types/rekord/rekord.go @@ -65,7 +65,7 @@ func (rt *BaseRekordType) CreateProposedEntry(ctx context.Context, version strin if err != nil { return nil, errors.Wrap(err, "fetching Rekord version implementation") } - return ei.CreateFromPFlags(ctx, props) + return ei.CreateFromArtifactProperties(ctx, props) } func (rt BaseRekordType) DefaultVersion() string { diff --git a/pkg/types/rekord/rekord_test.go b/pkg/types/rekord/rekord_test.go index 22b412db9..20b29232d 100644 --- a/pkg/types/rekord/rekord_test.go +++ b/pkg/types/rekord/rekord_test.go @@ -66,7 +66,7 @@ func (u UnmarshalTester) Attestation() (string, []byte) { return "", nil } -func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { +func (u UnmarshalTester) CreateFromArtifactProperties(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { return nil, nil } diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index b80fe65cb..66359716d 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -389,7 +389,7 @@ func (v V001Entry) Attestation() (string, []byte) { return "", nil } -func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { +func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { returnVal := models.Rekord{} re := V001Entry{} diff --git a/pkg/types/rfc3161/rfc3161.go b/pkg/types/rfc3161/rfc3161.go index 00363ffac..55882ea99 100644 --- a/pkg/types/rfc3161/rfc3161.go +++ b/pkg/types/rfc3161/rfc3161.go @@ -65,7 +65,7 @@ func (btt *BaseTimestampType) CreateProposedEntry(ctx context.Context, version s if err != nil { return nil, errors.Wrap(err, "fetching RFC3161 version implementation") } - return ei.CreateFromPFlags(ctx, props) + return ei.CreateFromArtifactProperties(ctx, props) } func (btt BaseTimestampType) DefaultVersion() string { diff --git a/pkg/types/rfc3161/rfc3161_test.go b/pkg/types/rfc3161/rfc3161_test.go index 18c0e4a39..17b447d9d 100644 --- a/pkg/types/rfc3161/rfc3161_test.go +++ b/pkg/types/rfc3161/rfc3161_test.go @@ -69,7 +69,7 @@ func (u UnmarshalTester) Unmarshal(pe models.ProposedEntry) error { func (u UnmarshalFailsTester) Attestation() (string, []byte) { return "", nil } -func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { +func (u UnmarshalTester) CreateFromArtifactProperties(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { return nil, nil } diff --git a/pkg/types/rfc3161/v0.0.1/entry.go b/pkg/types/rfc3161/v0.0.1/entry.go index 697627ba7..b22c9d189 100644 --- a/pkg/types/rfc3161/v0.0.1/entry.go +++ b/pkg/types/rfc3161/v0.0.1/entry.go @@ -194,7 +194,7 @@ func (v V001Entry) Attestation() (string, []byte) { return "", nil } -func (v V001Entry) CreateFromPFlags(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { +func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { returnVal := models.Rfc3161{} var err error diff --git a/pkg/types/rpm/rpm.go b/pkg/types/rpm/rpm.go index 043acf499..efba6cbd4 100644 --- a/pkg/types/rpm/rpm.go +++ b/pkg/types/rpm/rpm.go @@ -65,7 +65,7 @@ func (brt *BaseRPMType) CreateProposedEntry(ctx context.Context, version string, if err != nil { return nil, errors.Wrap(err, "fetching RPM version implementation") } - return ei.CreateFromPFlags(ctx, props) + return ei.CreateFromArtifactProperties(ctx, props) } func (brt BaseRPMType) DefaultVersion() string { diff --git a/pkg/types/rpm/rpm_test.go b/pkg/types/rpm/rpm_test.go index 45de75e6b..8fe48d11d 100644 --- a/pkg/types/rpm/rpm_test.go +++ b/pkg/types/rpm/rpm_test.go @@ -66,7 +66,7 @@ func (u UnmarshalTester) Attestation() (string, []byte) { return "", nil } -func (u UnmarshalTester) CreateFromPFlags(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { +func (u UnmarshalTester) CreateFromArtifactProperties(_ context.Context, _ types.ArtifactProperties) (models.ProposedEntry, error) { return nil, nil } diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index 595dc4d59..ac6d4bd78 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -363,7 +363,7 @@ func (v V001Entry) Attestation() (string, []byte) { return "", nil } -func (v V001Entry) CreateFromPFlags(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { +func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { returnVal := models.Rpm{} re := V001Entry{}