From a245498c6f0ee15403c493b8633c9278ed5b833c Mon Sep 17 00:00:00 2001 From: Mikhail Swift Date: Wed, 12 Jan 2022 20:27:43 -0500 Subject: [PATCH 1/8] add DSSE rekor type to support any DSSE envelope Adds a DSSE envelope type to rekor. If the DSSE envelope's payload is an in-toto statement the in-toto subjects will be used as indices for the envelope's rekord. If the envelope's payload is within the server's configured attestation size the payload will be stored as an attestation. Signed-off-by: Mikhail Swift Signed-off-by: pxp928 --- Makefile.swagger | 2 +- cmd/rekor-server/app/serve.go | 2 + openapi.yaml | 17 + pkg/generated/models/dsse.go | 210 ++++++++++ pkg/generated/models/dsse_schema.go | 29 ++ pkg/generated/models/dsse_v001_schema.go | 369 ++++++++++++++++++ pkg/generated/models/proposed_entry.go | 6 + pkg/generated/restapi/embedded_spec.go | 150 +++++++ pkg/types/dsse/dsse.go | 75 ++++ pkg/types/dsse/dsse_schema.json | 12 + pkg/types/dsse/dsse_test.go | 92 +++++ pkg/types/dsse/v0.0.1/dsse_v0_0_1_schema.json | 61 +++ pkg/types/dsse/v0.0.1/entry.go | 347 ++++++++++++++++ pkg/types/entries.go | 2 + tests/e2e_test.go | 102 +++++ 15 files changed, 1475 insertions(+), 1 deletion(-) create mode 100644 pkg/generated/models/dsse.go create mode 100644 pkg/generated/models/dsse_schema.go create mode 100644 pkg/generated/models/dsse_v001_schema.go create mode 100644 pkg/types/dsse/dsse.go create mode 100644 pkg/types/dsse/dsse_schema.json create mode 100644 pkg/types/dsse/dsse_test.go create mode 100644 pkg/types/dsse/v0.0.1/dsse_v0_0_1_schema.json create mode 100644 pkg/types/dsse/v0.0.1/entry.go diff --git a/Makefile.swagger b/Makefile.swagger index 1221342a1..a141816e5 100644 --- a/Makefile.swagger +++ b/Makefile.swagger @@ -1,2 +1,2 @@ # This file is generated after swagger runs as part of the build; do not edit! -SWAGGER_GEN=pkg/generated/client/entries/create_log_entry_parameters.go pkg/generated/client/entries/create_log_entry_responses.go pkg/generated/client/entries/entries_client.go pkg/generated/client/entries/get_log_entry_by_index_parameters.go pkg/generated/client/entries/get_log_entry_by_index_responses.go pkg/generated/client/entries/get_log_entry_by_uuid_parameters.go pkg/generated/client/entries/get_log_entry_by_uuid_responses.go pkg/generated/client/entries/search_log_query_parameters.go pkg/generated/client/entries/search_log_query_responses.go pkg/generated/client/index/index_client.go pkg/generated/client/index/search_index_parameters.go pkg/generated/client/index/search_index_responses.go pkg/generated/client/pubkey/get_public_key_parameters.go pkg/generated/client/pubkey/get_public_key_responses.go pkg/generated/client/pubkey/pubkey_client.go pkg/generated/client/rekor_client.go pkg/generated/client/server/get_rekor_version_parameters.go pkg/generated/client/server/get_rekor_version_responses.go pkg/generated/client/server/server_client.go pkg/generated/client/tlog/get_log_info_parameters.go pkg/generated/client/tlog/get_log_info_responses.go pkg/generated/client/tlog/get_log_proof_parameters.go pkg/generated/client/tlog/get_log_proof_responses.go pkg/generated/client/tlog/tlog_client.go pkg/generated/models/alpine.go pkg/generated/models/alpine_schema.go pkg/generated/models/alpine_v001_schema.go pkg/generated/models/consistency_proof.go pkg/generated/models/cose.go pkg/generated/models/cose_schema.go pkg/generated/models/cose_v001_schema.go pkg/generated/models/error.go pkg/generated/models/hashedrekord.go pkg/generated/models/hashedrekord_schema.go pkg/generated/models/hashedrekord_v001_schema.go pkg/generated/models/helm.go pkg/generated/models/helm_schema.go pkg/generated/models/helm_v001_schema.go pkg/generated/models/inactive_shard_log_info.go pkg/generated/models/inclusion_proof.go pkg/generated/models/intoto.go pkg/generated/models/intoto_schema.go pkg/generated/models/intoto_v001_schema.go pkg/generated/models/jar.go pkg/generated/models/jar_schema.go pkg/generated/models/jar_v001_schema.go pkg/generated/models/log_entry.go pkg/generated/models/log_info.go pkg/generated/models/proposed_entry.go pkg/generated/models/rekord.go pkg/generated/models/rekord_schema.go pkg/generated/models/rekord_v001_schema.go pkg/generated/models/rekor_version.go pkg/generated/models/rfc3161.go pkg/generated/models/rfc3161_schema.go pkg/generated/models/rfc3161_v001_schema.go pkg/generated/models/rpm.go pkg/generated/models/rpm_schema.go pkg/generated/models/rpm_v001_schema.go pkg/generated/models/search_index.go pkg/generated/models/search_log_query.go pkg/generated/models/tuf.go pkg/generated/models/tuf_schema.go pkg/generated/models/tuf_v001_schema.go pkg/generated/restapi/doc.go pkg/generated/restapi/embedded_spec.go pkg/generated/restapi/operations/entries/create_log_entry.go pkg/generated/restapi/operations/entries/create_log_entry_parameters.go pkg/generated/restapi/operations/entries/create_log_entry_responses.go pkg/generated/restapi/operations/entries/create_log_entry_urlbuilder.go pkg/generated/restapi/operations/entries/get_log_entry_by_index.go pkg/generated/restapi/operations/entries/get_log_entry_by_index_parameters.go pkg/generated/restapi/operations/entries/get_log_entry_by_index_responses.go pkg/generated/restapi/operations/entries/get_log_entry_by_index_urlbuilder.go pkg/generated/restapi/operations/entries/get_log_entry_by_uuid.go pkg/generated/restapi/operations/entries/get_log_entry_by_uuid_parameters.go pkg/generated/restapi/operations/entries/get_log_entry_by_uuid_responses.go pkg/generated/restapi/operations/entries/get_log_entry_by_uuid_urlbuilder.go pkg/generated/restapi/operations/entries/search_log_query.go pkg/generated/restapi/operations/entries/search_log_query_parameters.go pkg/generated/restapi/operations/entries/search_log_query_responses.go pkg/generated/restapi/operations/entries/search_log_query_urlbuilder.go pkg/generated/restapi/operations/index/search_index.go pkg/generated/restapi/operations/index/search_index_parameters.go pkg/generated/restapi/operations/index/search_index_responses.go pkg/generated/restapi/operations/index/search_index_urlbuilder.go pkg/generated/restapi/operations/pubkey/get_public_key.go pkg/generated/restapi/operations/pubkey/get_public_key_parameters.go pkg/generated/restapi/operations/pubkey/get_public_key_responses.go pkg/generated/restapi/operations/pubkey/get_public_key_urlbuilder.go pkg/generated/restapi/operations/rekor_server_api.go pkg/generated/restapi/operations/server/get_rekor_version.go pkg/generated/restapi/operations/server/get_rekor_version_parameters.go pkg/generated/restapi/operations/server/get_rekor_version_responses.go pkg/generated/restapi/operations/server/get_rekor_version_urlbuilder.go pkg/generated/restapi/operations/tlog/get_log_info.go pkg/generated/restapi/operations/tlog/get_log_info_parameters.go pkg/generated/restapi/operations/tlog/get_log_info_responses.go pkg/generated/restapi/operations/tlog/get_log_info_urlbuilder.go pkg/generated/restapi/operations/tlog/get_log_proof.go pkg/generated/restapi/operations/tlog/get_log_proof_parameters.go pkg/generated/restapi/operations/tlog/get_log_proof_responses.go pkg/generated/restapi/operations/tlog/get_log_proof_urlbuilder.go pkg/generated/restapi/server.go +SWAGGER_GEN=pkg/generated/client/entries/create_log_entry_parameters.go pkg/generated/client/entries/create_log_entry_responses.go pkg/generated/client/entries/entries_client.go pkg/generated/client/entries/get_log_entry_by_index_parameters.go pkg/generated/client/entries/get_log_entry_by_index_responses.go pkg/generated/client/entries/get_log_entry_by_uuid_parameters.go pkg/generated/client/entries/get_log_entry_by_uuid_responses.go pkg/generated/client/entries/search_log_query_parameters.go pkg/generated/client/entries/search_log_query_responses.go pkg/generated/client/index/index_client.go pkg/generated/client/index/search_index_parameters.go pkg/generated/client/index/search_index_responses.go pkg/generated/client/pubkey/get_public_key_parameters.go pkg/generated/client/pubkey/get_public_key_responses.go pkg/generated/client/pubkey/pubkey_client.go pkg/generated/client/rekor_client.go pkg/generated/client/server/get_rekor_version_parameters.go pkg/generated/client/server/get_rekor_version_responses.go pkg/generated/client/server/server_client.go pkg/generated/client/tlog/get_log_info_parameters.go pkg/generated/client/tlog/get_log_info_responses.go pkg/generated/client/tlog/get_log_proof_parameters.go pkg/generated/client/tlog/get_log_proof_responses.go pkg/generated/client/tlog/tlog_client.go pkg/generated/models//alpine.go pkg/generated/models//alpine_schema.go pkg/generated/models//alpine_v001_schema.go pkg/generated/models//consistency_proof.go pkg/generated/models//cose.go pkg/generated/models//cose_schema.go pkg/generated/models//cose_v001_schema.go pkg/generated/models//dsse.go pkg/generated/models//dsse_schema.go pkg/generated/models//dsse_v001_schema.go pkg/generated/models//error.go pkg/generated/models//hashedrekord.go pkg/generated/models//hashedrekord_schema.go pkg/generated/models//hashedrekord_v001_schema.go pkg/generated/models//helm.go pkg/generated/models//helm_schema.go pkg/generated/models//helm_v001_schema.go pkg/generated/models//inactive_shard_log_info.go pkg/generated/models//inclusion_proof.go pkg/generated/models//intoto.go pkg/generated/models//intoto_schema.go pkg/generated/models//intoto_v001_schema.go pkg/generated/models//jar.go pkg/generated/models//jar_schema.go pkg/generated/models//jar_v001_schema.go pkg/generated/models//log_entry.go pkg/generated/models//log_info.go pkg/generated/models//proposed_entry.go pkg/generated/models//rekord.go pkg/generated/models//rekord_schema.go pkg/generated/models//rekord_v001_schema.go pkg/generated/models//rekor_version.go pkg/generated/models//rfc3161.go pkg/generated/models//rfc3161_schema.go pkg/generated/models//rfc3161_v001_schema.go pkg/generated/models//rpm.go pkg/generated/models//rpm_schema.go pkg/generated/models//rpm_v001_schema.go pkg/generated/models//search_index.go pkg/generated/models//search_log_query.go pkg/generated/models//tuf.go pkg/generated/models//tuf_schema.go pkg/generated/models//tuf_v001_schema.go pkg/generated/restapi//doc.go pkg/generated/restapi//embedded_spec.go pkg/generated/restapi//operations/entries/create_log_entry.go pkg/generated/restapi//operations/entries/create_log_entry_parameters.go pkg/generated/restapi//operations/entries/create_log_entry_responses.go pkg/generated/restapi//operations/entries/create_log_entry_urlbuilder.go pkg/generated/restapi//operations/entries/get_log_entry_by_index.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_parameters.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_responses.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_urlbuilder.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_parameters.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_responses.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_urlbuilder.go pkg/generated/restapi//operations/entries/search_log_query.go pkg/generated/restapi//operations/entries/search_log_query_parameters.go pkg/generated/restapi//operations/entries/search_log_query_responses.go pkg/generated/restapi//operations/entries/search_log_query_urlbuilder.go pkg/generated/restapi//operations/index/search_index.go pkg/generated/restapi//operations/index/search_index_parameters.go pkg/generated/restapi//operations/index/search_index_responses.go pkg/generated/restapi//operations/index/search_index_urlbuilder.go pkg/generated/restapi//operations/pubkey/get_public_key.go pkg/generated/restapi//operations/pubkey/get_public_key_parameters.go pkg/generated/restapi//operations/pubkey/get_public_key_responses.go pkg/generated/restapi//operations/pubkey/get_public_key_urlbuilder.go pkg/generated/restapi//operations/rekor_server_api.go pkg/generated/restapi//operations/server/get_rekor_version.go pkg/generated/restapi//operations/server/get_rekor_version_parameters.go pkg/generated/restapi//operations/server/get_rekor_version_responses.go pkg/generated/restapi//operations/server/get_rekor_version_urlbuilder.go pkg/generated/restapi//operations/tlog/get_log_info.go pkg/generated/restapi//operations/tlog/get_log_info_parameters.go pkg/generated/restapi//operations/tlog/get_log_info_responses.go pkg/generated/restapi//operations/tlog/get_log_info_urlbuilder.go pkg/generated/restapi//operations/tlog/get_log_proof.go pkg/generated/restapi//operations/tlog/get_log_proof_parameters.go pkg/generated/restapi//operations/tlog/get_log_proof_responses.go pkg/generated/restapi//operations/tlog/get_log_proof_urlbuilder.go pkg/generated/restapi//server.go diff --git a/cmd/rekor-server/app/serve.go b/cmd/rekor-server/app/serve.go index b3534ef5f..6e37f9503 100644 --- a/cmd/rekor-server/app/serve.go +++ b/cmd/rekor-server/app/serve.go @@ -33,6 +33,7 @@ import ( alpine_v001 "github.com/sigstore/rekor/pkg/types/alpine/v0.0.1" "github.com/sigstore/rekor/pkg/types/cose" cose_v001 "github.com/sigstore/rekor/pkg/types/cose/v0.0.1" + "github.com/sigstore/rekor/pkg/types/dsse" hashedrekord "github.com/sigstore/rekor/pkg/types/hashedrekord" hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" "github.com/sigstore/rekor/pkg/types/helm" @@ -95,6 +96,7 @@ var serveCmd = &cobra.Command{ helm.KIND: helm_v001.APIVERSION, tuf.KIND: tuf_v001.APIVERSION, hashedrekord.KIND: hashedrekord_v001.APIVERSION, + dsse.KIND: dsse_v001.APIVERSION, } for k, v := range pluggableTypeMap { diff --git a/openapi.yaml b/openapi.yaml index 0946c93fb..26e32c17d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -410,6 +410,23 @@ definitions: - spec additionalProperties: false + dsse: + type: object + description: DSSE object + 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/dsse/dsse_schema.json' + required: + - apiVersion + - spec + additionalProperties: false + rfc3161: type: object description: RFC3161 Timestamp diff --git a/pkg/generated/models/dsse.go b/pkg/generated/models/dsse.go new file mode 100644 index 000000000..431e74b97 --- /dev/null +++ b/pkg/generated/models/dsse.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" +) + +// Dsse DSSE object +// +// swagger:model dsse +type Dsse 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 DsseSchema `json:"spec"` +} + +// Kind gets the kind of this subtype +func (m *Dsse) Kind() string { + return "dsse" +} + +// SetKind sets the kind of this subtype +func (m *Dsse) SetKind(val string) { +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *Dsse) 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 DsseSchema `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 Dsse + + 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 Dsse) 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 DsseSchema `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 dsse +func (m *Dsse) 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 *Dsse) 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 *Dsse) validateSpec(formats strfmt.Registry) error { + + if m.Spec == nil { + return errors.Required("spec", "body", nil) + } + + return nil +} + +// ContextValidate validate this dsse based on the context it is used +func (m *Dsse) 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 *Dsse) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Dsse) UnmarshalBinary(b []byte) error { + var res Dsse + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/generated/models/dsse_schema.go b/pkg/generated/models/dsse_schema.go new file mode 100644 index 000000000..fcb71c86c --- /dev/null +++ b/pkg/generated/models/dsse_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 + +// DsseSchema DSSE Schema +// +// DSSE for Rekord objects +// +// swagger:model dsseSchema +type DsseSchema interface{} diff --git a/pkg/generated/models/dsse_v001_schema.go b/pkg/generated/models/dsse_v001_schema.go new file mode 100644 index 000000000..bfaba9fe1 --- /dev/null +++ b/pkg/generated/models/dsse_v001_schema.go @@ -0,0 +1,369 @@ +// 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" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// DsseV001Schema dsse v0.0.1 Schema +// +// Schema for dsse object +// +// swagger:model dsseV001Schema +type DsseV001Schema struct { + + // payload of the envelope + // Format: byte + Payload strfmt.Base64 `json:"payload,omitempty"` + + // payload hash + PayloadHash *DsseV001SchemaPayloadHash `json:"payloadHash,omitempty"` + + // type describing the payload + // Required: true + PayloadType *string `json:"payloadType"` + + // collection of all signatures of the envelope's payload + // Required: true + // Min Items: 1 + Signatures []*DsseV001SchemaSignaturesItems0 `json:"signatures"` +} + +// Validate validates this dsse v001 schema +func (m *DsseV001Schema) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validatePayloadHash(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePayloadType(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSignatures(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *DsseV001Schema) validatePayloadHash(formats strfmt.Registry) error { + if swag.IsZero(m.PayloadHash) { // not required + return nil + } + + if m.PayloadHash != nil { + if err := m.PayloadHash.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("payloadHash") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("payloadHash") + } + return err + } + } + + return nil +} + +func (m *DsseV001Schema) validatePayloadType(formats strfmt.Registry) error { + + if err := validate.Required("payloadType", "body", m.PayloadType); err != nil { + return err + } + + return nil +} + +func (m *DsseV001Schema) validateSignatures(formats strfmt.Registry) error { + + if err := validate.Required("signatures", "body", m.Signatures); err != nil { + return err + } + + iSignaturesSize := int64(len(m.Signatures)) + + if err := validate.MinItems("signatures", "body", iSignaturesSize, 1); err != nil { + return err + } + + for i := 0; i < len(m.Signatures); i++ { + if swag.IsZero(m.Signatures[i]) { // not required + continue + } + + if m.Signatures[i] != nil { + if err := m.Signatures[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("signatures" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("signatures" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this dsse v001 schema based on the context it is used +func (m *DsseV001Schema) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidatePayloadHash(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateSignatures(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *DsseV001Schema) contextValidatePayloadHash(ctx context.Context, formats strfmt.Registry) error { + + if m.PayloadHash != nil { + if err := m.PayloadHash.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("payloadHash") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("payloadHash") + } + return err + } + } + + return nil +} + +func (m *DsseV001Schema) contextValidateSignatures(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Signatures); i++ { + + if m.Signatures[i] != nil { + if err := m.Signatures[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("signatures" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("signatures" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *DsseV001Schema) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *DsseV001Schema) UnmarshalBinary(b []byte) error { + var res DsseV001Schema + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// DsseV001SchemaPayloadHash hash of the envelope's payload after being PAE encoded +// +// swagger:model DsseV001SchemaPayloadHash +type DsseV001SchemaPayloadHash struct { + + // The hashing function used to compue the hash value + // Enum: [sha256] + Algorithm string `json:"algorithm,omitempty"` + + // The hash value of the PAE encoded payload + Value string `json:"value,omitempty"` +} + +// Validate validates this dsse v001 schema payload hash +func (m *DsseV001SchemaPayloadHash) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAlgorithm(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var dsseV001SchemaPayloadHashTypeAlgorithmPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["sha256"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + dsseV001SchemaPayloadHashTypeAlgorithmPropEnum = append(dsseV001SchemaPayloadHashTypeAlgorithmPropEnum, v) + } +} + +const ( + + // DsseV001SchemaPayloadHashAlgorithmSha256 captures enum value "sha256" + DsseV001SchemaPayloadHashAlgorithmSha256 string = "sha256" +) + +// prop value enum +func (m *DsseV001SchemaPayloadHash) validateAlgorithmEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, dsseV001SchemaPayloadHashTypeAlgorithmPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *DsseV001SchemaPayloadHash) validateAlgorithm(formats strfmt.Registry) error { + if swag.IsZero(m.Algorithm) { // not required + return nil + } + + // value enum + if err := m.validateAlgorithmEnum("payloadHash"+"."+"algorithm", "body", m.Algorithm); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this dsse v001 schema payload hash based on the context it is used +func (m *DsseV001SchemaPayloadHash) 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 *DsseV001SchemaPayloadHash) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *DsseV001SchemaPayloadHash) UnmarshalBinary(b []byte) error { + var res DsseV001SchemaPayloadHash + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// DsseV001SchemaSignaturesItems0 a signature of the envelope's payload along with the public key for the signature +// +// swagger:model DsseV001SchemaSignaturesItems0 +type DsseV001SchemaSignaturesItems0 struct { + + // optional id of the key used to create the signature + Keyid string `json:"keyid,omitempty"` + + // public key that corresponds to this signature + // Read Only: true + // Format: byte + PublicKey strfmt.Base64 `json:"publicKey,omitempty"` + + // signature of the payload + Sig string `json:"sig,omitempty"` +} + +// Validate validates this dsse v001 schema signatures items0 +func (m *DsseV001SchemaSignaturesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validate this dsse v001 schema signatures items0 based on the context it is used +func (m *DsseV001SchemaSignaturesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidatePublicKey(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *DsseV001SchemaSignaturesItems0) contextValidatePublicKey(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "publicKey", "body", strfmt.Base64(m.PublicKey)); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *DsseV001SchemaSignaturesItems0) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *DsseV001SchemaSignaturesItems0) UnmarshalBinary(b []byte) error { + var res DsseV001SchemaSignaturesItems0 + 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 6ebbf1016..0ab5970dc 100644 --- a/pkg/generated/models/proposed_entry.go +++ b/pkg/generated/models/proposed_entry.go @@ -127,6 +127,12 @@ func unmarshalProposedEntry(data []byte, consumer runtime.Consumer) (ProposedEnt return nil, err } return &result, nil + case "dsse": + var result Dsse + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "hashedrekord": var result Hashedrekord if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index ef89e89cb..10e336293 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -708,6 +708,32 @@ func init() { } ] }, + "dsse": { + "description": "DSSE object", + "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/dsse/dsse_schema.json" + } + }, + "additionalProperties": false + } + ] + }, "hashedrekord": { "description": "Hashed Rekord object", "type": "object", @@ -1536,6 +1562,44 @@ func init() { }, "readOnly": true }, + "DsseV001SchemaPayloadHash": { + "description": "hash of the envelope's payload after being PAE encoded", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compue the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value of the PAE encoded payload", + "type": "string" + } + }, + "readOnly": true + }, + "DsseV001SchemaSignaturesItems0": { + "description": "a signature of the envelope's payload along with the public key for the signature", + "type": "object", + "properties": { + "keyid": { + "description": "optional id of the key used to create the signature", + "type": "string" + }, + "publicKey": { + "description": "public key that corresponds to this signature", + "type": "string", + "format": "byte", + "readOnly": true + }, + "sig": { + "description": "signature of the payload", + "type": "string" + } + } + }, "Error": { "type": "object", "properties": { @@ -2801,6 +2865,92 @@ func init() { "$schema": "http://json-schema.org/draft-07/schema", "$id": "http://rekor.sigstore.dev/types/cose/cose_v0_0_1_schema.json" }, + "dsse": { + "description": "DSSE object", + "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/dsseSchema" + } + }, + "additionalProperties": false + } + ] + }, + "dsseSchema": { + "description": "DSSE for Rekord objects", + "type": "object", + "title": "DSSE Schema", + "oneOf": [ + { + "$ref": "#/definitions/dsseV001Schema" + } + ], + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://rekor.sigstore.dev/types/dsse/dsse_schema.json" + }, + "dsseV001Schema": { + "description": "Schema for dsse object", + "type": "object", + "title": "dsse v0.0.1 Schema", + "required": [ + "payloadType", + "signatures" + ], + "properties": { + "payload": { + "description": "payload of the envelope", + "type": "string", + "format": "byte", + "writeOnly": true + }, + "payloadHash": { + "description": "hash of the envelope's payload after being PAE encoded", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compue the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value of the PAE encoded payload", + "type": "string" + } + }, + "readOnly": true + }, + "payloadType": { + "description": "type describing the payload", + "type": "string" + }, + "signatures": { + "description": "collection of all signatures of the envelope's payload", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/DsseV001SchemaSignaturesItems0" + } + } + }, + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://rekor.sigstore.dev/types/dsse/dsse_v0_0_1_schema.json" + }, "hashedrekord": { "description": "Hashed Rekord object", "type": "object", diff --git a/pkg/types/dsse/dsse.go b/pkg/types/dsse/dsse.go new file mode 100644 index 000000000..e94c73c37 --- /dev/null +++ b/pkg/types/dsse/dsse.go @@ -0,0 +1,75 @@ +// +// Copyright 2022 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 dsse + +import ( + "context" + "errors" + "fmt" + + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/types" +) + +const ( + KIND = "dsse" +) + +type BaseDsseType struct { + types.RekorType +} + +func init() { + types.TypeMap.Store(KIND, New) +} + +func New() types.TypeImpl { + bdt := BaseDsseType{} + bdt.Kind = KIND + bdt.VersionMap = VersionMap + return &bdt +} + +var VersionMap = types.NewSemVerEntryFactoryMap() + +func (bdt BaseDsseType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImpl, error) { + if pe == nil { + return nil, errors.New("proposed entry cannot be nil") + } + + in, ok := pe.(*models.Dsse) + if !ok { + return nil, errors.New("cannot unmarshal non-DSSE types") + } + + return bdt.VersionedUnmarshal(in, *in.APIVersion) +} + +func (bdt *BaseDsseType) CreateProposedEntry(ctx context.Context, version string, props types.ArtifactProperties) (models.ProposedEntry, error) { + if version == "" { + version = bdt.DefaultVersion() + } + ei, err := bdt.VersionedUnmarshal(nil, version) + if err != nil { + return nil, fmt.Errorf("fetching DSSE version implementation, %w", err) + } + + return ei.CreateFromArtifactProperties(ctx, props) +} + +func (bdt BaseDsseType) DefaultVersion() string { + return "0.0.1" +} diff --git a/pkg/types/dsse/dsse_schema.json b/pkg/types/dsse/dsse_schema.json new file mode 100644 index 000000000..511130952 --- /dev/null +++ b/pkg/types/dsse/dsse_schema.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/dsse/dsse_schema.json", + "title": "DSSE Schema", + "description": "DSSE for Rekord objects", + "type": "object", + "oneOf": [ + { + "$ref": "v0.0.1/dsse_v0_0_1_schema.json" + } + ] +} diff --git a/pkg/types/dsse/dsse_test.go b/pkg/types/dsse/dsse_test.go new file mode 100644 index 000000000..56addd41e --- /dev/null +++ b/pkg/types/dsse/dsse_test.go @@ -0,0 +1,92 @@ +// +// Copyright 2022 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 dsse + +import ( + "errors" + "testing" + + "github.com/go-openapi/swag" + + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/types" +) + +type UnmarshalTester struct { + models.Dsse + types.BaseUnmarshalTester +} + +type UnmarshalFailsTester struct { + types.BaseUnmarshalTester +} + +func (u UnmarshalFailsTester) NewEntry() types.EntryImpl { + return &UnmarshalFailsTester{} +} + +func (u UnmarshalFailsTester) Unmarshal(pe models.ProposedEntry) error { + return errors.New("error") +} + +func TestDsseType(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.Dsse.APIVersion = swag.String("2.0.1") + brt := New() + + // version requested matches implementation in map + if _, err := brt.UnmarshalEntry(&u.Dsse); err != nil { + t.Errorf("unexpected error in Unmarshal: %v", err) + } + + // version requested fails to match implementation in map + u.Dsse.APIVersion = swag.String("1.2.2") + if _, err := brt.UnmarshalEntry(&u.Dsse); err == nil { + t.Error("unexpected success in Unmarshal for non-matching version") + } + + // error in Unmarshal call is raised appropriately + u.Dsse.APIVersion = swag.String("2.2.0") + u2 := UnmarshalFailsTester{} + _ = VersionMap.SetEntryFactory(">= 1.2.3", u2.NewEntry) + if _, err := brt.UnmarshalEntry(&u.Dsse); err == nil { + t.Error("unexpected success in Unmarshal when error is thrown") + } + + // version requested fails to match implementation in map + u.Dsse.APIVersion = swag.String("not_a_version") + if _, err := brt.UnmarshalEntry(&u.Dsse); err == nil { + t.Error("unexpected success in Unmarshal for invalid version") + } +} diff --git a/pkg/types/dsse/v0.0.1/dsse_v0_0_1_schema.json b/pkg/types/dsse/v0.0.1/dsse_v0_0_1_schema.json new file mode 100644 index 000000000..500335124 --- /dev/null +++ b/pkg/types/dsse/v0.0.1/dsse_v0_0_1_schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/dsse/dsse_v0_0_1_schema.json", + "title": "dsse v0.0.1 Schema", + "description": "Schema for dsse object", + "type": "object", + "properties": { + "payload": { + "description": "payload of the envelope", + "type": "string", + "format": "byte", + "writeOnly": true + }, + "payloadType": { + "description": "type describing the payload", + "type": "string" + }, + "payloadHash": { + "description": "hash of the envelope's payload after being PAE encoded", + "readOnly": true, + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compue the hash value", + "type": "string", + "enum": ["sha256"] + }, + "value": { + "description": "The hash value of the PAE encoded payload", + "type": "string" + } + } + }, + "signatures": { + "description": "collection of all signatures of the envelope's payload", + "type": "array", + "minItems": 1, + "items": { + "description": "a signature of the envelope's payload along with the public key for the signature", + "type": "object", + "properties": { + "keyid": { + "description": "optional id of the key used to create the signature", + "type": "string" + }, + "sig": { + "description": "signature of the payload", + "type": "string" + }, + "publicKey": { + "description": "public key that corresponds to this signature", + "type": "string", + "format": "byte", + "readOnly": true + } + } + } + } + }, + "required": ["payloadType", "signatures"] +} diff --git a/pkg/types/dsse/v0.0.1/entry.go b/pkg/types/dsse/v0.0.1/entry.go new file mode 100644 index 000000000..9b71dcbd4 --- /dev/null +++ b/pkg/types/dsse/v0.0.1/entry.go @@ -0,0 +1,347 @@ +// +// Copyright 2022 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 dsse + +import ( + "bytes" + "context" + "crypto" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/url" + "path/filepath" + + "github.com/in-toto/in-toto-golang/in_toto" + "github.com/secure-systems-lab/go-securesystemslib/dsse" + "github.com/spf13/viper" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/log" + "github.com/sigstore/rekor/pkg/pki/x509" + "github.com/sigstore/rekor/pkg/types" + rekordsse "github.com/sigstore/rekor/pkg/types/dsse" + "github.com/sigstore/sigstore/pkg/signature" +) + +const ( + APIVERSION = "0.0.1" +) + +func init() { + if err := rekordsse.VersionMap.SetEntryFactory(APIVERSION, NewEntry); err != nil { + log.Logger.Panic(err) + } +} + +type V001Entry struct { + DsseObj models.DsseV001Schema +} + +func (v V001Entry) APIVersion() string { + return APIVERSION +} + +func NewEntry() types.EntryImpl { + return &V001Entry{} +} + +func (v V001Entry) IndexKeys() ([]string, error) { + var result []string + fmt.Printf("%+v\n", v.DsseObj) + result = append(result, v.DsseObj.PayloadHash.Algorithm+":"+v.DsseObj.PayloadHash.Value) + + for _, sig := range v.DsseObj.Signatures { + keyHash := sha256.Sum256(sig.PublicKey) + result = append(result, "sha256:"+hex.EncodeToString(keyHash[:])) + } + + switch *v.DsseObj.PayloadType { + case in_toto.PayloadType: + statement, err := parseIntotoStatement(v.DsseObj.Payload) + if err != nil { + return result, err + } + + for _, s := range statement.Subject { + for alg, ds := range s.Digest { + result = append(result, alg+":"+ds) + } + } + default: + log.Logger.Infof("Cannot index payload of type: %s", *v.DsseObj.PayloadType) + } + + return result, nil +} + +func parseIntotoStatement(p []byte) (*in_toto.Statement, error) { + ps := in_toto.Statement{} + if err := json.Unmarshal(p, &ps); err != nil { + return nil, err + } + + return &ps, nil +} + +func (v *V001Entry) Unmarshal(pe models.ProposedEntry) error { + dsseModel, ok := pe.(*models.Dsse) + if !ok { + return errors.New("cannot unmarshal non DSSE v0.0.1 type") + } + + if err := types.DecodeEntry(dsseModel.Spec, &v.DsseObj); err != nil { + return err + } + + // field validation + if err := v.DsseObj.Validate(strfmt.Default); err != nil { + return err + } + + if string(v.DsseObj.Payload) == "" { + return nil + } + + env := &dsse.Envelope{ + Payload: string(v.DsseObj.Payload), + PayloadType: *v.DsseObj.PayloadType, + } + + allPubKeyBytes := make([][]byte, 0) + for _, sig := range v.DsseObj.Signatures { + env.Signatures = append(env.Signatures, dsse.Signature{ + KeyID: sig.Keyid, + Sig: string(sig.Sig), + }) + + allPubKeyBytes = append(allPubKeyBytes, sig.PublicKey) + } + + if _, err := verifyEnvelope(allPubKeyBytes, env); err != nil { + return err + } + + decodedPayload, err := base64.StdEncoding.DecodeString(string(v.DsseObj.Payload)) + if err != nil { + return fmt.Errorf("could not decode envelope payload: %w", err) + } + + paeEncodedPayload := dsse.PAE(*v.DsseObj.PayloadType, decodedPayload) + h := sha256.Sum256(paeEncodedPayload) + v.DsseObj.PayloadHash = &models.DsseV001SchemaPayloadHash{ + Algorithm: models.DsseV001SchemaPayloadHashAlgorithmSha256, + Value: hex.EncodeToString(h[:]), + } + + return nil +} + +func (v *V001Entry) Canonicalize(ctx context.Context) ([]byte, error) { + canonicalEntry := models.DsseV001Schema{ + PayloadHash: v.DsseObj.PayloadHash, + Signatures: v.DsseObj.Signatures, + PayloadType: v.DsseObj.PayloadType, + } + + model := models.Dsse{} + model.APIVersion = swag.String(APIVERSION) + model.Spec = canonicalEntry + return json.Marshal(&model) +} + +func (v *V001Entry) AttestationKey() string { + if v.DsseObj.PayloadHash != nil { + return fmt.Sprintf("%s:%s", v.DsseObj.PayloadHash.Algorithm, v.DsseObj.PayloadHash.Value) + } + + return "" +} + +func (v *V001Entry) AttestationKeyValue() (string, []byte) { + storageSize := base64.StdEncoding.DecodedLen(len(v.DsseObj.Payload)) + if storageSize > viper.GetInt("max_attestation_size") { + log.Logger.Infof("Skipping attestation storage, size %d is greater than max %d", storageSize, viper.GetInt("max_attestation_size")) + return "", nil + } + + decodedPayload, err := base64.StdEncoding.DecodeString(string(v.DsseObj.Payload)) + if err != nil { + log.Logger.Infof("Skipping attestation storage, error while decoding attestation: %v", err) + return "", nil + } + + return v.AttestationKey(), decodedPayload +} + +type verifier struct { + v signature.Verifier +} + +func (v *verifier) KeyID() (string, error) { + return "", nil +} + +func (v *verifier) Public() crypto.PublicKey { + // the dsse library uses this to generate a key ID if the KeyID function returns an empty string + // as well for the AcceptedKey return value. Unfortunately since key ids can be arbitrary, we don't + // know how to generate a matching id for the key id on the envelope's signature... + // dsse verify will skip verifiers whose key id doesn't match the signature's key id, unless it fails + // to generate one from the public key... so we trick it by returning nil ¯\_(ツ)_/¯ + return nil +} + +func (v *verifier) Verify(data, sig []byte) error { + if v.v == nil { + return errors.New("nil verifier") + } + return v.v.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data)) +} + +func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { + returnVal := models.Dsse{} + re := V001Entry{} + + artifactBytes := props.ArtifactBytes + if artifactBytes == nil { + if props.ArtifactPath == nil { + return nil, errors.New("path to artifact file must be specified") + } + if props.ArtifactPath.IsAbs() { + return nil, errors.New("dsse envelopes cannot be fetched over HTTP(S)") + } + + var err error + artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) + if err != nil { + return nil, err + } + } + + env := dsse.Envelope{} + if err := json.Unmarshal(artifactBytes, &env); err != nil { + return nil, fmt.Errorf("payload must be a valid dsse envelope: %w", err) + } + + allPubKeyBytes := make([][]byte, 0) + if props.PublicKeyBytes != nil { + allPubKeyBytes = append(allPubKeyBytes, props.PublicKeyBytes) + } + + allPubKeyBytes = append(allPubKeyBytes, props.PublicKeysBytes...) + allPubKeyPaths := make([]*url.URL, 0) + if props.PublicKeyPath != nil { + allPubKeyPaths = append(allPubKeyPaths, props.PublicKeyPath) + } + + for _, path := range allPubKeyPaths { + if path.IsAbs() { + return nil, errors.New("dsse public keys cannot be fetched over HTTP(S)") + } + + publicKeyBytes, err := ioutil.ReadFile(filepath.Clean(path.Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + + allPubKeyBytes = append(allPubKeyBytes, publicKeyBytes) + } + + keysBySig, err := verifyEnvelope(allPubKeyBytes, &env) + if err != nil { + return nil, err + } + + re.DsseObj.Payload = strfmt.Base64(env.Payload) + re.DsseObj.PayloadType = &env.PayloadType + for _, sig := range env.Signatures { + key, ok := keysBySig[sig.Sig] + if !ok { + return nil, errors.New("all signatures must have a key that verifies it") + } + + canonKey, err := key.CanonicalValue() + if err != nil { + return nil, fmt.Errorf("could not canonicize key: %w", err) + } + + keyBytes := strfmt.Base64(canonKey) + re.DsseObj.Signatures = append(re.DsseObj.Signatures, &models.DsseV001SchemaSignaturesItems0{ + Keyid: sig.KeyID, + Sig: sig.Sig, + PublicKey: keyBytes, + }) + } + + returnVal.APIVersion = swag.String(re.APIVersion()) + returnVal.Spec = re.DsseObj + return &returnVal, nil +} + +// verifyEnvelope takes in an array of possible key bytes and attempts to parse them as x509 public keys. +// it then uses these to verify the envelope and makes sure that every signature on the envelope is verified. +// it returns a map of verifiers indexed by the signature the verifier corresponds to. +func verifyEnvelope(allPubKeyBytes [][]byte, env *dsse.Envelope) (map[string]*x509.PublicKey, error) { + // generate a fake id for these keys so we can get back to the key bytes and match them to their corresponding signature + verifierBySig := make(map[string]*x509.PublicKey) + allSigs := make(map[string]struct{}) + for _, sig := range env.Signatures { + allSigs[sig.Sig] = struct{}{} + } + + for _, pubKeyBytes := range allPubKeyBytes { + key, err := x509.NewPublicKey(bytes.NewReader(pubKeyBytes)) + if err != nil { + return nil, fmt.Errorf("could not parse public key as x509: %w", err) + } + + vfr, err := signature.LoadVerifier(key.CryptoPubKey(), crypto.SHA256) + if err != nil { + return nil, fmt.Errorf("could not load verifier: %w", err) + } + + dsseVfr, err := dsse.NewEnvelopeVerifier(&verifier{ + v: vfr, + }) + + if err != nil { + return nil, fmt.Errorf("could not use public key as a dsse verifier: %w", err) + } + + accepted, err := dsseVfr.Verify(env) + if err != nil { + return nil, fmt.Errorf("could not verify envelope: %w", err) + } + + for _, accept := range accepted { + delete(allSigs, accept.Sig.Sig) + verifierBySig[accept.Sig.Sig] = key + } + } + + if len(allSigs) > 0 { + return nil, errors.New("all signatures must have a key that verifies it") + } + + return verifierBySig, nil +} diff --git a/pkg/types/entries.go b/pkg/types/entries.go index cda7f2782..63aeab0ac 100644 --- a/pkg/types/entries.go +++ b/pkg/types/entries.go @@ -154,5 +154,7 @@ type ArtifactProperties struct { SignatureBytes []byte PublicKeyPath *url.URL PublicKeyBytes []byte + PublicKeysBytes [][]byte + PublicKeysPaths []*url.URL PKIFormat string } diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 327c04762..d87c56441 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -559,6 +559,108 @@ func TestIntoto(t *testing.T) { } +func TestDsse(t *testing.T) { + td := t.TempDir() + attestationPath := filepath.Join(td, "attestation.json") + pubKeyPath := filepath.Join(td, "pub.pem") + + // Get some random data so it's unique each run + d := randomData(t, 10) + id := base64.StdEncoding.EncodeToString(d) + + it := in_toto.ProvenanceStatement{ + StatementHeader: in_toto.StatementHeader{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []in_toto.Subject{ + { + Name: "foobar", + Digest: slsa.DigestSet{ + "foo": "bar", + }, + }, + }, + }, + Predicate: slsa.ProvenancePredicate{ + Builder: slsa.ProvenanceBuilder{ + ID: "foo" + id, + }, + }, + } + + b, err := json.Marshal(it) + if err != nil { + t.Fatal(err) + } + + pb, _ := pem.Decode([]byte(ecdsaPriv)) + priv, err := x509.ParsePKCS8PrivateKey(pb.Bytes) + if err != nil { + t.Fatal(err) + } + signer, err := dsse.NewEnvelopeSigner(&IntotoSigner{ + priv: priv.(*ecdsa.PrivateKey), + }) + if err != nil { + t.Fatal(err) + } + + env, err := signer.SignPayload("application/vnd.in-toto+json", b) + if err != nil { + t.Fatal(err) + } + + eb, err := json.Marshal(env) + if err != nil { + t.Fatal(err) + } + + write(t, string(eb), attestationPath) + write(t, ecdsaPub, pubKeyPath) + + // If we do it twice, it should already exist + out := runCli(t, "upload", "--artifact", attestationPath, "--type", "dsse", "--public-key", pubKeyPath) + outputContains(t, out, "Created entry at") + uuid := getUUIDFromUploadOutput(t, out) + + out = runCli(t, "get", "--uuid", uuid, "--format=json") + g := getOut{} + if err := json.Unmarshal([]byte(out), &g); err != nil { + t.Fatal(err) + } + // The attestation should be stored at /var/run/attestations/$uuid + + got := in_toto.ProvenanceStatement{} + if err := json.Unmarshal([]byte(g.Attestation), &got); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(it, got); diff != "" { + t.Errorf("diff: %s", diff) + } + + attHash := sha256.Sum256(dsse.PAE("application/vnd.in-toto+json", []byte(g.Attestation))) + + dsseModel := &models.DsseV001Schema{} + if err := types.DecodeEntry(g.Body.(map[string]interface{})["DsseObj"], dsseModel); err != nil { + t.Errorf("could not convert body into dsse type: %v", err) + } + if dsseModel.PayloadHash == nil { + t.Errorf("could not find hash over attestation %v", dsseModel) + } + recordedPayloadHash, err := hex.DecodeString(dsseModel.PayloadHash.Value) + if err != nil { + t.Errorf("error converting attestation hash to []byte: %v", err) + } + + if !bytes.Equal(attHash[:], recordedPayloadHash) { + t.Fatal(fmt.Errorf("attestation hash %v doesnt match the payload we sent %v", hex.EncodeToString(attHash[:]), dsseModel.PayloadHash.Value)) + } + + out = runCli(t, "upload", "--artifact", attestationPath, "--type", "dsse", "--public-key", pubKeyPath) + outputContains(t, out, "Entry already exists") + +} + func TestTimestampArtifact(t *testing.T) { var out string out = runCli(t, "upload", "--type", "rfc3161", "--artifact", "test.tsr") From b40583fcf9b118fc56d73ebbde69972bbd34d194 Mon Sep 17 00:00:00 2001 From: pxp928 Date: Fri, 12 Aug 2022 13:40:06 -0400 Subject: [PATCH 2/8] added intoto v0.0.2 Signed-off-by: pxp928 --- Makefile.swagger | 2 +- cmd/rekor-cli/app/root.go | 1 + cmd/rekor-server/app/serve.go | 6 +- openapi.yaml | 17 - pkg/generated/models/dsse.go | 210 ----- pkg/generated/models/dsse_schema.go | 29 - pkg/generated/models/dsse_v001_schema.go | 369 --------- pkg/generated/models/intoto_v002_schema.go | 722 ++++++++++++++++++ pkg/generated/models/proposed_entry.go | 6 - pkg/generated/restapi/embedded_spec.go | 284 +++---- pkg/types/dsse/dsse.go | 75 -- pkg/types/dsse/dsse_schema.json | 12 - pkg/types/dsse/dsse_test.go | 92 --- pkg/types/dsse/v0.0.1/dsse_v0_0_1_schema.json | 61 -- pkg/types/entries.go | 4 +- pkg/types/intoto/intoto.go | 2 +- pkg/types/intoto/intoto_schema.json | 2 +- .../{dsse/v0.0.1 => intoto/v0.0.2}/entry.go | 206 +++-- pkg/types/intoto/v0.0.2/entry_test.go | 438 +++++++++++ .../intoto/v0.0.2/intoto_v0_0_2_schema.json | 101 +++ tests/e2e_test.go | 24 +- 21 files changed, 1531 insertions(+), 1132 deletions(-) delete mode 100644 pkg/generated/models/dsse.go delete mode 100644 pkg/generated/models/dsse_schema.go delete mode 100644 pkg/generated/models/dsse_v001_schema.go create mode 100644 pkg/generated/models/intoto_v002_schema.go delete mode 100644 pkg/types/dsse/dsse.go delete mode 100644 pkg/types/dsse/dsse_schema.json delete mode 100644 pkg/types/dsse/dsse_test.go delete mode 100644 pkg/types/dsse/v0.0.1/dsse_v0_0_1_schema.json rename pkg/types/{dsse/v0.0.1 => intoto/v0.0.2}/entry.go (55%) create mode 100644 pkg/types/intoto/v0.0.2/entry_test.go create mode 100644 pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json diff --git a/Makefile.swagger b/Makefile.swagger index a141816e5..2ea5b3bd3 100644 --- a/Makefile.swagger +++ b/Makefile.swagger @@ -1,2 +1,2 @@ # This file is generated after swagger runs as part of the build; do not edit! -SWAGGER_GEN=pkg/generated/client/entries/create_log_entry_parameters.go pkg/generated/client/entries/create_log_entry_responses.go pkg/generated/client/entries/entries_client.go pkg/generated/client/entries/get_log_entry_by_index_parameters.go pkg/generated/client/entries/get_log_entry_by_index_responses.go pkg/generated/client/entries/get_log_entry_by_uuid_parameters.go pkg/generated/client/entries/get_log_entry_by_uuid_responses.go pkg/generated/client/entries/search_log_query_parameters.go pkg/generated/client/entries/search_log_query_responses.go pkg/generated/client/index/index_client.go pkg/generated/client/index/search_index_parameters.go pkg/generated/client/index/search_index_responses.go pkg/generated/client/pubkey/get_public_key_parameters.go pkg/generated/client/pubkey/get_public_key_responses.go pkg/generated/client/pubkey/pubkey_client.go pkg/generated/client/rekor_client.go pkg/generated/client/server/get_rekor_version_parameters.go pkg/generated/client/server/get_rekor_version_responses.go pkg/generated/client/server/server_client.go pkg/generated/client/tlog/get_log_info_parameters.go pkg/generated/client/tlog/get_log_info_responses.go pkg/generated/client/tlog/get_log_proof_parameters.go pkg/generated/client/tlog/get_log_proof_responses.go pkg/generated/client/tlog/tlog_client.go pkg/generated/models//alpine.go pkg/generated/models//alpine_schema.go pkg/generated/models//alpine_v001_schema.go pkg/generated/models//consistency_proof.go pkg/generated/models//cose.go pkg/generated/models//cose_schema.go pkg/generated/models//cose_v001_schema.go pkg/generated/models//dsse.go pkg/generated/models//dsse_schema.go pkg/generated/models//dsse_v001_schema.go pkg/generated/models//error.go pkg/generated/models//hashedrekord.go pkg/generated/models//hashedrekord_schema.go pkg/generated/models//hashedrekord_v001_schema.go pkg/generated/models//helm.go pkg/generated/models//helm_schema.go pkg/generated/models//helm_v001_schema.go pkg/generated/models//inactive_shard_log_info.go pkg/generated/models//inclusion_proof.go pkg/generated/models//intoto.go pkg/generated/models//intoto_schema.go pkg/generated/models//intoto_v001_schema.go pkg/generated/models//jar.go pkg/generated/models//jar_schema.go pkg/generated/models//jar_v001_schema.go pkg/generated/models//log_entry.go pkg/generated/models//log_info.go pkg/generated/models//proposed_entry.go pkg/generated/models//rekord.go pkg/generated/models//rekord_schema.go pkg/generated/models//rekord_v001_schema.go pkg/generated/models//rekor_version.go pkg/generated/models//rfc3161.go pkg/generated/models//rfc3161_schema.go pkg/generated/models//rfc3161_v001_schema.go pkg/generated/models//rpm.go pkg/generated/models//rpm_schema.go pkg/generated/models//rpm_v001_schema.go pkg/generated/models//search_index.go pkg/generated/models//search_log_query.go pkg/generated/models//tuf.go pkg/generated/models//tuf_schema.go pkg/generated/models//tuf_v001_schema.go pkg/generated/restapi//doc.go pkg/generated/restapi//embedded_spec.go pkg/generated/restapi//operations/entries/create_log_entry.go pkg/generated/restapi//operations/entries/create_log_entry_parameters.go pkg/generated/restapi//operations/entries/create_log_entry_responses.go pkg/generated/restapi//operations/entries/create_log_entry_urlbuilder.go pkg/generated/restapi//operations/entries/get_log_entry_by_index.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_parameters.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_responses.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_urlbuilder.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_parameters.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_responses.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_urlbuilder.go pkg/generated/restapi//operations/entries/search_log_query.go pkg/generated/restapi//operations/entries/search_log_query_parameters.go pkg/generated/restapi//operations/entries/search_log_query_responses.go pkg/generated/restapi//operations/entries/search_log_query_urlbuilder.go pkg/generated/restapi//operations/index/search_index.go pkg/generated/restapi//operations/index/search_index_parameters.go pkg/generated/restapi//operations/index/search_index_responses.go pkg/generated/restapi//operations/index/search_index_urlbuilder.go pkg/generated/restapi//operations/pubkey/get_public_key.go pkg/generated/restapi//operations/pubkey/get_public_key_parameters.go pkg/generated/restapi//operations/pubkey/get_public_key_responses.go pkg/generated/restapi//operations/pubkey/get_public_key_urlbuilder.go pkg/generated/restapi//operations/rekor_server_api.go pkg/generated/restapi//operations/server/get_rekor_version.go pkg/generated/restapi//operations/server/get_rekor_version_parameters.go pkg/generated/restapi//operations/server/get_rekor_version_responses.go pkg/generated/restapi//operations/server/get_rekor_version_urlbuilder.go pkg/generated/restapi//operations/tlog/get_log_info.go pkg/generated/restapi//operations/tlog/get_log_info_parameters.go pkg/generated/restapi//operations/tlog/get_log_info_responses.go pkg/generated/restapi//operations/tlog/get_log_info_urlbuilder.go pkg/generated/restapi//operations/tlog/get_log_proof.go pkg/generated/restapi//operations/tlog/get_log_proof_parameters.go pkg/generated/restapi//operations/tlog/get_log_proof_responses.go pkg/generated/restapi//operations/tlog/get_log_proof_urlbuilder.go pkg/generated/restapi//server.go +SWAGGER_GEN=pkg/generated/client/entries/create_log_entry_parameters.go pkg/generated/client/entries/create_log_entry_responses.go pkg/generated/client/entries/entries_client.go pkg/generated/client/entries/get_log_entry_by_index_parameters.go pkg/generated/client/entries/get_log_entry_by_index_responses.go pkg/generated/client/entries/get_log_entry_by_uuid_parameters.go pkg/generated/client/entries/get_log_entry_by_uuid_responses.go pkg/generated/client/entries/search_log_query_parameters.go pkg/generated/client/entries/search_log_query_responses.go pkg/generated/client/index/index_client.go pkg/generated/client/index/search_index_parameters.go pkg/generated/client/index/search_index_responses.go pkg/generated/client/pubkey/get_public_key_parameters.go pkg/generated/client/pubkey/get_public_key_responses.go pkg/generated/client/pubkey/pubkey_client.go pkg/generated/client/rekor_client.go pkg/generated/client/server/get_rekor_version_parameters.go pkg/generated/client/server/get_rekor_version_responses.go pkg/generated/client/server/server_client.go pkg/generated/client/tlog/get_log_info_parameters.go pkg/generated/client/tlog/get_log_info_responses.go pkg/generated/client/tlog/get_log_proof_parameters.go pkg/generated/client/tlog/get_log_proof_responses.go pkg/generated/client/tlog/tlog_client.go pkg/generated/models//alpine.go pkg/generated/models//alpine_schema.go pkg/generated/models//alpine_v001_schema.go pkg/generated/models//consistency_proof.go pkg/generated/models//cose.go pkg/generated/models//cose_schema.go pkg/generated/models//cose_v001_schema.go pkg/generated/models//error.go pkg/generated/models//hashedrekord.go pkg/generated/models//hashedrekord_schema.go pkg/generated/models//hashedrekord_v001_schema.go pkg/generated/models//helm.go pkg/generated/models//helm_schema.go pkg/generated/models//helm_v001_schema.go pkg/generated/models//inactive_shard_log_info.go pkg/generated/models//inclusion_proof.go pkg/generated/models//intoto.go pkg/generated/models//intoto_schema.go pkg/generated/models//intoto_v001_schema.go pkg/generated/models//intoto_v002_schema.go pkg/generated/models//jar.go pkg/generated/models//jar_schema.go pkg/generated/models//jar_v001_schema.go pkg/generated/models//log_entry.go pkg/generated/models//log_info.go pkg/generated/models//proposed_entry.go pkg/generated/models//rekord.go pkg/generated/models//rekord_schema.go pkg/generated/models//rekord_v001_schema.go pkg/generated/models//rekor_version.go pkg/generated/models//rfc3161.go pkg/generated/models//rfc3161_schema.go pkg/generated/models//rfc3161_v001_schema.go pkg/generated/models//rpm.go pkg/generated/models//rpm_schema.go pkg/generated/models//rpm_v001_schema.go pkg/generated/models//search_index.go pkg/generated/models//search_log_query.go pkg/generated/models//tuf.go pkg/generated/models//tuf_schema.go pkg/generated/models//tuf_v001_schema.go pkg/generated/restapi//doc.go pkg/generated/restapi//embedded_spec.go pkg/generated/restapi//operations/entries/create_log_entry.go pkg/generated/restapi//operations/entries/create_log_entry_parameters.go pkg/generated/restapi//operations/entries/create_log_entry_responses.go pkg/generated/restapi//operations/entries/create_log_entry_urlbuilder.go pkg/generated/restapi//operations/entries/get_log_entry_by_index.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_parameters.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_responses.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_urlbuilder.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_parameters.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_responses.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_urlbuilder.go pkg/generated/restapi//operations/entries/search_log_query.go pkg/generated/restapi//operations/entries/search_log_query_parameters.go pkg/generated/restapi//operations/entries/search_log_query_responses.go pkg/generated/restapi//operations/entries/search_log_query_urlbuilder.go pkg/generated/restapi//operations/index/search_index.go pkg/generated/restapi//operations/index/search_index_parameters.go pkg/generated/restapi//operations/index/search_index_responses.go pkg/generated/restapi//operations/index/search_index_urlbuilder.go pkg/generated/restapi//operations/pubkey/get_public_key.go pkg/generated/restapi//operations/pubkey/get_public_key_parameters.go pkg/generated/restapi//operations/pubkey/get_public_key_responses.go pkg/generated/restapi//operations/pubkey/get_public_key_urlbuilder.go pkg/generated/restapi//operations/rekor_server_api.go pkg/generated/restapi//operations/server/get_rekor_version.go pkg/generated/restapi//operations/server/get_rekor_version_parameters.go pkg/generated/restapi//operations/server/get_rekor_version_responses.go pkg/generated/restapi//operations/server/get_rekor_version_urlbuilder.go pkg/generated/restapi//operations/tlog/get_log_info.go pkg/generated/restapi//operations/tlog/get_log_info_parameters.go pkg/generated/restapi//operations/tlog/get_log_info_responses.go pkg/generated/restapi//operations/tlog/get_log_info_urlbuilder.go pkg/generated/restapi//operations/tlog/get_log_proof.go pkg/generated/restapi//operations/tlog/get_log_proof_parameters.go pkg/generated/restapi//operations/tlog/get_log_proof_responses.go pkg/generated/restapi//operations/tlog/get_log_proof_urlbuilder.go pkg/generated/restapi//server.go diff --git a/cmd/rekor-cli/app/root.go b/cmd/rekor-cli/app/root.go index 400b9acb4..2cb38e282 100644 --- a/cmd/rekor-cli/app/root.go +++ b/cmd/rekor-cli/app/root.go @@ -32,6 +32,7 @@ import ( _ "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" _ "github.com/sigstore/rekor/pkg/types/helm/v0.0.1" _ "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" + _ "github.com/sigstore/rekor/pkg/types/intoto/v0.0.2" _ "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" diff --git a/cmd/rekor-server/app/serve.go b/cmd/rekor-server/app/serve.go index 6e37f9503..46652bb21 100644 --- a/cmd/rekor-server/app/serve.go +++ b/cmd/rekor-server/app/serve.go @@ -33,13 +33,12 @@ import ( alpine_v001 "github.com/sigstore/rekor/pkg/types/alpine/v0.0.1" "github.com/sigstore/rekor/pkg/types/cose" cose_v001 "github.com/sigstore/rekor/pkg/types/cose/v0.0.1" - "github.com/sigstore/rekor/pkg/types/dsse" hashedrekord "github.com/sigstore/rekor/pkg/types/hashedrekord" hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" "github.com/sigstore/rekor/pkg/types/helm" helm_v001 "github.com/sigstore/rekor/pkg/types/helm/v0.0.1" "github.com/sigstore/rekor/pkg/types/intoto" - intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" + intoto_v002 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.2" "github.com/sigstore/rekor/pkg/types/jar" jar_v001 "github.com/sigstore/rekor/pkg/types/jar/v0.0.1" "github.com/sigstore/rekor/pkg/types/rekord" @@ -89,14 +88,13 @@ var serveCmd = &cobra.Command{ rekord.KIND: rekord_v001.APIVERSION, rpm.KIND: rpm_v001.APIVERSION, jar.KIND: jar_v001.APIVERSION, - intoto.KIND: intoto_v001.APIVERSION, + intoto.KIND: intoto_v002.APIVERSION, cose.KIND: cose_v001.APIVERSION, rfc3161.KIND: rfc3161_v001.APIVERSION, alpine.KIND: alpine_v001.APIVERSION, helm.KIND: helm_v001.APIVERSION, tuf.KIND: tuf_v001.APIVERSION, hashedrekord.KIND: hashedrekord_v001.APIVERSION, - dsse.KIND: dsse_v001.APIVERSION, } for k, v := range pluggableTypeMap { diff --git a/openapi.yaml b/openapi.yaml index 26e32c17d..0946c93fb 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -410,23 +410,6 @@ definitions: - spec additionalProperties: false - dsse: - type: object - description: DSSE object - 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/dsse/dsse_schema.json' - required: - - apiVersion - - spec - additionalProperties: false - rfc3161: type: object description: RFC3161 Timestamp diff --git a/pkg/generated/models/dsse.go b/pkg/generated/models/dsse.go deleted file mode 100644 index 431e74b97..000000000 --- a/pkg/generated/models/dsse.go +++ /dev/null @@ -1,210 +0,0 @@ -// 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" -) - -// Dsse DSSE object -// -// swagger:model dsse -type Dsse 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 DsseSchema `json:"spec"` -} - -// Kind gets the kind of this subtype -func (m *Dsse) Kind() string { - return "dsse" -} - -// SetKind sets the kind of this subtype -func (m *Dsse) SetKind(val string) { -} - -// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure -func (m *Dsse) 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 DsseSchema `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 Dsse - - 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 Dsse) 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 DsseSchema `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 dsse -func (m *Dsse) 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 *Dsse) 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 *Dsse) validateSpec(formats strfmt.Registry) error { - - if m.Spec == nil { - return errors.Required("spec", "body", nil) - } - - return nil -} - -// ContextValidate validate this dsse based on the context it is used -func (m *Dsse) 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 *Dsse) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *Dsse) UnmarshalBinary(b []byte) error { - var res Dsse - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/pkg/generated/models/dsse_schema.go b/pkg/generated/models/dsse_schema.go deleted file mode 100644 index fcb71c86c..000000000 --- a/pkg/generated/models/dsse_schema.go +++ /dev/null @@ -1,29 +0,0 @@ -// 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 - -// DsseSchema DSSE Schema -// -// DSSE for Rekord objects -// -// swagger:model dsseSchema -type DsseSchema interface{} diff --git a/pkg/generated/models/dsse_v001_schema.go b/pkg/generated/models/dsse_v001_schema.go deleted file mode 100644 index bfaba9fe1..000000000 --- a/pkg/generated/models/dsse_v001_schema.go +++ /dev/null @@ -1,369 +0,0 @@ -// 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" - "strconv" - - "github.com/go-openapi/errors" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" -) - -// DsseV001Schema dsse v0.0.1 Schema -// -// Schema for dsse object -// -// swagger:model dsseV001Schema -type DsseV001Schema struct { - - // payload of the envelope - // Format: byte - Payload strfmt.Base64 `json:"payload,omitempty"` - - // payload hash - PayloadHash *DsseV001SchemaPayloadHash `json:"payloadHash,omitempty"` - - // type describing the payload - // Required: true - PayloadType *string `json:"payloadType"` - - // collection of all signatures of the envelope's payload - // Required: true - // Min Items: 1 - Signatures []*DsseV001SchemaSignaturesItems0 `json:"signatures"` -} - -// Validate validates this dsse v001 schema -func (m *DsseV001Schema) Validate(formats strfmt.Registry) error { - var res []error - - if err := m.validatePayloadHash(formats); err != nil { - res = append(res, err) - } - - if err := m.validatePayloadType(formats); err != nil { - res = append(res, err) - } - - if err := m.validateSignatures(formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *DsseV001Schema) validatePayloadHash(formats strfmt.Registry) error { - if swag.IsZero(m.PayloadHash) { // not required - return nil - } - - if m.PayloadHash != nil { - if err := m.PayloadHash.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("payloadHash") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("payloadHash") - } - return err - } - } - - return nil -} - -func (m *DsseV001Schema) validatePayloadType(formats strfmt.Registry) error { - - if err := validate.Required("payloadType", "body", m.PayloadType); err != nil { - return err - } - - return nil -} - -func (m *DsseV001Schema) validateSignatures(formats strfmt.Registry) error { - - if err := validate.Required("signatures", "body", m.Signatures); err != nil { - return err - } - - iSignaturesSize := int64(len(m.Signatures)) - - if err := validate.MinItems("signatures", "body", iSignaturesSize, 1); err != nil { - return err - } - - for i := 0; i < len(m.Signatures); i++ { - if swag.IsZero(m.Signatures[i]) { // not required - continue - } - - if m.Signatures[i] != nil { - if err := m.Signatures[i].Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("signatures" + "." + strconv.Itoa(i)) - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("signatures" + "." + strconv.Itoa(i)) - } - return err - } - } - - } - - return nil -} - -// ContextValidate validate this dsse v001 schema based on the context it is used -func (m *DsseV001Schema) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - var res []error - - if err := m.contextValidatePayloadHash(ctx, formats); err != nil { - res = append(res, err) - } - - if err := m.contextValidateSignatures(ctx, formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *DsseV001Schema) contextValidatePayloadHash(ctx context.Context, formats strfmt.Registry) error { - - if m.PayloadHash != nil { - if err := m.PayloadHash.ContextValidate(ctx, formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("payloadHash") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("payloadHash") - } - return err - } - } - - return nil -} - -func (m *DsseV001Schema) contextValidateSignatures(ctx context.Context, formats strfmt.Registry) error { - - for i := 0; i < len(m.Signatures); i++ { - - if m.Signatures[i] != nil { - if err := m.Signatures[i].ContextValidate(ctx, formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("signatures" + "." + strconv.Itoa(i)) - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("signatures" + "." + strconv.Itoa(i)) - } - return err - } - } - - } - - return nil -} - -// MarshalBinary interface implementation -func (m *DsseV001Schema) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *DsseV001Schema) UnmarshalBinary(b []byte) error { - var res DsseV001Schema - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} - -// DsseV001SchemaPayloadHash hash of the envelope's payload after being PAE encoded -// -// swagger:model DsseV001SchemaPayloadHash -type DsseV001SchemaPayloadHash struct { - - // The hashing function used to compue the hash value - // Enum: [sha256] - Algorithm string `json:"algorithm,omitempty"` - - // The hash value of the PAE encoded payload - Value string `json:"value,omitempty"` -} - -// Validate validates this dsse v001 schema payload hash -func (m *DsseV001SchemaPayloadHash) Validate(formats strfmt.Registry) error { - var res []error - - if err := m.validateAlgorithm(formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -var dsseV001SchemaPayloadHashTypeAlgorithmPropEnum []interface{} - -func init() { - var res []string - if err := json.Unmarshal([]byte(`["sha256"]`), &res); err != nil { - panic(err) - } - for _, v := range res { - dsseV001SchemaPayloadHashTypeAlgorithmPropEnum = append(dsseV001SchemaPayloadHashTypeAlgorithmPropEnum, v) - } -} - -const ( - - // DsseV001SchemaPayloadHashAlgorithmSha256 captures enum value "sha256" - DsseV001SchemaPayloadHashAlgorithmSha256 string = "sha256" -) - -// prop value enum -func (m *DsseV001SchemaPayloadHash) validateAlgorithmEnum(path, location string, value string) error { - if err := validate.EnumCase(path, location, value, dsseV001SchemaPayloadHashTypeAlgorithmPropEnum, true); err != nil { - return err - } - return nil -} - -func (m *DsseV001SchemaPayloadHash) validateAlgorithm(formats strfmt.Registry) error { - if swag.IsZero(m.Algorithm) { // not required - return nil - } - - // value enum - if err := m.validateAlgorithmEnum("payloadHash"+"."+"algorithm", "body", m.Algorithm); err != nil { - return err - } - - return nil -} - -// ContextValidate validate this dsse v001 schema payload hash based on the context it is used -func (m *DsseV001SchemaPayloadHash) 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 *DsseV001SchemaPayloadHash) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *DsseV001SchemaPayloadHash) UnmarshalBinary(b []byte) error { - var res DsseV001SchemaPayloadHash - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} - -// DsseV001SchemaSignaturesItems0 a signature of the envelope's payload along with the public key for the signature -// -// swagger:model DsseV001SchemaSignaturesItems0 -type DsseV001SchemaSignaturesItems0 struct { - - // optional id of the key used to create the signature - Keyid string `json:"keyid,omitempty"` - - // public key that corresponds to this signature - // Read Only: true - // Format: byte - PublicKey strfmt.Base64 `json:"publicKey,omitempty"` - - // signature of the payload - Sig string `json:"sig,omitempty"` -} - -// Validate validates this dsse v001 schema signatures items0 -func (m *DsseV001SchemaSignaturesItems0) Validate(formats strfmt.Registry) error { - return nil -} - -// ContextValidate validate this dsse v001 schema signatures items0 based on the context it is used -func (m *DsseV001SchemaSignaturesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - var res []error - - if err := m.contextValidatePublicKey(ctx, formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *DsseV001SchemaSignaturesItems0) contextValidatePublicKey(ctx context.Context, formats strfmt.Registry) error { - - if err := validate.ReadOnly(ctx, "publicKey", "body", strfmt.Base64(m.PublicKey)); err != nil { - return err - } - - return nil -} - -// MarshalBinary interface implementation -func (m *DsseV001SchemaSignaturesItems0) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *DsseV001SchemaSignaturesItems0) UnmarshalBinary(b []byte) error { - var res DsseV001SchemaSignaturesItems0 - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/pkg/generated/models/intoto_v002_schema.go b/pkg/generated/models/intoto_v002_schema.go new file mode 100644 index 000000000..06ddfd59f --- /dev/null +++ b/pkg/generated/models/intoto_v002_schema.go @@ -0,0 +1,722 @@ +// 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" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// IntotoV002Schema intoto v0.0.2 Schema +// +// Schema for intoto object +// +// swagger:model intotoV002Schema +type IntotoV002Schema struct { + + // content + // Required: true + Content *IntotoV002SchemaContent `json:"content"` +} + +// Validate validates this intoto v002 schema +func (m *IntotoV002Schema) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateContent(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *IntotoV002Schema) validateContent(formats strfmt.Registry) error { + + if err := validate.Required("content", "body", m.Content); err != nil { + return err + } + + if m.Content != nil { + if err := m.Content.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("content") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("content") + } + return err + } + } + + return nil +} + +// ContextValidate validate this intoto v002 schema based on the context it is used +func (m *IntotoV002Schema) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateContent(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *IntotoV002Schema) contextValidateContent(ctx context.Context, formats strfmt.Registry) error { + + if m.Content != nil { + if err := m.Content.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("content") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("content") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *IntotoV002Schema) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *IntotoV002Schema) UnmarshalBinary(b []byte) error { + var res IntotoV002Schema + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// IntotoV002SchemaContent intoto v002 schema content +// +// swagger:model IntotoV002SchemaContent +type IntotoV002SchemaContent struct { + + // envelope + Envelope *IntotoV002SchemaContentEnvelope `json:"envelope,omitempty"` + + // hash + Hash *IntotoV002SchemaContentHash `json:"hash,omitempty"` + + // payload hash + PayloadHash *IntotoV002SchemaContentPayloadHash `json:"payloadHash,omitempty"` +} + +// Validate validates this intoto v002 schema content +func (m *IntotoV002SchemaContent) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateEnvelope(formats); err != nil { + res = append(res, err) + } + + if err := m.validateHash(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePayloadHash(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *IntotoV002SchemaContent) validateEnvelope(formats strfmt.Registry) error { + if swag.IsZero(m.Envelope) { // not required + return nil + } + + if m.Envelope != nil { + if err := m.Envelope.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("content" + "." + "envelope") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("content" + "." + "envelope") + } + return err + } + } + + return nil +} + +func (m *IntotoV002SchemaContent) 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("content" + "." + "hash") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("content" + "." + "hash") + } + return err + } + } + + return nil +} + +func (m *IntotoV002SchemaContent) validatePayloadHash(formats strfmt.Registry) error { + if swag.IsZero(m.PayloadHash) { // not required + return nil + } + + if m.PayloadHash != nil { + if err := m.PayloadHash.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("content" + "." + "payloadHash") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("content" + "." + "payloadHash") + } + return err + } + } + + return nil +} + +// ContextValidate validate this intoto v002 schema content based on the context it is used +func (m *IntotoV002SchemaContent) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateEnvelope(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateHash(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidatePayloadHash(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *IntotoV002SchemaContent) contextValidateEnvelope(ctx context.Context, formats strfmt.Registry) error { + + if m.Envelope != nil { + if err := m.Envelope.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("content" + "." + "envelope") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("content" + "." + "envelope") + } + return err + } + } + + return nil +} + +func (m *IntotoV002SchemaContent) 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("content" + "." + "hash") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("content" + "." + "hash") + } + return err + } + } + + return nil +} + +func (m *IntotoV002SchemaContent) contextValidatePayloadHash(ctx context.Context, formats strfmt.Registry) error { + + if m.PayloadHash != nil { + if err := m.PayloadHash.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("content" + "." + "payloadHash") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("content" + "." + "payloadHash") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *IntotoV002SchemaContent) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *IntotoV002SchemaContent) UnmarshalBinary(b []byte) error { + var res IntotoV002SchemaContent + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// IntotoV002SchemaContentEnvelope dsse envelope +// +// swagger:model IntotoV002SchemaContentEnvelope +type IntotoV002SchemaContentEnvelope struct { + + // payload of the envelope + Payload string `json:"payload,omitempty"` + + // type describing the payload + // Required: true + PayloadType *string `json:"payloadType"` + + // collection of all signatures of the envelope's payload + // Required: true + // Min Items: 1 + Signatures []*IntotoV002SchemaContentEnvelopeSignaturesItems0 `json:"signatures"` +} + +// Validate validates this intoto v002 schema content envelope +func (m *IntotoV002SchemaContentEnvelope) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validatePayloadType(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSignatures(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *IntotoV002SchemaContentEnvelope) validatePayloadType(formats strfmt.Registry) error { + + if err := validate.Required("content"+"."+"envelope"+"."+"payloadType", "body", m.PayloadType); err != nil { + return err + } + + return nil +} + +func (m *IntotoV002SchemaContentEnvelope) validateSignatures(formats strfmt.Registry) error { + + if err := validate.Required("content"+"."+"envelope"+"."+"signatures", "body", m.Signatures); err != nil { + return err + } + + iSignaturesSize := int64(len(m.Signatures)) + + if err := validate.MinItems("content"+"."+"envelope"+"."+"signatures", "body", iSignaturesSize, 1); err != nil { + return err + } + + for i := 0; i < len(m.Signatures); i++ { + if swag.IsZero(m.Signatures[i]) { // not required + continue + } + + if m.Signatures[i] != nil { + if err := m.Signatures[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("content" + "." + "envelope" + "." + "signatures" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("content" + "." + "envelope" + "." + "signatures" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this intoto v002 schema content envelope based on the context it is used +func (m *IntotoV002SchemaContentEnvelope) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateSignatures(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *IntotoV002SchemaContentEnvelope) contextValidateSignatures(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Signatures); i++ { + + if m.Signatures[i] != nil { + if err := m.Signatures[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("content" + "." + "envelope" + "." + "signatures" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("content" + "." + "envelope" + "." + "signatures" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *IntotoV002SchemaContentEnvelope) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *IntotoV002SchemaContentEnvelope) UnmarshalBinary(b []byte) error { + var res IntotoV002SchemaContentEnvelope + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// IntotoV002SchemaContentEnvelopeSignaturesItems0 a signature of the envelope's payload along with the public key for the signature +// +// swagger:model IntotoV002SchemaContentEnvelopeSignaturesItems0 +type IntotoV002SchemaContentEnvelopeSignaturesItems0 struct { + + // optional id of the key used to create the signature + Keyid string `json:"keyid,omitempty"` + + // public key that corresponds to this signature + // Read Only: true + // Format: byte + PublicKey strfmt.Base64 `json:"publicKey,omitempty"` + + // signature of the payload + Sig string `json:"sig,omitempty"` +} + +// Validate validates this intoto v002 schema content envelope signatures items0 +func (m *IntotoV002SchemaContentEnvelopeSignaturesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validate this intoto v002 schema content envelope signatures items0 based on the context it is used +func (m *IntotoV002SchemaContentEnvelopeSignaturesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidatePublicKey(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *IntotoV002SchemaContentEnvelopeSignaturesItems0) contextValidatePublicKey(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "publicKey", "body", strfmt.Base64(m.PublicKey)); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *IntotoV002SchemaContentEnvelopeSignaturesItems0) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *IntotoV002SchemaContentEnvelopeSignaturesItems0) UnmarshalBinary(b []byte) error { + var res IntotoV002SchemaContentEnvelopeSignaturesItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// IntotoV002SchemaContentHash Specifies the hash algorithm and value encompassing the entire signed envelope +// +// swagger:model IntotoV002SchemaContentHash +type IntotoV002SchemaContentHash struct { + + // The hashing function used to compute the hash value + // Required: true + // Enum: [sha256] + Algorithm *string `json:"algorithm"` + + // The hash value for the archive + // Required: true + Value *string `json:"value"` +} + +// Validate validates this intoto v002 schema content hash +func (m *IntotoV002SchemaContentHash) 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 intotoV002SchemaContentHashTypeAlgorithmPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["sha256"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + intotoV002SchemaContentHashTypeAlgorithmPropEnum = append(intotoV002SchemaContentHashTypeAlgorithmPropEnum, v) + } +} + +const ( + + // IntotoV002SchemaContentHashAlgorithmSha256 captures enum value "sha256" + IntotoV002SchemaContentHashAlgorithmSha256 string = "sha256" +) + +// prop value enum +func (m *IntotoV002SchemaContentHash) validateAlgorithmEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, intotoV002SchemaContentHashTypeAlgorithmPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *IntotoV002SchemaContentHash) validateAlgorithm(formats strfmt.Registry) error { + + if err := validate.Required("content"+"."+"hash"+"."+"algorithm", "body", m.Algorithm); err != nil { + return err + } + + // value enum + if err := m.validateAlgorithmEnum("content"+"."+"hash"+"."+"algorithm", "body", *m.Algorithm); err != nil { + return err + } + + return nil +} + +func (m *IntotoV002SchemaContentHash) validateValue(formats strfmt.Registry) error { + + if err := validate.Required("content"+"."+"hash"+"."+"value", "body", m.Value); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this intoto v002 schema content hash based on the context it is used +func (m *IntotoV002SchemaContentHash) 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 *IntotoV002SchemaContentHash) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *IntotoV002SchemaContentHash) UnmarshalBinary(b []byte) error { + var res IntotoV002SchemaContentHash + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// IntotoV002SchemaContentPayloadHash Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded +// +// swagger:model IntotoV002SchemaContentPayloadHash +type IntotoV002SchemaContentPayloadHash struct { + + // The hashing function used to compute the hash value + // Required: true + // Enum: [sha256] + Algorithm *string `json:"algorithm"` + + // The hash value of the PAE encoded payload + // Required: true + Value *string `json:"value"` +} + +// Validate validates this intoto v002 schema content payload hash +func (m *IntotoV002SchemaContentPayloadHash) 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 intotoV002SchemaContentPayloadHashTypeAlgorithmPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["sha256"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + intotoV002SchemaContentPayloadHashTypeAlgorithmPropEnum = append(intotoV002SchemaContentPayloadHashTypeAlgorithmPropEnum, v) + } +} + +const ( + + // IntotoV002SchemaContentPayloadHashAlgorithmSha256 captures enum value "sha256" + IntotoV002SchemaContentPayloadHashAlgorithmSha256 string = "sha256" +) + +// prop value enum +func (m *IntotoV002SchemaContentPayloadHash) validateAlgorithmEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, intotoV002SchemaContentPayloadHashTypeAlgorithmPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *IntotoV002SchemaContentPayloadHash) validateAlgorithm(formats strfmt.Registry) error { + + if err := validate.Required("content"+"."+"payloadHash"+"."+"algorithm", "body", m.Algorithm); err != nil { + return err + } + + // value enum + if err := m.validateAlgorithmEnum("content"+"."+"payloadHash"+"."+"algorithm", "body", *m.Algorithm); err != nil { + return err + } + + return nil +} + +func (m *IntotoV002SchemaContentPayloadHash) validateValue(formats strfmt.Registry) error { + + if err := validate.Required("content"+"."+"payloadHash"+"."+"value", "body", m.Value); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this intoto v002 schema content payload hash based on the context it is used +func (m *IntotoV002SchemaContentPayloadHash) 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 *IntotoV002SchemaContentPayloadHash) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *IntotoV002SchemaContentPayloadHash) UnmarshalBinary(b []byte) error { + var res IntotoV002SchemaContentPayloadHash + 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 0ab5970dc..6ebbf1016 100644 --- a/pkg/generated/models/proposed_entry.go +++ b/pkg/generated/models/proposed_entry.go @@ -127,12 +127,6 @@ func unmarshalProposedEntry(data []byte, consumer runtime.Consumer) (ProposedEnt return nil, err } return &result, nil - case "dsse": - var result Dsse - if err := consumer.Consume(buf2, &result); err != nil { - return nil, err - } - return &result, nil case "hashedrekord": var result Hashedrekord if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index 10e336293..9ca2baea7 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -708,32 +708,6 @@ func init() { } ] }, - "dsse": { - "description": "DSSE object", - "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/dsse/dsse_schema.json" - } - }, - "additionalProperties": false - } - ] - }, "hashedrekord": { "description": "Hashed Rekord object", "type": "object", @@ -1562,44 +1536,6 @@ func init() { }, "readOnly": true }, - "DsseV001SchemaPayloadHash": { - "description": "hash of the envelope's payload after being PAE encoded", - "type": "object", - "properties": { - "algorithm": { - "description": "The hashing function used to compue the hash value", - "type": "string", - "enum": [ - "sha256" - ] - }, - "value": { - "description": "The hash value of the PAE encoded payload", - "type": "string" - } - }, - "readOnly": true - }, - "DsseV001SchemaSignaturesItems0": { - "description": "a signature of the envelope's payload along with the public key for the signature", - "type": "object", - "properties": { - "keyid": { - "description": "optional id of the key used to create the signature", - "type": "string" - }, - "publicKey": { - "description": "public key that corresponds to this signature", - "type": "string", - "format": "byte", - "readOnly": true - }, - "sig": { - "description": "signature of the payload", - "type": "string" - } - } - }, "Error": { "type": "object", "properties": { @@ -1921,12 +1857,35 @@ func init() { } } }, - "IntotoV001SchemaContent": { + "IntotoV002SchemaContent": { "type": "object", "properties": { "envelope": { - "description": "envelope", - "type": "string", + "description": "dsse envelope", + "type": "object", + "required": [ + "payloadType", + "signatures" + ], + "properties": { + "payload": { + "description": "payload of the envelope", + "type": "string", + "writeOnly": true + }, + "payloadType": { + "description": "type describing the payload", + "type": "string" + }, + "signatures": { + "description": "collection of all signatures of the envelope's payload", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/IntotoV002SchemaContentEnvelopeSignaturesItems0" + } + } + }, "writeOnly": true }, "hash": { @@ -1952,7 +1911,7 @@ func init() { "readOnly": true }, "payloadHash": { - "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", + "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", "type": "object", "required": [ "algorithm", @@ -1967,7 +1926,7 @@ func init() { ] }, "value": { - "description": "The hash value for the envelope's payload", + "description": "The hash value of the PAE encoded payload", "type": "string" } }, @@ -1975,7 +1934,55 @@ func init() { } } }, - "IntotoV001SchemaContentHash": { + "IntotoV002SchemaContentEnvelope": { + "description": "dsse envelope", + "type": "object", + "required": [ + "payloadType", + "signatures" + ], + "properties": { + "payload": { + "description": "payload of the envelope", + "type": "string", + "writeOnly": true + }, + "payloadType": { + "description": "type describing the payload", + "type": "string" + }, + "signatures": { + "description": "collection of all signatures of the envelope's payload", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/IntotoV002SchemaContentEnvelopeSignaturesItems0" + } + } + }, + "writeOnly": true + }, + "IntotoV002SchemaContentEnvelopeSignaturesItems0": { + "description": "a signature of the envelope's payload along with the public key for the signature", + "type": "object", + "properties": { + "keyid": { + "description": "optional id of the key used to create the signature", + "type": "string" + }, + "publicKey": { + "description": "public key that corresponds to this signature", + "type": "string", + "format": "byte", + "readOnly": true + }, + "sig": { + "description": "signature of the payload", + "type": "string" + } + } + }, + "IntotoV002SchemaContentHash": { "description": "Specifies the hash algorithm and value encompassing the entire signed envelope", "type": "object", "required": [ @@ -1997,8 +2004,8 @@ func init() { }, "readOnly": true }, - "IntotoV001SchemaContentPayloadHash": { - "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", + "IntotoV002SchemaContentPayloadHash": { + "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", "type": "object", "required": [ "algorithm", @@ -2013,7 +2020,7 @@ func init() { ] }, "value": { - "description": "The hash value for the envelope's payload", + "description": "The hash value of the PAE encoded payload", "type": "string" } }, @@ -2865,92 +2872,6 @@ func init() { "$schema": "http://json-schema.org/draft-07/schema", "$id": "http://rekor.sigstore.dev/types/cose/cose_v0_0_1_schema.json" }, - "dsse": { - "description": "DSSE object", - "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/dsseSchema" - } - }, - "additionalProperties": false - } - ] - }, - "dsseSchema": { - "description": "DSSE for Rekord objects", - "type": "object", - "title": "DSSE Schema", - "oneOf": [ - { - "$ref": "#/definitions/dsseV001Schema" - } - ], - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "http://rekor.sigstore.dev/types/dsse/dsse_schema.json" - }, - "dsseV001Schema": { - "description": "Schema for dsse object", - "type": "object", - "title": "dsse v0.0.1 Schema", - "required": [ - "payloadType", - "signatures" - ], - "properties": { - "payload": { - "description": "payload of the envelope", - "type": "string", - "format": "byte", - "writeOnly": true - }, - "payloadHash": { - "description": "hash of the envelope's payload after being PAE encoded", - "type": "object", - "properties": { - "algorithm": { - "description": "The hashing function used to compue the hash value", - "type": "string", - "enum": [ - "sha256" - ] - }, - "value": { - "description": "The hash value of the PAE encoded payload", - "type": "string" - } - }, - "readOnly": true - }, - "payloadType": { - "description": "type describing the payload", - "type": "string" - }, - "signatures": { - "description": "collection of all signatures of the envelope's payload", - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/DsseV001SchemaSignaturesItems0" - } - } - }, - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "http://rekor.sigstore.dev/types/dsse/dsse_v0_0_1_schema.json" - }, "hashedrekord": { "description": "Hashed Rekord object", "type": "object", @@ -3215,18 +3136,17 @@ func init() { "title": "Intoto Schema", "oneOf": [ { - "$ref": "#/definitions/intotoV001Schema" + "$ref": "#/definitions/intotoV002Schema" } ], "$schema": "http://json-schema.org/draft-07/schema", "$id": "http://rekor.sigstore.dev/types/intoto/intoto_schema.json" }, - "intotoV001Schema": { + "intotoV002Schema": { "description": "Schema for intoto object", "type": "object", - "title": "intoto v0.0.1 Schema", + "title": "intoto v0.0.2 Schema", "required": [ - "publicKey", "content" ], "properties": { @@ -3234,8 +3154,31 @@ func init() { "type": "object", "properties": { "envelope": { - "description": "envelope", - "type": "string", + "description": "dsse envelope", + "type": "object", + "required": [ + "payloadType", + "signatures" + ], + "properties": { + "payload": { + "description": "payload of the envelope", + "type": "string", + "writeOnly": true + }, + "payloadType": { + "description": "type describing the payload", + "type": "string" + }, + "signatures": { + "description": "collection of all signatures of the envelope's payload", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/IntotoV002SchemaContentEnvelopeSignaturesItems0" + } + } + }, "writeOnly": true }, "hash": { @@ -3261,7 +3204,7 @@ func init() { "readOnly": true }, "payloadHash": { - "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", + "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", "type": "object", "required": [ "algorithm", @@ -3276,22 +3219,17 @@ func init() { ] }, "value": { - "description": "The hash value for the envelope's payload", + "description": "The hash value of the PAE encoded payload", "type": "string" } }, "readOnly": true } } - }, - "publicKey": { - "description": "The public key that can verify the signature", - "type": "string", - "format": "byte" } }, "$schema": "http://json-schema.org/draft-07/schema", - "$id": "http://rekor.sigstore.dev/types/intoto/intoto_v0_0_1_schema.json" + "$id": "http://rekor.sigstore.dev/types/intoto/intoto_v0_0_2_schema.json" }, "jar": { "description": "Java Archive (JAR)", diff --git a/pkg/types/dsse/dsse.go b/pkg/types/dsse/dsse.go deleted file mode 100644 index e94c73c37..000000000 --- a/pkg/types/dsse/dsse.go +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright 2022 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 dsse - -import ( - "context" - "errors" - "fmt" - - "github.com/sigstore/rekor/pkg/generated/models" - "github.com/sigstore/rekor/pkg/types" -) - -const ( - KIND = "dsse" -) - -type BaseDsseType struct { - types.RekorType -} - -func init() { - types.TypeMap.Store(KIND, New) -} - -func New() types.TypeImpl { - bdt := BaseDsseType{} - bdt.Kind = KIND - bdt.VersionMap = VersionMap - return &bdt -} - -var VersionMap = types.NewSemVerEntryFactoryMap() - -func (bdt BaseDsseType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImpl, error) { - if pe == nil { - return nil, errors.New("proposed entry cannot be nil") - } - - in, ok := pe.(*models.Dsse) - if !ok { - return nil, errors.New("cannot unmarshal non-DSSE types") - } - - return bdt.VersionedUnmarshal(in, *in.APIVersion) -} - -func (bdt *BaseDsseType) CreateProposedEntry(ctx context.Context, version string, props types.ArtifactProperties) (models.ProposedEntry, error) { - if version == "" { - version = bdt.DefaultVersion() - } - ei, err := bdt.VersionedUnmarshal(nil, version) - if err != nil { - return nil, fmt.Errorf("fetching DSSE version implementation, %w", err) - } - - return ei.CreateFromArtifactProperties(ctx, props) -} - -func (bdt BaseDsseType) DefaultVersion() string { - return "0.0.1" -} diff --git a/pkg/types/dsse/dsse_schema.json b/pkg/types/dsse/dsse_schema.json deleted file mode 100644 index 511130952..000000000 --- a/pkg/types/dsse/dsse_schema.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "http://rekor.sigstore.dev/types/dsse/dsse_schema.json", - "title": "DSSE Schema", - "description": "DSSE for Rekord objects", - "type": "object", - "oneOf": [ - { - "$ref": "v0.0.1/dsse_v0_0_1_schema.json" - } - ] -} diff --git a/pkg/types/dsse/dsse_test.go b/pkg/types/dsse/dsse_test.go deleted file mode 100644 index 56addd41e..000000000 --- a/pkg/types/dsse/dsse_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// -// Copyright 2022 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 dsse - -import ( - "errors" - "testing" - - "github.com/go-openapi/swag" - - "github.com/sigstore/rekor/pkg/generated/models" - "github.com/sigstore/rekor/pkg/types" -) - -type UnmarshalTester struct { - models.Dsse - types.BaseUnmarshalTester -} - -type UnmarshalFailsTester struct { - types.BaseUnmarshalTester -} - -func (u UnmarshalFailsTester) NewEntry() types.EntryImpl { - return &UnmarshalFailsTester{} -} - -func (u UnmarshalFailsTester) Unmarshal(pe models.ProposedEntry) error { - return errors.New("error") -} - -func TestDsseType(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.Dsse.APIVersion = swag.String("2.0.1") - brt := New() - - // version requested matches implementation in map - if _, err := brt.UnmarshalEntry(&u.Dsse); err != nil { - t.Errorf("unexpected error in Unmarshal: %v", err) - } - - // version requested fails to match implementation in map - u.Dsse.APIVersion = swag.String("1.2.2") - if _, err := brt.UnmarshalEntry(&u.Dsse); err == nil { - t.Error("unexpected success in Unmarshal for non-matching version") - } - - // error in Unmarshal call is raised appropriately - u.Dsse.APIVersion = swag.String("2.2.0") - u2 := UnmarshalFailsTester{} - _ = VersionMap.SetEntryFactory(">= 1.2.3", u2.NewEntry) - if _, err := brt.UnmarshalEntry(&u.Dsse); err == nil { - t.Error("unexpected success in Unmarshal when error is thrown") - } - - // version requested fails to match implementation in map - u.Dsse.APIVersion = swag.String("not_a_version") - if _, err := brt.UnmarshalEntry(&u.Dsse); err == nil { - t.Error("unexpected success in Unmarshal for invalid version") - } -} diff --git a/pkg/types/dsse/v0.0.1/dsse_v0_0_1_schema.json b/pkg/types/dsse/v0.0.1/dsse_v0_0_1_schema.json deleted file mode 100644 index 500335124..000000000 --- a/pkg/types/dsse/v0.0.1/dsse_v0_0_1_schema.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "http://rekor.sigstore.dev/types/dsse/dsse_v0_0_1_schema.json", - "title": "dsse v0.0.1 Schema", - "description": "Schema for dsse object", - "type": "object", - "properties": { - "payload": { - "description": "payload of the envelope", - "type": "string", - "format": "byte", - "writeOnly": true - }, - "payloadType": { - "description": "type describing the payload", - "type": "string" - }, - "payloadHash": { - "description": "hash of the envelope's payload after being PAE encoded", - "readOnly": true, - "type": "object", - "properties": { - "algorithm": { - "description": "The hashing function used to compue the hash value", - "type": "string", - "enum": ["sha256"] - }, - "value": { - "description": "The hash value of the PAE encoded payload", - "type": "string" - } - } - }, - "signatures": { - "description": "collection of all signatures of the envelope's payload", - "type": "array", - "minItems": 1, - "items": { - "description": "a signature of the envelope's payload along with the public key for the signature", - "type": "object", - "properties": { - "keyid": { - "description": "optional id of the key used to create the signature", - "type": "string" - }, - "sig": { - "description": "signature of the payload", - "type": "string" - }, - "publicKey": { - "description": "public key that corresponds to this signature", - "type": "string", - "format": "byte", - "readOnly": true - } - } - } - } - }, - "required": ["payloadType", "signatures"] -} diff --git a/pkg/types/entries.go b/pkg/types/entries.go index 63aeab0ac..ab497205c 100644 --- a/pkg/types/entries.go +++ b/pkg/types/entries.go @@ -154,7 +154,7 @@ type ArtifactProperties struct { SignatureBytes []byte PublicKeyPath *url.URL PublicKeyBytes []byte - PublicKeysBytes [][]byte - PublicKeysPaths []*url.URL + MultiPublicKeyBytes [][]byte + MultiPublicKeyPaths []*url.URL PKIFormat string } diff --git a/pkg/types/intoto/intoto.go b/pkg/types/intoto/intoto.go index f48daacbe..f682f7ede 100644 --- a/pkg/types/intoto/intoto.go +++ b/pkg/types/intoto/intoto.go @@ -70,5 +70,5 @@ func (it *BaseIntotoType) CreateProposedEntry(ctx context.Context, version strin } func (it BaseIntotoType) DefaultVersion() string { - return "0.0.1" + return "0.0.2" } diff --git a/pkg/types/intoto/intoto_schema.json b/pkg/types/intoto/intoto_schema.json index b99b3c2d9..852ba660d 100644 --- a/pkg/types/intoto/intoto_schema.json +++ b/pkg/types/intoto/intoto_schema.json @@ -6,7 +6,7 @@ "type": "object", "oneOf": [ { - "$ref": "v0.0.1/intoto_v0_0_1_schema.json" + "$ref": "v0.0.2/intoto_v0_0_2_schema.json" } ] } diff --git a/pkg/types/dsse/v0.0.1/entry.go b/pkg/types/intoto/v0.0.2/entry.go similarity index 55% rename from pkg/types/dsse/v0.0.1/entry.go rename to pkg/types/intoto/v0.0.2/entry.go index 9b71dcbd4..85f2e0b51 100644 --- a/pkg/types/dsse/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.2/entry.go @@ -1,5 +1,5 @@ // -// Copyright 2022 The Sigstore Authors. +// 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. @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package dsse +package intoto import ( "bytes" @@ -28,6 +28,7 @@ import ( "io/ioutil" "net/url" "path/filepath" + "strings" "github.com/in-toto/in-toto-golang/in_toto" "github.com/secure-systems-lab/go-securesystemslib/dsse" @@ -40,96 +41,141 @@ import ( "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/pki/x509" "github.com/sigstore/rekor/pkg/types" - rekordsse "github.com/sigstore/rekor/pkg/types/dsse" + "github.com/sigstore/rekor/pkg/types/intoto" "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" ) const ( - APIVERSION = "0.0.1" + APIVERSION = "0.0.2" ) func init() { - if err := rekordsse.VersionMap.SetEntryFactory(APIVERSION, NewEntry); err != nil { + if err := intoto.VersionMap.SetEntryFactory(APIVERSION, NewEntry); err != nil { log.Logger.Panic(err) } } -type V001Entry struct { - DsseObj models.DsseV001Schema +type V002Entry struct { + IntotoObj models.IntotoV002Schema + env dsse.Envelope } -func (v V001Entry) APIVersion() string { +func (v V002Entry) APIVersion() string { return APIVERSION } func NewEntry() types.EntryImpl { - return &V001Entry{} + return &V002Entry{} } -func (v V001Entry) IndexKeys() ([]string, error) { +func (v V002Entry) IndexKeys() ([]string, error) { var result []string - fmt.Printf("%+v\n", v.DsseObj) - result = append(result, v.DsseObj.PayloadHash.Algorithm+":"+v.DsseObj.PayloadHash.Value) - for _, sig := range v.DsseObj.Signatures { + if v.IntotoObj.Content == nil || v.IntotoObj.Content.Envelope == nil { + log.Logger.Info("IntotoObj content or dsse envelope is nil") + return result, nil + } + + for _, sig := range v.IntotoObj.Content.Envelope.Signatures { keyHash := sha256.Sum256(sig.PublicKey) result = append(result, "sha256:"+hex.EncodeToString(keyHash[:])) + + keyObj, err := x509.NewPublicKey(bytes.NewReader(sig.PublicKey)) + if err != nil { + return nil, err + } + + result = append(result, keyObj.EmailAddresses()...) } - switch *v.DsseObj.PayloadType { + payloadKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.PayloadHash.Algorithm, *v.IntotoObj.Content.PayloadHash.Value)) + result = append(result, payloadKey) + + switch *v.IntotoObj.Content.Envelope.PayloadType { case in_toto.PayloadType: - statement, err := parseIntotoStatement(v.DsseObj.Payload) + + hashkey := strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.Hash.Algorithm, *v.IntotoObj.Content.Hash.Value)) + result = append(result, hashkey) + + if v.IntotoObj.Content.Envelope.Payload == "" { + log.Logger.Info("IntotoObj DSSE payload is empty") + return result, nil + } + decodedPayload, err := base64.StdEncoding.DecodeString(string(v.IntotoObj.Content.Envelope.Payload)) + if err != nil { + return result, fmt.Errorf("could not decode envelope payload: %w", err) + } + statement, err := parseStatement(decodedPayload) if err != nil { return result, err } - for _, s := range statement.Subject { for alg, ds := range s.Digest { result = append(result, alg+":"+ds) } } + // Not all in-toto statements will contain a SLSA provenance predicate. + // See https://github.com/in-toto/attestation/blob/main/spec/README.md#predicate + // for other predicates. + if predicate, err := parseSlsaPredicate(decodedPayload); err == nil { + if predicate.Predicate.Materials != nil { + for _, s := range predicate.Predicate.Materials { + for alg, ds := range s.Digest { + result = append(result, alg+":"+ds) + } + } + } + } default: - log.Logger.Infof("Cannot index payload of type: %s", *v.DsseObj.PayloadType) + log.Logger.Infof("Unknown in_toto DSSE envelope Type: %s", *v.IntotoObj.Content.Envelope.PayloadType) } - return result, nil } -func parseIntotoStatement(p []byte) (*in_toto.Statement, error) { +func parseStatement(p []byte) (*in_toto.Statement, error) { ps := in_toto.Statement{} if err := json.Unmarshal(p, &ps); err != nil { return nil, err } - return &ps, nil } -func (v *V001Entry) Unmarshal(pe models.ProposedEntry) error { - dsseModel, ok := pe.(*models.Dsse) +func parseSlsaPredicate(p []byte) (*in_toto.ProvenanceStatement, error) { + predicate := in_toto.ProvenanceStatement{} + if err := json.Unmarshal(p, &predicate); err != nil { + return nil, err + } + return &predicate, nil +} + +func (v *V002Entry) Unmarshal(pe models.ProposedEntry) error { + it, ok := pe.(*models.Intoto) if !ok { - return errors.New("cannot unmarshal non DSSE v0.0.1 type") + return errors.New("cannot unmarshal non Intoto v0.0.1 type") } - if err := types.DecodeEntry(dsseModel.Spec, &v.DsseObj); err != nil { + var err error + if err := types.DecodeEntry(it.Spec, &v.IntotoObj); err != nil { return err } // field validation - if err := v.DsseObj.Validate(strfmt.Default); err != nil { + if err := v.IntotoObj.Validate(strfmt.Default); err != nil { return err } - if string(v.DsseObj.Payload) == "" { + if string(v.IntotoObj.Content.Envelope.Payload) == "" { return nil } env := &dsse.Envelope{ - Payload: string(v.DsseObj.Payload), - PayloadType: *v.DsseObj.PayloadType, + Payload: string(v.IntotoObj.Content.Envelope.Payload), + PayloadType: *v.IntotoObj.Content.Envelope.PayloadType, } allPubKeyBytes := make([][]byte, 0) - for _, sig := range v.DsseObj.Signatures { + for _, sig := range v.IntotoObj.Content.Envelope.Signatures { env.Signatures = append(env.Signatures, dsse.Signature{ KeyID: sig.Keyid, Sig: string(sig.Sig), @@ -142,59 +188,61 @@ func (v *V001Entry) Unmarshal(pe models.ProposedEntry) error { return err } - decodedPayload, err := base64.StdEncoding.DecodeString(string(v.DsseObj.Payload)) + v.env = *env + + decodedPayload, err := base64.StdEncoding.DecodeString(string(v.IntotoObj.Content.Envelope.Payload)) if err != nil { return fmt.Errorf("could not decode envelope payload: %w", err) } - paeEncodedPayload := dsse.PAE(*v.DsseObj.PayloadType, decodedPayload) + paeEncodedPayload := dsse.PAE(*v.IntotoObj.Content.Envelope.PayloadType, decodedPayload) h := sha256.Sum256(paeEncodedPayload) - v.DsseObj.PayloadHash = &models.DsseV001SchemaPayloadHash{ - Algorithm: models.DsseV001SchemaPayloadHashAlgorithmSha256, - Value: hex.EncodeToString(h[:]), + v.IntotoObj.Content.PayloadHash = &models.IntotoV002SchemaContentPayloadHash{ + Algorithm: swag.String(models.IntotoV002SchemaContentPayloadHashAlgorithmSha256), + Value: swag.String(hex.EncodeToString(h[:])), } return nil } -func (v *V001Entry) Canonicalize(ctx context.Context) ([]byte, error) { - canonicalEntry := models.DsseV001Schema{ - PayloadHash: v.DsseObj.PayloadHash, - Signatures: v.DsseObj.Signatures, - PayloadType: v.DsseObj.PayloadType, +func (v *V002Entry) Canonicalize(ctx context.Context) ([]byte, error) { + + canonicalEntry := models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: v.IntotoObj.Content.Envelope, + Hash: v.IntotoObj.Content.Hash, + PayloadHash: v.IntotoObj.Content.PayloadHash, + }, } - model := models.Dsse{} - model.APIVersion = swag.String(APIVERSION) - model.Spec = canonicalEntry - return json.Marshal(&model) + itObj := models.Intoto{} + itObj.APIVersion = swag.String(APIVERSION) + itObj.Spec = &canonicalEntry + + return json.Marshal(&itObj) } -func (v *V001Entry) AttestationKey() string { - if v.DsseObj.PayloadHash != nil { - return fmt.Sprintf("%s:%s", v.DsseObj.PayloadHash.Algorithm, v.DsseObj.PayloadHash.Value) +// AttestationKey returns the digest of the attestation that was uploaded, to be used to lookup the attestation from storage +func (v *V002Entry) AttestationKey() string { + if v.IntotoObj.Content != nil && v.IntotoObj.Content.PayloadHash != nil { + return fmt.Sprintf("%s:%s", *v.IntotoObj.Content.PayloadHash.Algorithm, *v.IntotoObj.Content.PayloadHash.Value) } - return "" } -func (v *V001Entry) AttestationKeyValue() (string, []byte) { - storageSize := base64.StdEncoding.DecodedLen(len(v.DsseObj.Payload)) +// AttestationKeyValue returns both the key and value to be persisted into attestation storage +func (v *V002Entry) AttestationKeyValue() (string, []byte) { + storageSize := base64.StdEncoding.DecodedLen(len(v.env.Payload)) if storageSize > viper.GetInt("max_attestation_size") { log.Logger.Infof("Skipping attestation storage, size %d is greater than max %d", storageSize, viper.GetInt("max_attestation_size")) return "", nil } - - decodedPayload, err := base64.StdEncoding.DecodeString(string(v.DsseObj.Payload)) - if err != nil { - log.Logger.Infof("Skipping attestation storage, error while decoding attestation: %v", err) - return "", nil - } - - return v.AttestationKey(), decodedPayload + attBytes, _ := base64.StdEncoding.DecodeString(v.env.Payload) + return v.AttestationKey(), attBytes } type verifier struct { + s signature.Signer v signature.Verifier } @@ -211,6 +259,17 @@ func (v *verifier) Public() crypto.PublicKey { return nil } +func (v *verifier) Sign(data []byte) (sig []byte, err error) { + if v.s == nil { + return nil, errors.New("nil signer") + } + sig, err = v.s.SignMessage(bytes.NewReader(data), options.WithCryptoSignerOpts(crypto.SHA256)) + if err != nil { + return nil, err + } + return sig, nil +} + func (v *verifier) Verify(data, sig []byte) error { if v.v == nil { return errors.New("nil verifier") @@ -218,20 +277,19 @@ func (v *verifier) Verify(data, sig []byte) error { return v.v.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data)) } -func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { - returnVal := models.Dsse{} - re := V001Entry{} +func (v V002Entry) CreateFromArtifactProperties(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { + returnVal := models.Intoto{} + re := V002Entry{} + var err error artifactBytes := props.ArtifactBytes if artifactBytes == nil { if props.ArtifactPath == nil { return nil, errors.New("path to artifact file must be specified") } if props.ArtifactPath.IsAbs() { - return nil, errors.New("dsse envelopes cannot be fetched over HTTP(S)") + return nil, errors.New("intoto envelopes cannot be fetched over HTTP(S)") } - - var err error artifactBytes, err = ioutil.ReadFile(filepath.Clean(props.ArtifactPath.Path)) if err != nil { return nil, err @@ -248,12 +306,16 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A allPubKeyBytes = append(allPubKeyBytes, props.PublicKeyBytes) } - allPubKeyBytes = append(allPubKeyBytes, props.PublicKeysBytes...) + allPubKeyBytes = append(allPubKeyBytes, props.MultiPublicKeyBytes...) allPubKeyPaths := make([]*url.URL, 0) if props.PublicKeyPath != nil { allPubKeyPaths = append(allPubKeyPaths, props.PublicKeyPath) } + if props.MultiPublicKeyPaths != nil { + allPubKeyPaths = append(allPubKeyPaths, props.MultiPublicKeyPaths...) + } + for _, path := range allPubKeyPaths { if path.IsAbs() { return nil, errors.New("dsse public keys cannot be fetched over HTTP(S)") @@ -272,8 +334,9 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A return nil, err } - re.DsseObj.Payload = strfmt.Base64(env.Payload) - re.DsseObj.PayloadType = &env.PayloadType + re.IntotoObj.Content.Envelope.Payload = env.Payload + re.IntotoObj.Content.Envelope.PayloadType = &env.PayloadType + for _, sig := range env.Signatures { key, ok := keysBySig[sig.Sig] if !ok { @@ -286,15 +349,22 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A } keyBytes := strfmt.Base64(canonKey) - re.DsseObj.Signatures = append(re.DsseObj.Signatures, &models.DsseV001SchemaSignaturesItems0{ + re.IntotoObj.Content.Envelope.Signatures = append(re.IntotoObj.Content.Envelope.Signatures, &models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ Keyid: sig.KeyID, Sig: sig.Sig, PublicKey: keyBytes, }) } + h := sha256.Sum256([]byte(artifactBytes)) + re.IntotoObj.Content.Hash = &models.IntotoV002SchemaContentHash{ + Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256), + Value: swag.String(hex.EncodeToString(h[:])), + } + + returnVal.Spec = re.IntotoObj returnVal.APIVersion = swag.String(re.APIVersion()) - returnVal.Spec = re.DsseObj + return &returnVal, nil } diff --git a/pkg/types/intoto/v0.0.2/entry_test.go b/pkg/types/intoto/v0.0.2/entry_test.go new file mode 100644 index 000000000..2ed4073dc --- /dev/null +++ b/pkg/types/intoto/v0.0.2/entry_test.go @@ -0,0 +1,438 @@ +// +// 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 intoto + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/json" + "encoding/pem" + "fmt" + "math/big" + "reflect" + "sort" + "strings" + "testing" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/google/go-cmp/cmp" + "github.com/in-toto/in-toto-golang/in_toto" + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" + "github.com/secure-systems-lab/go-securesystemslib/dsse" + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/sigstore/pkg/signature" + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + +func TestNewEntryReturnType(t *testing.T) { + entry := NewEntry() + if reflect.TypeOf(entry) != reflect.ValueOf(&V002Entry{}).Type() { + t.Errorf("invalid type returned from NewEntry: %T", entry) + } +} + +func envelope(t *testing.T, k *ecdsa.PrivateKey, payload []byte) *dsse.Envelope { + + s, err := signature.LoadECDSASigner(k, crypto.SHA256) + if err != nil { + t.Fatal(err) + } + signer, err := in_toto.NewDSSESigner( + &verifier{ + s: s, + }) + if err != nil { + t.Fatal(err) + } + dsseEnv, err := signer.SignPayload(payload) + if err != nil { + t.Fatal(err) + } + + return dsseEnv +} + +func multiSignEnvelope(t *testing.T, k []*ecdsa.PrivateKey, payload []byte) *dsse.Envelope { + evps := []*verifier{} + for _, key := range k { + s, err := signature.LoadECDSASigner(key, crypto.SHA256) + if err != nil { + t.Fatal(err) + } + evps = append(evps, &verifier{ + s: s, + }) + } + + signer, err := dsse.NewMultiEnvelopeSigner(2, evps[0], evps[1]) + if err != nil { + t.Fatal(err) + } + dsseEnv, err := signer.SignPayload(in_toto.PayloadType, payload) + if err != nil { + t.Fatal(err) + } + + return dsseEnv +} + +func createRekorEnvelope(dsseEnv *dsse.Envelope, pub [][]byte) *models.IntotoV002SchemaContentEnvelope { + + env := &models.IntotoV002SchemaContentEnvelope{ + Payload: dsseEnv.Payload, + PayloadType: &dsseEnv.PayloadType, + } + + for i, sig := range dsseEnv.Signatures { + env.Signatures = append(env.Signatures, &models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ + Keyid: sig.KeyID, + Sig: sig.Sig, + PublicKey: pub[i], + }) + } + return env +} + +func envelopeHash(t *testing.T, dsseEnv *dsse.Envelope) string { + val, err := json.Marshal(dsseEnv) + if err != nil { + t.Fatal(err) + } + h := sha256.Sum256(val) + return hex.EncodeToString(h[:]) +} + +func TestV002Entry_Unmarshal(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + der, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + if err != nil { + t.Fatal(err) + } + pub := pem.EncodeToMemory(&pem.Block{ + Bytes: der, + Type: "PUBLIC KEY", + }) + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + + ca := &x509.Certificate{ + SerialNumber: big.NewInt(1), + } + caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &priv.PublicKey, priv) + if err != nil { + t.Fatal(err) + } + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + }) + + invalid := dsse.Envelope{ + Payload: "hello", + Signatures: []dsse.Signature{ + { + Sig: string(strfmt.Base64("foobar")), + }, + }, + } + + validPayload := "hellothispayloadisvalid" + + tests := []struct { + env *dsse.Envelope + name string + it *models.IntotoV002Schema + wantErr bool + }{ + { + name: "empty", + it: &models.IntotoV002Schema{}, + wantErr: true, + }, + { + name: "missing envelope", + it: &models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Hash: &models.IntotoV002SchemaContentHash{ + Algorithm: swag.String(models.IntotoV002SchemaContentHashAlgorithmSha256), + }, + }, + }, + wantErr: true, + }, + { + env: envelope(t, key, []byte(validPayload)), + name: "valid", + it: &models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: createRekorEnvelope(envelope(t, key, []byte(validPayload)), [][]byte{pub}), + Hash: &models.IntotoV002SchemaContentHash{ + Algorithm: swag.String(models.IntotoV002SchemaContentHashAlgorithmSha256), + Value: swag.String(envelopeHash(t, envelope(t, key, []byte(validPayload)))), + }, + }, + }, + wantErr: false, + }, + { + env: envelope(t, priv, []byte(validPayload)), + name: "cert", + it: &models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: createRekorEnvelope(envelope(t, priv, []byte(validPayload)), [][]byte{pemBytes}), + Hash: &models.IntotoV002SchemaContentHash{ + Algorithm: swag.String(models.IntotoV002SchemaContentHashAlgorithmSha256), + Value: swag.String(envelopeHash(t, envelope(t, priv, []byte(validPayload)))), + }, + }, + }, + wantErr: false, + }, + { + env: &invalid, + name: "invalid", + it: &models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: createRekorEnvelope(&invalid, [][]byte{pub}), + Hash: &models.IntotoV002SchemaContentHash{ + Algorithm: swag.String(models.IntotoV002SchemaContentHashAlgorithmSha256), + Value: swag.String(envelopeHash(t, &invalid)), + }, + }, + }, + wantErr: true, + }, + { + env: envelope(t, key, []byte(validPayload)), + name: "invalid key", + it: &models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: createRekorEnvelope(envelope(t, key, []byte(validPayload)), [][]byte{[]byte("notavalidkey")}), + Hash: &models.IntotoV002SchemaContentHash{ + Algorithm: swag.String(models.IntotoV002SchemaContentHashAlgorithmSha256), + Value: swag.String(envelopeHash(t, envelope(t, key, []byte(validPayload)))), + }, + }, + }, + wantErr: true, + }, + { + env: multiSignEnvelope(t, []*ecdsa.PrivateKey{key, priv}, []byte(validPayload)), + name: "multi-key", + it: &models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: createRekorEnvelope(multiSignEnvelope(t, []*ecdsa.PrivateKey{key, priv}, []byte(validPayload)), [][]byte{pub, pemBytes}), + Hash: &models.IntotoV002SchemaContentHash{ + Algorithm: swag.String(models.IntotoV002SchemaContentHashAlgorithmSha256), + Value: swag.String(envelopeHash(t, multiSignEnvelope(t, []*ecdsa.PrivateKey{key, priv}, []byte(validPayload)))), + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &V002Entry{} + + it := &models.Intoto{ + Spec: tt.it, + } + + var uv = func() error { + if err := v.Unmarshal(it); err != nil { + return err + } + want := []string{} + for _, sig := range v.IntotoObj.Content.Envelope.Signatures { + keyHash := sha256.Sum256(sig.PublicKey) + want = append(want, "sha256:"+hex.EncodeToString(keyHash[:])) + } + decodedPayload, err := base64.StdEncoding.DecodeString(tt.env.Payload) + if err != nil { + return fmt.Errorf("could not decode envelope payload: %w", err) + } + paeEncodedPayload := dsse.PAE(tt.env.PayloadType, decodedPayload) + h := sha256.Sum256(paeEncodedPayload) + want = append(want, "sha256:"+hex.EncodeToString(h[:])) + + if !reflect.DeepEqual(v.AttestationKey(), "sha256:"+hex.EncodeToString(h[:])) { + t.Errorf("V002Entry.AttestationKey() = %v, want %v", v.AttestationKey(), "sha256:"+hex.EncodeToString(h[:])) + } + + hashkey := strings.ToLower(fmt.Sprintf("%s:%s", *tt.it.Content.Hash.Algorithm, *tt.it.Content.Hash.Value)) + want = append(want, hashkey) + got, _ := v.IndexKeys() + sort.Strings(got) + sort.Strings(want) + if !reflect.DeepEqual(got, want) { + t.Errorf("V002Entry.IndexKeys() = %v, want %v", got, want) + } + + return nil + } + if err := uv(); (err != nil) != tt.wantErr { + t.Errorf("V002Entry.Unmarshal() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestV002Entry_IndexKeys(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + der, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + if err != nil { + t.Fatal(err) + } + pub := pem.EncodeToMemory(&pem.Block{ + Bytes: der, + Type: "PUBLIC KEY", + }) + + tests := []struct { + name string + statement in_toto.Statement + want []string + }{ + { + name: "standard", + want: []string{}, + statement: in_toto.Statement{ + Predicate: "hello", + }, + }, + { + name: "subject", + want: []string{"sha256:foo"}, + statement: in_toto.Statement{ + StatementHeader: in_toto.StatementHeader{ + Subject: []in_toto.Subject{ + { + Name: "foo", + Digest: map[string]string{ + "sha256": "foo", + }, + }, + }, + }, + Predicate: "hello", + }, + }, + { + name: "slsa", + want: []string{"sha256:bar"}, + statement: in_toto.Statement{ + Predicate: slsa.ProvenancePredicate{ + Materials: []slsa.ProvenanceMaterial{ + { + URI: "foo", + Digest: map[string]string{ + "sha256": "bar", + }}, + }, + }, + }, + }, + { + name: "slsa wit header", + want: []string{"sha256:foo", "sha256:bar"}, + statement: in_toto.Statement{ + StatementHeader: in_toto.StatementHeader{ + Subject: []in_toto.Subject{ + { + Name: "foo", + Digest: map[string]string{ + "sha256": "foo", + }, + }, + }, + }, + Predicate: slsa.ProvenancePredicate{ + Materials: []slsa.ProvenanceMaterial{ + { + URI: "foo", + Digest: map[string]string{ + "sha256": "bar", + }}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, err := json.Marshal(tt.statement) + if err != nil { + t.Fatal(err) + } + paeEncodedPayload := dsse.PAE(in_toto.PayloadType, b) + payloadHash := sha256.Sum256(paeEncodedPayload) + v := V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: createRekorEnvelope(envelope(t, key, b), [][]byte{pub}), + Hash: &models.IntotoV002SchemaContentHash{ + Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256), + Value: swag.String(envelopeHash(t, envelope(t, key, b))), + }, + PayloadHash: &models.IntotoV002SchemaContentPayloadHash{ + Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256), + Value: swag.String(hex.EncodeToString(payloadHash[:])), + }, + }, + }, + env: *envelope(t, key, b), + } + want := []string{} + for _, sig := range v.IntotoObj.Content.Envelope.Signatures { + keyHash := sha256.Sum256(sig.PublicKey) + want = append(want, "sha256:"+hex.EncodeToString(keyHash[:])) + } + + want = append(want, "sha256:"+hex.EncodeToString(payloadHash[:])) + + hashkey := strings.ToLower("sha256:" + *v.IntotoObj.Content.Hash.Value) + want = append(want, hashkey) + want = append(want, tt.want...) + got, _ := v.IndexKeys() + sort.Strings(got) + sort.Strings(want) + if !cmp.Equal(got, want) { + t.Errorf("V001Entry.IndexKeys() = %v, want %v", got, want) + } + }) + } +} diff --git a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json new file mode 100644 index 000000000..66415c765 --- /dev/null +++ b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json @@ -0,0 +1,101 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/intoto/intoto_v0_0_2_schema.json", + "title": "intoto v0.0.2 Schema", + "description": "Schema for intoto object", + "type": "object", + "properties": { + "content": { + "type": "object", + "properties": { + "envelope": { + "description": "dsse envelope", + "type": "object", + "properties": { + "payload": { + "description": "payload of the envelope", + "type": "string", + "writeOnly": true + }, + "payloadType": { + "description": "type describing the payload", + "type": "string" + }, + "signatures": { + "description": "collection of all signatures of the envelope's payload", + "type": "array", + "minItems": 1, + "items": { + "description": "a signature of the envelope's payload along with the public key for the signature", + "type": "object", + "properties": { + "keyid": { + "description": "optional id of the key used to create the signature", + "type": "string" + }, + "sig": { + "description": "signature of the payload", + "type": "string" + }, + "publicKey": { + "description": "public key that corresponds to this signature", + "type": "string", + "format": "byte", + "readOnly": true + } + } + } + } + }, + "required": ["payloadType", "signatures"], + "writeOnly": true + }, + "hash": { + "description": "Specifies the hash algorithm and value encompassing the entire signed envelope", + "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 archive", + "type": "string" + } + }, + "required": [ + "algorithm", + "value" + ], + "readOnly": true + }, + "payloadHash": { + "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ "sha256" ] + }, + "value": { + "description": "The hash value of the PAE encoded payload", + "type": "string" + } + }, + "required": [ + "algorithm", + "value" + ], + "readOnly": true + } + } + } + }, + "required": [ + "content" + ] +} diff --git a/tests/e2e_test.go b/tests/e2e_test.go index d87c56441..c707b5966 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -30,7 +30,6 @@ import ( "encoding/json" "encoding/pem" "fmt" - "golang.org/x/sync/errgroup" "io/ioutil" "net/http" "os" @@ -42,6 +41,8 @@ import ( "testing" "time" + "golang.org/x/sync/errgroup" + "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" @@ -559,7 +560,7 @@ func TestIntoto(t *testing.T) { } -func TestDsse(t *testing.T) { +func TestIntotoV002(t *testing.T) { td := t.TempDir() attestationPath := filepath.Join(td, "attestation.json") pubKeyPath := filepath.Join(td, "pub.pem") @@ -619,7 +620,7 @@ func TestDsse(t *testing.T) { write(t, ecdsaPub, pubKeyPath) // If we do it twice, it should already exist - out := runCli(t, "upload", "--artifact", attestationPath, "--type", "dsse", "--public-key", pubKeyPath) + out := runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", pubKeyPath) outputContains(t, out, "Created entry at") uuid := getUUIDFromUploadOutput(t, out) @@ -640,23 +641,24 @@ func TestDsse(t *testing.T) { attHash := sha256.Sum256(dsse.PAE("application/vnd.in-toto+json", []byte(g.Attestation))) - dsseModel := &models.DsseV001Schema{} - if err := types.DecodeEntry(g.Body.(map[string]interface{})["DsseObj"], dsseModel); err != nil { - t.Errorf("could not convert body into dsse type: %v", err) + intotoV002Model := &models.IntotoV002Schema{} + if err := types.DecodeEntry(g.Body.(map[string]interface{})["IntotoObj"], intotoV002Model); err != nil { + t.Errorf("could not convert body into intoto type: %v", err) } - if dsseModel.PayloadHash == nil { - t.Errorf("could not find hash over attestation %v", dsseModel) + if intotoV002Model.Content.Hash == nil { + t.Errorf("could not find hash over attestation %v", intotoV002Model) } - recordedPayloadHash, err := hex.DecodeString(dsseModel.PayloadHash.Value) + recordedPayloadHash, err := hex.DecodeString(*intotoV002Model.Content.PayloadHash.Value) if err != nil { t.Errorf("error converting attestation hash to []byte: %v", err) } if !bytes.Equal(attHash[:], recordedPayloadHash) { - t.Fatal(fmt.Errorf("attestation hash %v doesnt match the payload we sent %v", hex.EncodeToString(attHash[:]), dsseModel.PayloadHash.Value)) + t.Fatal(fmt.Errorf("attestation hash %v doesnt match the payload we sent %v", hex.EncodeToString(attHash[:]), + *intotoV002Model.Content.PayloadHash.Value)) } - out = runCli(t, "upload", "--artifact", attestationPath, "--type", "dsse", "--public-key", pubKeyPath) + out = runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", pubKeyPath) outputContains(t, out, "Entry already exists") } From d7b45ea565d387ef5f8667cd1b828bf2df1cb493 Mon Sep 17 00:00:00 2001 From: pxp928 Date: Wed, 17 Aug 2022 14:09:40 -0400 Subject: [PATCH 3/8] implemented fixes and added new cli flag Signed-off-by: pxp928 --- cmd/rekor-cli/app/pflag_groups.go | 20 ++++++ cmd/rekor-cli/app/pflags.go | 65 +++++++++++++++---- cmd/rekor-cli/app/pflags_test.go | 14 ++++ pkg/generated/models/intoto_v002_schema.go | 18 ++++- pkg/generated/restapi/embedded_spec.go | 9 ++- pkg/types/intoto/v0.0.2/entry.go | 48 +++++++++----- pkg/types/intoto/v0.0.2/entry_test.go | 10 ++- .../intoto/v0.0.2/intoto_v0_0_2_schema.json | 4 +- tests/e2e_test.go | 2 +- tests/intoto_multi_dsse.json | 6 ++ 10 files changed, 150 insertions(+), 46 deletions(-) create mode 100644 tests/intoto_multi_dsse.json diff --git a/cmd/rekor-cli/app/pflag_groups.go b/cmd/rekor-cli/app/pflag_groups.go index 9cf7818e1..090c28892 100644 --- a/cmd/rekor-cli/app/pflag_groups.go +++ b/cmd/rekor-cli/app/pflag_groups.go @@ -73,6 +73,11 @@ func addArtifactPFlags(cmd *cobra.Command) error { "path or URL to public key file", false, }, + "multi-public-key": { + multiFileOrURLFlag, + "path or URL to public key files", + false, + }, "artifact": { fileOrURLFlag, "path or URL to artifact file", @@ -157,6 +162,21 @@ func CreatePropsFromPflags() *types.ArtifactProperties { } } + multiPublicKeyString := viper.GetString("multi-public-key") + splitPubKeyString := strings.Split(multiPublicKeyString, ",") + if len(splitPubKeyString) > 0 { + collectedKeys := []*url.URL{} + for _, key := range splitPubKeyString { + if isURL(key) { + keyPath, _ := url.Parse(key) + collectedKeys = append(collectedKeys, keyPath) + } else { + collectedKeys = append(collectedKeys, &url.URL{Path: key}) + } + } + props.MultiPublicKeyPaths = collectedKeys + } + props.PKIFormat = viper.GetString("pki-format") b64aad := viper.GetString("aad") if b64aad != "" { diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index 21f16021f..1a04b5ae4 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -35,20 +35,21 @@ import ( type FlagType string const ( - uuidFlag FlagType = "uuid" - shaFlag FlagType = "sha" - emailFlag FlagType = "email" - operatorFlag FlagType = "operator" - logIndexFlag FlagType = "logIndex" - pkiFormatFlag FlagType = "pkiFormat" - typeFlag FlagType = "type" - fileFlag FlagType = "file" - urlFlag FlagType = "url" - fileOrURLFlag FlagType = "fileOrURL" - oidFlag FlagType = "oid" - formatFlag FlagType = "format" - timeoutFlag FlagType = "timeout" - base64Flag FlagType = "base64" + uuidFlag FlagType = "uuid" + shaFlag FlagType = "sha" + emailFlag FlagType = "email" + operatorFlag FlagType = "operator" + logIndexFlag FlagType = "logIndex" + pkiFormatFlag FlagType = "pkiFormat" + typeFlag FlagType = "type" + fileFlag FlagType = "file" + urlFlag FlagType = "url" + fileOrURLFlag FlagType = "fileOrURL" + multiFileOrURLFlag FlagType = "multiFileOrURL" + oidFlag FlagType = "oid" + formatFlag FlagType = "format" + timeoutFlag FlagType = "timeout" + base64Flag FlagType = "base64" ) type newPFlagValueFunc func() pflag.Value @@ -100,6 +101,10 @@ func initializePFlagMap() { // applies logic of fileFlag OR urlFlag validators from above return valueFactory(fileOrURLFlag, validateFileOrURL, "") }, + multiFileOrURLFlag: func() pflag.Value { + // applies logic of fileFlag OR urlFlag validators from above for multi file and URL + return multiValueFactory(multiFileOrURLFlag, validateFileOrURL, []string{}) + }, oidFlag: func() pflag.Value { // this validates for an OID, which is a sequence of positive integers separated by periods return valueFactory(oidFlag, validateOID, "") @@ -142,6 +147,38 @@ func valueFactory(flagType FlagType, v validationFunc, defaultVal string) pflag. } } +func multiValueFactory(flagType FlagType, v validationFunc, defaultVal []string) pflag.Value { + return &multiBaseValue{ + flagType: flagType, + validationFunc: v, + value: defaultVal, + } +} + +// multiBaseValue implements pflag.Value +type multiBaseValue struct { + flagType FlagType + value []string + validationFunc validationFunc +} + +func (b *multiBaseValue) String() string { + return strings.Join(b.value, ",") +} + +// Type returns the type of this Value +func (b multiBaseValue) Type() string { + return string(b.flagType) +} + +func (b *multiBaseValue) Set(value string) error { + if err := b.validationFunc(value); err != nil { + return err + } + b.value = append(b.value, value) + return nil +} + // baseValue implements pflag.Value type baseValue struct { flagType FlagType diff --git a/cmd/rekor-cli/app/pflags_test.go b/cmd/rekor-cli/app/pflags_test.go index 64887c4aa..f69a97bc4 100644 --- a/cmd/rekor-cli/app/pflags_test.go +++ b/cmd/rekor-cli/app/pflags_test.go @@ -37,6 +37,7 @@ func TestArtifactPFlags(t *testing.T) { artifact string signature string publicKey string + multiPublicKey []string uuid string aad string uuidRequired bool @@ -373,6 +374,14 @@ func TestArtifactPFlags(t *testing.T) { expectParseSuccess: true, expectValidateSuccess: false, }, + { + caseDesc: "valid intoto - multi keys", + typeStr: "intoto", + artifact: "../../../tests/intoto_multi_dsse.json", + multiPublicKey: []string{"../../../tests/intoto_dsse.pem", "../../../tests/intoto_dsse.pem"}, + expectParseSuccess: true, + expectValidateSuccess: true, + }, } for _, tc := range tests { @@ -405,6 +414,11 @@ func TestArtifactPFlags(t *testing.T) { if tc.publicKey != "" { args = append(args, "--public-key", tc.publicKey) } + if len(tc.multiPublicKey) > 0 { + for _, key := range tc.multiPublicKey { + args = append(args, "--multi-public-key", key) + } + } if tc.uuid != "" { args = append(args, "--uuid", tc.uuid) } diff --git a/pkg/generated/models/intoto_v002_schema.go b/pkg/generated/models/intoto_v002_schema.go index 06ddfd59f..d8d8bcfd9 100644 --- a/pkg/generated/models/intoto_v002_schema.go +++ b/pkg/generated/models/intoto_v002_schema.go @@ -314,7 +314,8 @@ func (m *IntotoV002SchemaContent) UnmarshalBinary(b []byte) error { type IntotoV002SchemaContentEnvelope struct { // payload of the envelope - Payload string `json:"payload,omitempty"` + // Required: true + Payload *string `json:"payload"` // type describing the payload // Required: true @@ -330,6 +331,10 @@ type IntotoV002SchemaContentEnvelope struct { func (m *IntotoV002SchemaContentEnvelope) Validate(formats strfmt.Registry) error { var res []error + if err := m.validatePayload(formats); err != nil { + res = append(res, err) + } + if err := m.validatePayloadType(formats); err != nil { res = append(res, err) } @@ -344,6 +349,15 @@ func (m *IntotoV002SchemaContentEnvelope) Validate(formats strfmt.Registry) erro return nil } +func (m *IntotoV002SchemaContentEnvelope) validatePayload(formats strfmt.Registry) error { + + if err := validate.Required("content"+"."+"envelope"+"."+"payload", "body", m.Payload); err != nil { + return err + } + + return nil +} + func (m *IntotoV002SchemaContentEnvelope) validatePayloadType(formats strfmt.Registry) error { if err := validate.Required("content"+"."+"envelope"+"."+"payloadType", "body", m.PayloadType); err != nil { @@ -611,7 +625,7 @@ func (m *IntotoV002SchemaContentHash) UnmarshalBinary(b []byte) error { return nil } -// IntotoV002SchemaContentPayloadHash Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded +// IntotoV002SchemaContentPayloadHash Specifies the hash algorithm and value covering the payload within the DSSE envelope // // swagger:model IntotoV002SchemaContentPayloadHash type IntotoV002SchemaContentPayloadHash struct { diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index 9ca2baea7..1afe7470d 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -1864,6 +1864,7 @@ func init() { "description": "dsse envelope", "type": "object", "required": [ + "payload", "payloadType", "signatures" ], @@ -1911,7 +1912,7 @@ func init() { "readOnly": true }, "payloadHash": { - "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", "type": "object", "required": [ "algorithm", @@ -1938,6 +1939,7 @@ func init() { "description": "dsse envelope", "type": "object", "required": [ + "payload", "payloadType", "signatures" ], @@ -2005,7 +2007,7 @@ func init() { "readOnly": true }, "IntotoV002SchemaContentPayloadHash": { - "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", "type": "object", "required": [ "algorithm", @@ -3157,6 +3159,7 @@ func init() { "description": "dsse envelope", "type": "object", "required": [ + "payload", "payloadType", "signatures" ], @@ -3204,7 +3207,7 @@ func init() { "readOnly": true }, "payloadHash": { - "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", "type": "object", "required": [ "algorithm", diff --git a/pkg/types/intoto/v0.0.2/entry.go b/pkg/types/intoto/v0.0.2/entry.go index 85f2e0b51..252e181e4 100644 --- a/pkg/types/intoto/v0.0.2/entry.go +++ b/pkg/types/intoto/v0.0.2/entry.go @@ -1,5 +1,5 @@ // -// Copyright 2021 The Sigstore Authors. +// Copyright 2022 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. @@ -86,7 +86,7 @@ func (v V002Entry) IndexKeys() ([]string, error) { return nil, err } - result = append(result, keyObj.EmailAddresses()...) + result = append(result, keyObj.Subjects()...) } payloadKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.PayloadHash.Algorithm, *v.IntotoObj.Content.PayloadHash.Value)) @@ -98,11 +98,11 @@ func (v V002Entry) IndexKeys() ([]string, error) { hashkey := strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.Hash.Algorithm, *v.IntotoObj.Content.Hash.Value)) result = append(result, hashkey) - if v.IntotoObj.Content.Envelope.Payload == "" { + if *v.IntotoObj.Content.Envelope.Payload == "" { log.Logger.Info("IntotoObj DSSE payload is empty") return result, nil } - decodedPayload, err := base64.StdEncoding.DecodeString(string(v.IntotoObj.Content.Envelope.Payload)) + decodedPayload, err := base64.StdEncoding.DecodeString(string(*v.IntotoObj.Content.Envelope.Payload)) if err != nil { return result, fmt.Errorf("could not decode envelope payload: %w", err) } @@ -152,7 +152,7 @@ func parseSlsaPredicate(p []byte) (*in_toto.ProvenanceStatement, error) { func (v *V002Entry) Unmarshal(pe models.ProposedEntry) error { it, ok := pe.(*models.Intoto) if !ok { - return errors.New("cannot unmarshal non Intoto v0.0.1 type") + return errors.New("cannot unmarshal non Intoto v0.0.2 type") } var err error @@ -165,12 +165,12 @@ func (v *V002Entry) Unmarshal(pe models.ProposedEntry) error { return err } - if string(v.IntotoObj.Content.Envelope.Payload) == "" { - return nil + if string(*v.IntotoObj.Content.Envelope.Payload) == "" { + return errors.New("DSSE envelope does not contain a payload") } env := &dsse.Envelope{ - Payload: string(v.IntotoObj.Content.Envelope.Payload), + Payload: string(*v.IntotoObj.Content.Envelope.Payload), PayloadType: *v.IntotoObj.Content.Envelope.PayloadType, } @@ -190,13 +190,12 @@ func (v *V002Entry) Unmarshal(pe models.ProposedEntry) error { v.env = *env - decodedPayload, err := base64.StdEncoding.DecodeString(string(v.IntotoObj.Content.Envelope.Payload)) + decodedPayload, err := base64.StdEncoding.DecodeString(string(*v.IntotoObj.Content.Envelope.Payload)) if err != nil { return fmt.Errorf("could not decode envelope payload: %w", err) } - paeEncodedPayload := dsse.PAE(*v.IntotoObj.Content.Envelope.PayloadType, decodedPayload) - h := sha256.Sum256(paeEncodedPayload) + h := sha256.Sum256(decodedPayload) v.IntotoObj.Content.PayloadHash = &models.IntotoV002SchemaContentPayloadHash{ Algorithm: swag.String(models.IntotoV002SchemaContentPayloadHashAlgorithmSha256), Value: swag.String(hex.EncodeToString(h[:])), @@ -209,12 +208,17 @@ func (v *V002Entry) Canonicalize(ctx context.Context) ([]byte, error) { canonicalEntry := models.IntotoV002Schema{ Content: &models.IntotoV002SchemaContent{ - Envelope: v.IntotoObj.Content.Envelope, - Hash: v.IntotoObj.Content.Hash, - PayloadHash: v.IntotoObj.Content.PayloadHash, + Envelope: &models.IntotoV002SchemaContentEnvelope{}, + Hash: &models.IntotoV002SchemaContentHash{}, + PayloadHash: &models.IntotoV002SchemaContentPayloadHash{}, }, } + canonicalEntry.Content.Envelope.PayloadType = v.IntotoObj.Content.Envelope.PayloadType + canonicalEntry.Content.Envelope.Signatures = v.IntotoObj.Content.Envelope.Signatures + canonicalEntry.Content.Hash = v.IntotoObj.Content.Hash + canonicalEntry.Content.PayloadHash = v.IntotoObj.Content.PayloadHash + itObj := models.Intoto{} itObj.APIVersion = swag.String(APIVERSION) itObj.Spec = &canonicalEntry @@ -237,7 +241,11 @@ func (v *V002Entry) AttestationKeyValue() (string, []byte) { log.Logger.Infof("Skipping attestation storage, size %d is greater than max %d", storageSize, viper.GetInt("max_attestation_size")) return "", nil } - attBytes, _ := base64.StdEncoding.DecodeString(v.env.Payload) + attBytes, err := base64.StdEncoding.DecodeString(v.env.Payload) + if err != nil { + log.Logger.Infof("could not decode envelope payload: %w", err) + return "", nil + } return v.AttestationKey(), attBytes } @@ -279,8 +287,12 @@ func (v *verifier) Verify(data, sig []byte) error { func (v V002Entry) CreateFromArtifactProperties(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { returnVal := models.Intoto{} - re := V002Entry{} - + re := V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: &models.IntotoV002SchemaContentEnvelope{}, + }, + }} var err error artifactBytes := props.ArtifactBytes if artifactBytes == nil { @@ -334,7 +346,7 @@ func (v V002Entry) CreateFromArtifactProperties(_ context.Context, props types.A return nil, err } - re.IntotoObj.Content.Envelope.Payload = env.Payload + re.IntotoObj.Content.Envelope.Payload = swag.String(env.Payload) re.IntotoObj.Content.Envelope.PayloadType = &env.PayloadType for _, sig := range env.Signatures { diff --git a/pkg/types/intoto/v0.0.2/entry_test.go b/pkg/types/intoto/v0.0.2/entry_test.go index 2ed4073dc..a9c8a1414 100644 --- a/pkg/types/intoto/v0.0.2/entry_test.go +++ b/pkg/types/intoto/v0.0.2/entry_test.go @@ -1,5 +1,5 @@ // -// Copyright 2021 The Sigstore Authors. +// Copyright 2022 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. @@ -103,7 +103,7 @@ func multiSignEnvelope(t *testing.T, k []*ecdsa.PrivateKey, payload []byte) *dss func createRekorEnvelope(dsseEnv *dsse.Envelope, pub [][]byte) *models.IntotoV002SchemaContentEnvelope { env := &models.IntotoV002SchemaContentEnvelope{ - Payload: dsseEnv.Payload, + Payload: swag.String(dsseEnv.Payload), PayloadType: &dsseEnv.PayloadType, } @@ -282,8 +282,7 @@ func TestV002Entry_Unmarshal(t *testing.T) { if err != nil { return fmt.Errorf("could not decode envelope payload: %w", err) } - paeEncodedPayload := dsse.PAE(tt.env.PayloadType, decodedPayload) - h := sha256.Sum256(paeEncodedPayload) + h := sha256.Sum256(decodedPayload) want = append(want, "sha256:"+hex.EncodeToString(h[:])) if !reflect.DeepEqual(v.AttestationKey(), "sha256:"+hex.EncodeToString(h[:])) { @@ -398,8 +397,7 @@ func TestV002Entry_IndexKeys(t *testing.T) { if err != nil { t.Fatal(err) } - paeEncodedPayload := dsse.PAE(in_toto.PayloadType, b) - payloadHash := sha256.Sum256(paeEncodedPayload) + payloadHash := sha256.Sum256(b) v := V002Entry{ IntotoObj: models.IntotoV002Schema{ Content: &models.IntotoV002SchemaContent{ diff --git a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json index 66415c765..0f79c1547 100644 --- a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json +++ b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json @@ -47,7 +47,7 @@ } } }, - "required": ["payloadType", "signatures"], + "required": ["payload", "payloadType", "signatures"], "writeOnly": true }, "hash": { @@ -73,7 +73,7 @@ "readOnly": true }, "payloadHash": { - "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", "type": "object", "properties": { "algorithm": { diff --git a/tests/e2e_test.go b/tests/e2e_test.go index c707b5966..8c4cfd512 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -639,7 +639,7 @@ func TestIntotoV002(t *testing.T) { t.Errorf("diff: %s", diff) } - attHash := sha256.Sum256(dsse.PAE("application/vnd.in-toto+json", []byte(g.Attestation))) + attHash := sha256.Sum256([]byte(g.Attestation)) intotoV002Model := &models.IntotoV002Schema{} if err := types.DecodeEntry(g.Body.(map[string]interface{})["IntotoObj"], intotoV002Model); err != nil { diff --git a/tests/intoto_multi_dsse.json b/tests/intoto_multi_dsse.json new file mode 100644 index 000000000..7ddfa907c --- /dev/null +++ b/tests/intoto_multi_dsse.json @@ -0,0 +1,6 @@ +{"payloadType":"application/vnd.in-toto+json", +"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJmb29iYXIiLCJkaWdlc3QiOnsiZm9vIjoiYmFyIn19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJmb29ISzFiZ2Y1WC8xckNxZz09In0sImJ1aWxkVHlwZSI6IiIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", +"signatures":[{ + "keyid":"first","sig":"MEQCIAIlnxHC3eU4jmUsqJExxfzqyy8bk+61btgnRiGcRDxgAiBwmdnJ/GX1yCYhYAvwAtkuYN0yFlVPQVAx9R6JpUUBiA=="}, + {"keyid":"second","sig":"MEQCIAIlnxHC3eU4jmUsqJExxfzqyy8bk+61btgnRiGcRDxgAiBwmdnJ/GX1yCYhYAvwAtkuYN0yFlVPQVAx9R6JpUUBiA=="}] +} From 4811d79217225f9a8c858183040c5e73076a8ea6 Mon Sep 17 00:00:00 2001 From: pxp928 Date: Thu, 18 Aug 2022 17:25:13 -0400 Subject: [PATCH 4/8] changed public-key to slice Signed-off-by: pxp928 --- Makefile.swagger | 2 +- cmd/rekor-cli/app/pflag_groups.go | 20 +---- cmd/rekor-cli/app/pflags_test.go | 12 ++- cmd/rekor-cli/app/search.go | 19 +++-- cmd/rekor-server/app/serve.go | 23 +++--- pkg/generated/models/intoto_v002_schema.go | 8 +- pkg/generated/restapi/embedded_spec.go | 12 ++- pkg/types/alpine/v0.0.1/entry.go | 19 +++-- pkg/types/cose/v0.0.1/entry.go | 18 +++-- pkg/types/entries.go | 6 +- pkg/types/hashedrekord/v0.0.1/entry.go | 19 +++-- pkg/types/helm/v0.0.1/entry.go | 18 +++-- pkg/types/intoto/v0.0.1/entry.go | 19 +++-- pkg/types/intoto/v0.0.2/entry.go | 55 ++++++-------- pkg/types/intoto/v0.0.2/entry_test.go | 12 +-- .../intoto/v0.0.2/intoto_v0_0_2_schema.json | 6 +- pkg/types/rekord/v0.0.1/entry.go | 20 +++-- pkg/types/rpm/v0.0.1/entry.go | 20 +++-- pkg/types/tuf/v0.0.1/entry.go | 20 +++-- tests/e2e_test.go | 54 +++++++++++--- tests/harness_test.go | 12 ++- tests/intoto_multi_dsse.json | 6 +- tests/intoto_multi_pub2.pem | 9 +++ tests/x509.go | 74 +++++++++++++------ 24 files changed, 304 insertions(+), 179 deletions(-) create mode 100644 tests/intoto_multi_pub2.pem diff --git a/Makefile.swagger b/Makefile.swagger index 2ea5b3bd3..781b4d37d 100644 --- a/Makefile.swagger +++ b/Makefile.swagger @@ -1,2 +1,2 @@ # This file is generated after swagger runs as part of the build; do not edit! -SWAGGER_GEN=pkg/generated/client/entries/create_log_entry_parameters.go pkg/generated/client/entries/create_log_entry_responses.go pkg/generated/client/entries/entries_client.go pkg/generated/client/entries/get_log_entry_by_index_parameters.go pkg/generated/client/entries/get_log_entry_by_index_responses.go pkg/generated/client/entries/get_log_entry_by_uuid_parameters.go pkg/generated/client/entries/get_log_entry_by_uuid_responses.go pkg/generated/client/entries/search_log_query_parameters.go pkg/generated/client/entries/search_log_query_responses.go pkg/generated/client/index/index_client.go pkg/generated/client/index/search_index_parameters.go pkg/generated/client/index/search_index_responses.go pkg/generated/client/pubkey/get_public_key_parameters.go pkg/generated/client/pubkey/get_public_key_responses.go pkg/generated/client/pubkey/pubkey_client.go pkg/generated/client/rekor_client.go pkg/generated/client/server/get_rekor_version_parameters.go pkg/generated/client/server/get_rekor_version_responses.go pkg/generated/client/server/server_client.go pkg/generated/client/tlog/get_log_info_parameters.go pkg/generated/client/tlog/get_log_info_responses.go pkg/generated/client/tlog/get_log_proof_parameters.go pkg/generated/client/tlog/get_log_proof_responses.go pkg/generated/client/tlog/tlog_client.go pkg/generated/models//alpine.go pkg/generated/models//alpine_schema.go pkg/generated/models//alpine_v001_schema.go pkg/generated/models//consistency_proof.go pkg/generated/models//cose.go pkg/generated/models//cose_schema.go pkg/generated/models//cose_v001_schema.go pkg/generated/models//error.go pkg/generated/models//hashedrekord.go pkg/generated/models//hashedrekord_schema.go pkg/generated/models//hashedrekord_v001_schema.go pkg/generated/models//helm.go pkg/generated/models//helm_schema.go pkg/generated/models//helm_v001_schema.go pkg/generated/models//inactive_shard_log_info.go pkg/generated/models//inclusion_proof.go pkg/generated/models//intoto.go pkg/generated/models//intoto_schema.go pkg/generated/models//intoto_v001_schema.go pkg/generated/models//intoto_v002_schema.go pkg/generated/models//jar.go pkg/generated/models//jar_schema.go pkg/generated/models//jar_v001_schema.go pkg/generated/models//log_entry.go pkg/generated/models//log_info.go pkg/generated/models//proposed_entry.go pkg/generated/models//rekord.go pkg/generated/models//rekord_schema.go pkg/generated/models//rekord_v001_schema.go pkg/generated/models//rekor_version.go pkg/generated/models//rfc3161.go pkg/generated/models//rfc3161_schema.go pkg/generated/models//rfc3161_v001_schema.go pkg/generated/models//rpm.go pkg/generated/models//rpm_schema.go pkg/generated/models//rpm_v001_schema.go pkg/generated/models//search_index.go pkg/generated/models//search_log_query.go pkg/generated/models//tuf.go pkg/generated/models//tuf_schema.go pkg/generated/models//tuf_v001_schema.go pkg/generated/restapi//doc.go pkg/generated/restapi//embedded_spec.go pkg/generated/restapi//operations/entries/create_log_entry.go pkg/generated/restapi//operations/entries/create_log_entry_parameters.go pkg/generated/restapi//operations/entries/create_log_entry_responses.go pkg/generated/restapi//operations/entries/create_log_entry_urlbuilder.go pkg/generated/restapi//operations/entries/get_log_entry_by_index.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_parameters.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_responses.go pkg/generated/restapi//operations/entries/get_log_entry_by_index_urlbuilder.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_parameters.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_responses.go pkg/generated/restapi//operations/entries/get_log_entry_by_uuid_urlbuilder.go pkg/generated/restapi//operations/entries/search_log_query.go pkg/generated/restapi//operations/entries/search_log_query_parameters.go pkg/generated/restapi//operations/entries/search_log_query_responses.go pkg/generated/restapi//operations/entries/search_log_query_urlbuilder.go pkg/generated/restapi//operations/index/search_index.go pkg/generated/restapi//operations/index/search_index_parameters.go pkg/generated/restapi//operations/index/search_index_responses.go pkg/generated/restapi//operations/index/search_index_urlbuilder.go pkg/generated/restapi//operations/pubkey/get_public_key.go pkg/generated/restapi//operations/pubkey/get_public_key_parameters.go pkg/generated/restapi//operations/pubkey/get_public_key_responses.go pkg/generated/restapi//operations/pubkey/get_public_key_urlbuilder.go pkg/generated/restapi//operations/rekor_server_api.go pkg/generated/restapi//operations/server/get_rekor_version.go pkg/generated/restapi//operations/server/get_rekor_version_parameters.go pkg/generated/restapi//operations/server/get_rekor_version_responses.go pkg/generated/restapi//operations/server/get_rekor_version_urlbuilder.go pkg/generated/restapi//operations/tlog/get_log_info.go pkg/generated/restapi//operations/tlog/get_log_info_parameters.go pkg/generated/restapi//operations/tlog/get_log_info_responses.go pkg/generated/restapi//operations/tlog/get_log_info_urlbuilder.go pkg/generated/restapi//operations/tlog/get_log_proof.go pkg/generated/restapi//operations/tlog/get_log_proof_parameters.go pkg/generated/restapi//operations/tlog/get_log_proof_responses.go pkg/generated/restapi//operations/tlog/get_log_proof_urlbuilder.go pkg/generated/restapi//server.go +SWAGGER_GEN=pkg/generated/client/entries/create_log_entry_parameters.go pkg/generated/client/entries/create_log_entry_responses.go pkg/generated/client/entries/entries_client.go pkg/generated/client/entries/get_log_entry_by_index_parameters.go pkg/generated/client/entries/get_log_entry_by_index_responses.go pkg/generated/client/entries/get_log_entry_by_uuid_parameters.go pkg/generated/client/entries/get_log_entry_by_uuid_responses.go pkg/generated/client/entries/search_log_query_parameters.go pkg/generated/client/entries/search_log_query_responses.go pkg/generated/client/index/index_client.go pkg/generated/client/index/search_index_parameters.go pkg/generated/client/index/search_index_responses.go pkg/generated/client/pubkey/get_public_key_parameters.go pkg/generated/client/pubkey/get_public_key_responses.go pkg/generated/client/pubkey/pubkey_client.go pkg/generated/client/rekor_client.go pkg/generated/client/server/get_rekor_version_parameters.go pkg/generated/client/server/get_rekor_version_responses.go pkg/generated/client/server/server_client.go pkg/generated/client/tlog/get_log_info_parameters.go pkg/generated/client/tlog/get_log_info_responses.go pkg/generated/client/tlog/get_log_proof_parameters.go pkg/generated/client/tlog/get_log_proof_responses.go pkg/generated/client/tlog/tlog_client.go pkg/generated/models/alpine.go pkg/generated/models/alpine_schema.go pkg/generated/models/alpine_v001_schema.go pkg/generated/models/consistency_proof.go pkg/generated/models/cose.go pkg/generated/models/cose_schema.go pkg/generated/models/cose_v001_schema.go pkg/generated/models/error.go pkg/generated/models/hashedrekord.go pkg/generated/models/hashedrekord_schema.go pkg/generated/models/hashedrekord_v001_schema.go pkg/generated/models/helm.go pkg/generated/models/helm_schema.go pkg/generated/models/helm_v001_schema.go pkg/generated/models/inactive_shard_log_info.go pkg/generated/models/inclusion_proof.go pkg/generated/models/intoto.go pkg/generated/models/intoto_schema.go pkg/generated/models/intoto_v001_schema.go pkg/generated/models/intoto_v002_schema.go pkg/generated/models/jar.go pkg/generated/models/jar_schema.go pkg/generated/models/jar_v001_schema.go pkg/generated/models/log_entry.go pkg/generated/models/log_info.go pkg/generated/models/proposed_entry.go pkg/generated/models/rekord.go pkg/generated/models/rekord_schema.go pkg/generated/models/rekord_v001_schema.go pkg/generated/models/rekor_version.go pkg/generated/models/rfc3161.go pkg/generated/models/rfc3161_schema.go pkg/generated/models/rfc3161_v001_schema.go pkg/generated/models/rpm.go pkg/generated/models/rpm_schema.go pkg/generated/models/rpm_v001_schema.go pkg/generated/models/search_index.go pkg/generated/models/search_log_query.go pkg/generated/models/tuf.go pkg/generated/models/tuf_schema.go pkg/generated/models/tuf_v001_schema.go pkg/generated/restapi/doc.go pkg/generated/restapi/embedded_spec.go pkg/generated/restapi/operations/entries/create_log_entry.go pkg/generated/restapi/operations/entries/create_log_entry_parameters.go pkg/generated/restapi/operations/entries/create_log_entry_responses.go pkg/generated/restapi/operations/entries/create_log_entry_urlbuilder.go pkg/generated/restapi/operations/entries/get_log_entry_by_index.go pkg/generated/restapi/operations/entries/get_log_entry_by_index_parameters.go pkg/generated/restapi/operations/entries/get_log_entry_by_index_responses.go pkg/generated/restapi/operations/entries/get_log_entry_by_index_urlbuilder.go pkg/generated/restapi/operations/entries/get_log_entry_by_uuid.go pkg/generated/restapi/operations/entries/get_log_entry_by_uuid_parameters.go pkg/generated/restapi/operations/entries/get_log_entry_by_uuid_responses.go pkg/generated/restapi/operations/entries/get_log_entry_by_uuid_urlbuilder.go pkg/generated/restapi/operations/entries/search_log_query.go pkg/generated/restapi/operations/entries/search_log_query_parameters.go pkg/generated/restapi/operations/entries/search_log_query_responses.go pkg/generated/restapi/operations/entries/search_log_query_urlbuilder.go pkg/generated/restapi/operations/index/search_index.go pkg/generated/restapi/operations/index/search_index_parameters.go pkg/generated/restapi/operations/index/search_index_responses.go pkg/generated/restapi/operations/index/search_index_urlbuilder.go pkg/generated/restapi/operations/pubkey/get_public_key.go pkg/generated/restapi/operations/pubkey/get_public_key_parameters.go pkg/generated/restapi/operations/pubkey/get_public_key_responses.go pkg/generated/restapi/operations/pubkey/get_public_key_urlbuilder.go pkg/generated/restapi/operations/rekor_server_api.go pkg/generated/restapi/operations/server/get_rekor_version.go pkg/generated/restapi/operations/server/get_rekor_version_parameters.go pkg/generated/restapi/operations/server/get_rekor_version_responses.go pkg/generated/restapi/operations/server/get_rekor_version_urlbuilder.go pkg/generated/restapi/operations/tlog/get_log_info.go pkg/generated/restapi/operations/tlog/get_log_info_parameters.go pkg/generated/restapi/operations/tlog/get_log_info_responses.go pkg/generated/restapi/operations/tlog/get_log_info_urlbuilder.go pkg/generated/restapi/operations/tlog/get_log_proof.go pkg/generated/restapi/operations/tlog/get_log_proof_parameters.go pkg/generated/restapi/operations/tlog/get_log_proof_responses.go pkg/generated/restapi/operations/tlog/get_log_proof_urlbuilder.go pkg/generated/restapi/server.go diff --git a/cmd/rekor-cli/app/pflag_groups.go b/cmd/rekor-cli/app/pflag_groups.go index 090c28892..8057a4d68 100644 --- a/cmd/rekor-cli/app/pflag_groups.go +++ b/cmd/rekor-cli/app/pflag_groups.go @@ -69,13 +69,8 @@ func addArtifactPFlags(cmd *cobra.Command) error { false, }, "public-key": { - fileOrURLFlag, - "path or URL to public key file", - false, - }, - "multi-public-key": { multiFileOrURLFlag, - "path or URL to public key files", + "path or URL to public key file", false, }, "artifact": { @@ -154,16 +149,7 @@ func CreatePropsFromPflags() *types.ArtifactProperties { } publicKeyString := viper.GetString("public-key") - if publicKeyString != "" { - if isURL(publicKeyString) { - props.PublicKeyPath, _ = url.Parse(publicKeyString) - } else { - props.PublicKeyPath = &url.URL{Path: publicKeyString} - } - } - - multiPublicKeyString := viper.GetString("multi-public-key") - splitPubKeyString := strings.Split(multiPublicKeyString, ",") + splitPubKeyString := strings.Split(publicKeyString, ",") if len(splitPubKeyString) > 0 { collectedKeys := []*url.URL{} for _, key := range splitPubKeyString { @@ -174,7 +160,7 @@ func CreatePropsFromPflags() *types.ArtifactProperties { collectedKeys = append(collectedKeys, &url.URL{Path: key}) } } - props.MultiPublicKeyPaths = collectedKeys + props.PublicKeyPath = collectedKeys } props.PKIFormat = viper.GetString("pki-format") diff --git a/cmd/rekor-cli/app/pflags_test.go b/cmd/rekor-cli/app/pflags_test.go index f69a97bc4..a6a769abe 100644 --- a/cmd/rekor-cli/app/pflags_test.go +++ b/cmd/rekor-cli/app/pflags_test.go @@ -374,11 +374,19 @@ func TestArtifactPFlags(t *testing.T) { expectParseSuccess: true, expectValidateSuccess: false, }, + { + caseDesc: "valid intoto - one keys", + typeStr: "intoto", + artifact: "../../../tests/intoto_dsse.json", + publicKey: "../../../tests/intoto_dsse.pem", + expectParseSuccess: true, + expectValidateSuccess: true, + }, { caseDesc: "valid intoto - multi keys", typeStr: "intoto", artifact: "../../../tests/intoto_multi_dsse.json", - multiPublicKey: []string{"../../../tests/intoto_dsse.pem", "../../../tests/intoto_dsse.pem"}, + multiPublicKey: []string{"../../../tests/intoto_dsse.pem", "../../../tests/intoto_multi_pub2.pem"}, expectParseSuccess: true, expectValidateSuccess: true, }, @@ -416,7 +424,7 @@ func TestArtifactPFlags(t *testing.T) { } if len(tc.multiPublicKey) > 0 { for _, key := range tc.multiPublicKey { - args = append(args, "--multi-public-key", key) + args = append(args, "--public-key", key) } } if tc.uuid != "" { diff --git a/cmd/rekor-cli/app/search.go b/cmd/rekor-cli/app/search.go index a33c127f6..b59639c45 100644 --- a/cmd/rekor-cli/app/search.go +++ b/cmd/rekor-cli/app/search.go @@ -164,15 +164,18 @@ var searchCmd = &cobra.Command{ default: return nil, fmt.Errorf("unknown pki-format %v", pkiFormat) } - publicKeyStr := viper.GetString("public-key") - if isURL(publicKeyStr) { - params.Query.PublicKey.URL = strfmt.URI(publicKeyStr) - } else { - keyBytes, err := ioutil.ReadFile(filepath.Clean(publicKeyStr)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) + + splitPubKeyString := strings.Split(publicKeyStr, ",") + if len(splitPubKeyString) == 1 { + if isURL(splitPubKeyString[0]) { + params.Query.PublicKey.URL = strfmt.URI(splitPubKeyString[0]) + } else { + keyBytes, err := ioutil.ReadFile(filepath.Clean(splitPubKeyString[0])) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + params.Query.PublicKey.Content = strfmt.Base64(keyBytes) } - params.Query.PublicKey.Content = strfmt.Base64(keyBytes) } } diff --git a/cmd/rekor-server/app/serve.go b/cmd/rekor-server/app/serve.go index 46652bb21..536443e8e 100644 --- a/cmd/rekor-server/app/serve.go +++ b/cmd/rekor-server/app/serve.go @@ -38,6 +38,7 @@ import ( "github.com/sigstore/rekor/pkg/types/helm" helm_v001 "github.com/sigstore/rekor/pkg/types/helm/v0.0.1" "github.com/sigstore/rekor/pkg/types/intoto" + intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" intoto_v002 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.2" "github.com/sigstore/rekor/pkg/types/jar" jar_v001 "github.com/sigstore/rekor/pkg/types/jar/v0.0.1" @@ -84,17 +85,17 @@ var serveCmd = &cobra.Command{ //TODO: add command line option to print versions supported in binary // these trigger loading of package and therefore init() methods to run - pluggableTypeMap := map[string]string{ - rekord.KIND: rekord_v001.APIVERSION, - rpm.KIND: rpm_v001.APIVERSION, - jar.KIND: jar_v001.APIVERSION, - intoto.KIND: intoto_v002.APIVERSION, - cose.KIND: cose_v001.APIVERSION, - rfc3161.KIND: rfc3161_v001.APIVERSION, - alpine.KIND: alpine_v001.APIVERSION, - helm.KIND: helm_v001.APIVERSION, - tuf.KIND: tuf_v001.APIVERSION, - hashedrekord.KIND: hashedrekord_v001.APIVERSION, + pluggableTypeMap := map[string][]string{ + rekord.KIND: {rekord_v001.APIVERSION}, + rpm.KIND: {rpm_v001.APIVERSION}, + jar.KIND: {jar_v001.APIVERSION}, + intoto.KIND: {intoto_v001.APIVERSION, intoto_v002.APIVERSION}, + cose.KIND: {cose_v001.APIVERSION}, + rfc3161.KIND: {rfc3161_v001.APIVERSION}, + alpine.KIND: {alpine_v001.APIVERSION}, + helm.KIND: {helm_v001.APIVERSION}, + tuf.KIND: {tuf_v001.APIVERSION}, + hashedrekord.KIND: {hashedrekord_v001.APIVERSION}, } for k, v := range pluggableTypeMap { diff --git a/pkg/generated/models/intoto_v002_schema.go b/pkg/generated/models/intoto_v002_schema.go index d8d8bcfd9..3b95dab47 100644 --- a/pkg/generated/models/intoto_v002_schema.go +++ b/pkg/generated/models/intoto_v002_schema.go @@ -315,7 +315,8 @@ type IntotoV002SchemaContentEnvelope struct { // payload of the envelope // Required: true - Payload *string `json:"payload"` + // Format: byte + Payload *strfmt.Base64 `json:"payload"` // type describing the payload // Required: true @@ -466,7 +467,8 @@ type IntotoV002SchemaContentEnvelopeSignaturesItems0 struct { PublicKey strfmt.Base64 `json:"publicKey,omitempty"` // signature of the payload - Sig string `json:"sig,omitempty"` + // Format: byte + Sig strfmt.Base64 `json:"sig,omitempty"` } // Validate validates this intoto v002 schema content envelope signatures items0 @@ -635,7 +637,7 @@ type IntotoV002SchemaContentPayloadHash struct { // Enum: [sha256] Algorithm *string `json:"algorithm"` - // The hash value of the PAE encoded payload + // The hash value of the payload // Required: true Value *string `json:"value"` } diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index 1afe7470d..b7f6261b5 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -1872,6 +1872,7 @@ func init() { "payload": { "description": "payload of the envelope", "type": "string", + "format": "byte", "writeOnly": true }, "payloadType": { @@ -1927,7 +1928,7 @@ func init() { ] }, "value": { - "description": "The hash value of the PAE encoded payload", + "description": "The hash value of the payload", "type": "string" } }, @@ -1947,6 +1948,7 @@ func init() { "payload": { "description": "payload of the envelope", "type": "string", + "format": "byte", "writeOnly": true }, "payloadType": { @@ -1980,7 +1982,8 @@ func init() { }, "sig": { "description": "signature of the payload", - "type": "string" + "type": "string", + "format": "byte" } } }, @@ -2022,7 +2025,7 @@ func init() { ] }, "value": { - "description": "The hash value of the PAE encoded payload", + "description": "The hash value of the payload", "type": "string" } }, @@ -3167,6 +3170,7 @@ func init() { "payload": { "description": "payload of the envelope", "type": "string", + "format": "byte", "writeOnly": true }, "payloadType": { @@ -3222,7 +3226,7 @@ func init() { ] }, "value": { - "description": "The hash value of the PAE encoded payload", + "description": "The hash value of the payload", "type": "string" } }, diff --git a/pkg/types/alpine/v0.0.1/entry.go b/pkg/types/alpine/v0.0.1/entry.go index 3a68cebe1..28f00918f 100644 --- a/pkg/types/alpine/v0.0.1/entry.go +++ b/pkg/types/alpine/v0.0.1/entry.go @@ -322,14 +322,21 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.AlpineModel.PublicKey = &models.AlpineV001SchemaPublicKey{} publicKeyBytes := props.PublicKeyBytes - if publicKeyBytes == nil { - publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) + if len(publicKeyBytes) == 0 { + if len(props.PublicKeyPath) > 1 { + return nil, errors.New("only one public key must be provided") } - re.AlpineModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes) + if len(props.PublicKeyPath) == 1 { + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + publicKeyBytes = append(publicKeyBytes, keyBytes) + re.AlpineModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + } + } else { - re.AlpineModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes) + re.AlpineModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) } if err := re.validate(); err != nil { diff --git a/pkg/types/cose/v0.0.1/entry.go b/pkg/types/cose/v0.0.1/entry.go index 7ddb5a508..2638cba74 100644 --- a/pkg/types/cose/v0.0.1/entry.go +++ b/pkg/types/cose/v0.0.1/entry.go @@ -316,19 +316,25 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A } } publicKeyBytes := props.PublicKeyBytes - if publicKeyBytes == nil { - if props.PublicKeyPath == nil { + if len(publicKeyBytes) == 0 { + if len(props.PublicKeyPath) == 0 { return nil, errors.New("public key must be provided to verify signature") } - publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) + if len(props.PublicKeyPath) > 1 { + return nil, errors.New("only one public key must be provided") + } + if len(props.PublicKeyPath) == 1 { + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + publicKeyBytes = append(publicKeyBytes, keyBytes) } } if err != nil { return nil, err } - kb := strfmt.Base64(publicKeyBytes) + kb := strfmt.Base64(publicKeyBytes[0]) mb := strfmt.Base64(messageBytes) re := V001Entry{ diff --git a/pkg/types/entries.go b/pkg/types/entries.go index ab497205c..fad4877fa 100644 --- a/pkg/types/entries.go +++ b/pkg/types/entries.go @@ -152,9 +152,7 @@ type ArtifactProperties struct { ArtifactBytes []byte SignaturePath *url.URL SignatureBytes []byte - PublicKeyPath *url.URL - PublicKeyBytes []byte - MultiPublicKeyBytes [][]byte - MultiPublicKeyPaths []*url.URL + PublicKeyPath []*url.URL + PublicKeyBytes [][]byte PKIFormat string } diff --git a/pkg/types/hashedrekord/v0.0.1/entry.go b/pkg/types/hashedrekord/v0.0.1/entry.go index aea92fcb0..88ac83e83 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry.go +++ b/pkg/types/hashedrekord/v0.0.1/entry.go @@ -217,16 +217,23 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.HashedRekordObj.Signature.PublicKey = &models.HashedrekordV001SchemaSignaturePublicKey{} publicKeyBytes := props.PublicKeyBytes - if publicKeyBytes == nil { - if props.PublicKeyPath == nil { + if len(publicKeyBytes) == 0 { + if len(props.PublicKeyPath) == 0 { return nil, errors.New("public key must be provided to verify detached signature") } - publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) + if len(props.PublicKeyPath) > 1 { + return nil, errors.New("only one public key must be provided") + } + if len(props.PublicKeyPath) == 1 { + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + publicKeyBytes = append(publicKeyBytes, keyBytes) } } - re.HashedRekordObj.Signature.PublicKey.Content = strfmt.Base64(publicKeyBytes) + + re.HashedRekordObj.Signature.PublicKey.Content = strfmt.Base64(publicKeyBytes[0]) re.HashedRekordObj.Data.Hash = &models.HashedrekordV001SchemaDataHash{ Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), diff --git a/pkg/types/helm/v0.0.1/entry.go b/pkg/types/helm/v0.0.1/entry.go index 55f68fe30..1f0056bda 100644 --- a/pkg/types/helm/v0.0.1/entry.go +++ b/pkg/types/helm/v0.0.1/entry.go @@ -317,14 +317,20 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.HelmObj.PublicKey = &models.HelmV001SchemaPublicKey{} publicKeyBytes := props.PublicKeyBytes - if publicKeyBytes == nil { - publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) + if len(publicKeyBytes) == 0 { + if len(props.PublicKeyPath) > 1 { + return nil, errors.New("only one public key must be provided") + } + if len(props.PublicKeyPath) == 1 { + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + publicKeyBytes = append(publicKeyBytes, keyBytes) + re.HelmObj.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) } - re.HelmObj.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes) } else { - re.HelmObj.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes) + re.HelmObj.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) } if err := re.validate(); err != nil { diff --git a/pkg/types/intoto/v0.0.1/entry.go b/pkg/types/intoto/v0.0.1/entry.go index 49e42ccea..8aa3471f5 100644 --- a/pkg/types/intoto/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.1/entry.go @@ -292,16 +292,23 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A } } publicKeyBytes := props.PublicKeyBytes - if publicKeyBytes == nil { - if props.PublicKeyPath == nil { + if len(publicKeyBytes) == 0 { + if len(props.PublicKeyPath) == 0 { return nil, errors.New("public key must be provided to verify signature") } - publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) + if len(props.PublicKeyPath) > 1 { + return nil, errors.New("only one public key must be provided") + } + if len(props.PublicKeyPath) == 1 { + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + publicKeyBytes = append(publicKeyBytes, keyBytes) } } - kb := strfmt.Base64(publicKeyBytes) + + kb := strfmt.Base64(publicKeyBytes[0]) re := V001Entry{ IntotoObj: models.IntotoV001Schema{ diff --git a/pkg/types/intoto/v0.0.2/entry.go b/pkg/types/intoto/v0.0.2/entry.go index 252e181e4..da037d50d 100644 --- a/pkg/types/intoto/v0.0.2/entry.go +++ b/pkg/types/intoto/v0.0.2/entry.go @@ -26,7 +26,6 @@ import ( "errors" "fmt" "io/ioutil" - "net/url" "path/filepath" "strings" @@ -98,7 +97,7 @@ func (v V002Entry) IndexKeys() ([]string, error) { hashkey := strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.Hash.Algorithm, *v.IntotoObj.Content.Hash.Value)) result = append(result, hashkey) - if *v.IntotoObj.Content.Envelope.Payload == "" { + if v.IntotoObj.Content.Envelope.Payload == nil { log.Logger.Info("IntotoObj DSSE payload is empty") return result, nil } @@ -208,17 +207,14 @@ func (v *V002Entry) Canonicalize(ctx context.Context) ([]byte, error) { canonicalEntry := models.IntotoV002Schema{ Content: &models.IntotoV002SchemaContent{ - Envelope: &models.IntotoV002SchemaContentEnvelope{}, - Hash: &models.IntotoV002SchemaContentHash{}, - PayloadHash: &models.IntotoV002SchemaContentPayloadHash{}, + Envelope: &models.IntotoV002SchemaContentEnvelope{ + PayloadType: v.IntotoObj.Content.Envelope.PayloadType, + Signatures: v.IntotoObj.Content.Envelope.Signatures, + }, + Hash: v.IntotoObj.Content.Hash, + PayloadHash: v.IntotoObj.Content.PayloadHash, }, } - - canonicalEntry.Content.Envelope.PayloadType = v.IntotoObj.Content.Envelope.PayloadType - canonicalEntry.Content.Envelope.Signatures = v.IntotoObj.Content.Envelope.Signatures - canonicalEntry.Content.Hash = v.IntotoObj.Content.Hash - canonicalEntry.Content.PayloadHash = v.IntotoObj.Content.PayloadHash - itObj := models.Intoto{} itObj.APIVersion = swag.String(APIVERSION) itObj.Spec = &canonicalEntry @@ -314,31 +310,23 @@ func (v V002Entry) CreateFromArtifactProperties(_ context.Context, props types.A } allPubKeyBytes := make([][]byte, 0) - if props.PublicKeyBytes != nil { - allPubKeyBytes = append(allPubKeyBytes, props.PublicKeyBytes) + if len(props.PublicKeyBytes) > 0 { + allPubKeyBytes = append(allPubKeyBytes, props.PublicKeyBytes...) } - allPubKeyBytes = append(allPubKeyBytes, props.MultiPublicKeyBytes...) - allPubKeyPaths := make([]*url.URL, 0) - if props.PublicKeyPath != nil { - allPubKeyPaths = append(allPubKeyPaths, props.PublicKeyPath) - } - - if props.MultiPublicKeyPaths != nil { - allPubKeyPaths = append(allPubKeyPaths, props.MultiPublicKeyPaths...) - } + if len(props.PublicKeyPath) > 0 { + for _, path := range props.PublicKeyPath { + if path.IsAbs() { + return nil, errors.New("dsse public keys cannot be fetched over HTTP(S)") + } - for _, path := range allPubKeyPaths { - if path.IsAbs() { - return nil, errors.New("dsse public keys cannot be fetched over HTTP(S)") - } + publicKeyBytes, err := ioutil.ReadFile(filepath.Clean(path.Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } - publicKeyBytes, err := ioutil.ReadFile(filepath.Clean(path.Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) + allPubKeyBytes = append(allPubKeyBytes, publicKeyBytes) } - - allPubKeyBytes = append(allPubKeyBytes, publicKeyBytes) } keysBySig, err := verifyEnvelope(allPubKeyBytes, &env) @@ -346,7 +334,8 @@ func (v V002Entry) CreateFromArtifactProperties(_ context.Context, props types.A return nil, err } - re.IntotoObj.Content.Envelope.Payload = swag.String(env.Payload) + b64 := strfmt.Base64([]byte(env.Payload)) + re.IntotoObj.Content.Envelope.Payload = &b64 re.IntotoObj.Content.Envelope.PayloadType = &env.PayloadType for _, sig := range env.Signatures { @@ -363,7 +352,7 @@ func (v V002Entry) CreateFromArtifactProperties(_ context.Context, props types.A keyBytes := strfmt.Base64(canonKey) re.IntotoObj.Content.Envelope.Signatures = append(re.IntotoObj.Content.Envelope.Signatures, &models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ Keyid: sig.KeyID, - Sig: sig.Sig, + Sig: strfmt.Base64([]byte(sig.Sig)), PublicKey: keyBytes, }) } diff --git a/pkg/types/intoto/v0.0.2/entry_test.go b/pkg/types/intoto/v0.0.2/entry_test.go index a9c8a1414..e39d9eb23 100644 --- a/pkg/types/intoto/v0.0.2/entry_test.go +++ b/pkg/types/intoto/v0.0.2/entry_test.go @@ -102,16 +102,16 @@ func multiSignEnvelope(t *testing.T, k []*ecdsa.PrivateKey, payload []byte) *dss func createRekorEnvelope(dsseEnv *dsse.Envelope, pub [][]byte) *models.IntotoV002SchemaContentEnvelope { - env := &models.IntotoV002SchemaContentEnvelope{ - Payload: swag.String(dsseEnv.Payload), - PayloadType: &dsseEnv.PayloadType, - } + env := &models.IntotoV002SchemaContentEnvelope{} + b64 := strfmt.Base64([]byte(dsseEnv.Payload)) + env.Payload = &b64 + env.PayloadType = &dsseEnv.PayloadType for i, sig := range dsseEnv.Signatures { env.Signatures = append(env.Signatures, &models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ Keyid: sig.KeyID, - Sig: sig.Sig, - PublicKey: pub[i], + Sig: strfmt.Base64([]byte(sig.Sig)), + PublicKey: strfmt.Base64(pub[i]), }) } return env diff --git a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json index 0f79c1547..a6fe9cbd6 100644 --- a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json +++ b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json @@ -15,6 +15,7 @@ "payload": { "description": "payload of the envelope", "type": "string", + "format": "byte", "writeOnly": true }, "payloadType": { @@ -35,7 +36,8 @@ }, "sig": { "description": "signature of the payload", - "type": "string" + "type": "string", + "format": "byte" }, "publicKey": { "description": "public key that corresponds to this signature", @@ -82,7 +84,7 @@ "enum": [ "sha256" ] }, "value": { - "description": "The hash value of the PAE encoded payload", + "description": "The hash value of the payload", "type": "string" } }, diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index 4c566a054..e54b4a381 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -391,17 +391,23 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.RekordObj.Signature.PublicKey = &models.RekordV001SchemaSignaturePublicKey{} publicKeyBytes := props.PublicKeyBytes - if publicKeyBytes == nil { - if props.PublicKeyPath == nil { + if len(publicKeyBytes) == 0 { + if len(props.PublicKeyPath) == 0 { return nil, errors.New("public key must be provided to verify detached signature") } - publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) + if len(props.PublicKeyPath) > 1 { + return nil, errors.New("only one public key must be provided") + } + if len(props.PublicKeyPath) == 1 { + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + publicKeyBytes = append(publicKeyBytes, keyBytes) + re.RekordObj.Signature.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) } - re.RekordObj.Signature.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes) } else { - re.RekordObj.Signature.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes) + re.RekordObj.Signature.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) } if err := re.validate(); err != nil { diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index b8b00a8f5..10587b09d 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -342,17 +342,23 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.RPMModel.PublicKey = &models.RpmV001SchemaPublicKey{} publicKeyBytes := props.PublicKeyBytes - if publicKeyBytes == nil { - if props.PublicKeyPath == nil { + if len(publicKeyBytes) == 0 { + if len(props.PublicKeyPath) == 0 { return nil, errors.New("public key must be provided to verify RPM signature") } - publicKeyBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) + if len(props.PublicKeyPath) > 1 { + return nil, errors.New("only one public key must be provided") + } + if len(props.PublicKeyPath) == 1 { + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + publicKeyBytes = append(publicKeyBytes, keyBytes) + re.RPMModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) } - re.RPMModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes) } else { - re.RPMModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes) + re.RPMModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) } if err := re.validate(); err != nil { diff --git a/pkg/types/tuf/v0.0.1/entry.go b/pkg/types/tuf/v0.0.1/entry.go index e53e069a9..029219b47 100644 --- a/pkg/types/tuf/v0.0.1/entry.go +++ b/pkg/types/tuf/v0.0.1/entry.go @@ -333,22 +333,28 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types rootBytes := props.PublicKeyBytes re.TufObj.Root = &models.TUFV001SchemaRoot{} - if rootBytes == nil { - if props.PublicKeyPath == nil { + if len(rootBytes) == 0 { + if len(props.PublicKeyPath) == 0 { return nil, errors.New("path to root file must be specified") } - rootBytes, err = ioutil.ReadFile(filepath.Clean(props.PublicKeyPath.Path)) - if err != nil { - return nil, fmt.Errorf("error reading root file: %w", err) + if len(props.PublicKeyPath) > 1 { + return nil, errors.New("only one path to root file must be specified") + } + if len(props.PublicKeyPath) == 1 { + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading root file: %w", err) + } + rootBytes = append(rootBytes, keyBytes) } s := &data.Signed{} - if err := json.Unmarshal(rootBytes, s); err != nil { + if err := json.Unmarshal(rootBytes[0], s); err != nil { return nil, err } re.TufObj.Root.Content = s } else { s := &data.Signed{} - if err := json.Unmarshal(rootBytes, s); err != nil { + if err := json.Unmarshal(rootBytes[0], s); err != nil { return nil, err } re.TufObj.Root.Content = s diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 8c4cfd512..b762b9220 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -23,6 +23,7 @@ import ( "context" "crypto" "crypto/ecdsa" + "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" @@ -496,14 +497,20 @@ func TestIntoto(t *testing.T) { if err != nil { t.Fatal(err) } - signer, err := dsse.NewEnvelopeSigner(&IntotoSigner{ - priv: priv.(*ecdsa.PrivateKey), + + s, err := signature.LoadECDSASigner(priv.(*ecdsa.PrivateKey), crypto.SHA256) + if err != nil { + t.Fatal(err) + } + + signer, err := dsse.NewEnvelopeSigner(&verifier{ + s: s, }) if err != nil { t.Fatal(err) } - env, err := signer.SignPayload("application/vnd.in-toto+json", b) + env, err := signer.SignPayload(in_toto.PayloadType, b) if err != nil { t.Fatal(err) } @@ -563,7 +570,8 @@ func TestIntoto(t *testing.T) { func TestIntotoV002(t *testing.T) { td := t.TempDir() attestationPath := filepath.Join(td, "attestation.json") - pubKeyPath := filepath.Join(td, "pub.pem") + ecdsapubKeyPath := filepath.Join(td, "ecdsapub.pem") + rsapubKeyPath := filepath.Join(td, "rsapub.pem") // Get some random data so it's unique each run d := randomData(t, 10) @@ -594,19 +602,44 @@ func TestIntotoV002(t *testing.T) { t.Fatal(err) } + evps := []*verifier{} + pb, _ := pem.Decode([]byte(ecdsaPriv)) priv, err := x509.ParsePKCS8PrivateKey(pb.Bytes) if err != nil { t.Fatal(err) } - signer, err := dsse.NewEnvelopeSigner(&IntotoSigner{ - priv: priv.(*ecdsa.PrivateKey), + + signECDSA, err := signature.LoadECDSASigner(priv.(*ecdsa.PrivateKey), crypto.SHA256) + if err != nil { + t.Fatal(err) + } + + evps = append(evps, &verifier{ + s: signECDSA, + }) + + pbRSA, _ := pem.Decode([]byte(rsaKey)) + rsaPriv, err := x509.ParsePKCS8PrivateKey(pbRSA.Bytes) + if err != nil { + t.Fatal(err) + } + + signRSA, err := signature.LoadRSAPKCS1v15Signer(rsaPriv.(*rsa.PrivateKey), crypto.SHA256) + if err != nil { + t.Fatal(err) + } + + evps = append(evps, &verifier{ + s: signRSA, }) + + signer, err := dsse.NewMultiEnvelopeSigner(2, evps[0], evps[1]) if err != nil { t.Fatal(err) } - env, err := signer.SignPayload("application/vnd.in-toto+json", b) + env, err := signer.SignPayload(in_toto.PayloadType, b) if err != nil { t.Fatal(err) } @@ -617,10 +650,11 @@ func TestIntotoV002(t *testing.T) { } write(t, string(eb), attestationPath) - write(t, ecdsaPub, pubKeyPath) + write(t, ecdsaPub, ecdsapubKeyPath) + write(t, pubKey, rsapubKeyPath) // If we do it twice, it should already exist - out := runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", pubKeyPath) + out := runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", ecdsapubKeyPath, "--public-key", rsapubKeyPath) outputContains(t, out, "Created entry at") uuid := getUUIDFromUploadOutput(t, out) @@ -658,7 +692,7 @@ func TestIntotoV002(t *testing.T) { *intotoV002Model.Content.PayloadHash.Value)) } - out = runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", pubKeyPath) + out = runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", ecdsapubKeyPath, "--public-key", rsapubKeyPath) outputContains(t, out, "Entry already exists") } diff --git a/tests/harness_test.go b/tests/harness_test.go index 3286cba0a..1f598dd31 100644 --- a/tests/harness_test.go +++ b/tests/harness_test.go @@ -19,6 +19,7 @@ package e2e import ( "bytes" + "crypto" "crypto/ecdsa" "crypto/sha256" "crypto/x509" @@ -42,6 +43,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/sharding" "github.com/sigstore/rekor/pkg/types" + "github.com/sigstore/sigstore/pkg/signature" ) type StoredEntry struct { @@ -121,8 +123,14 @@ func TestHarnessAddIntoto(t *testing.T) { if err != nil { t.Fatal(err) } - signer, err := dsse.NewEnvelopeSigner(&IntotoSigner{ - priv: priv.(*ecdsa.PrivateKey), + + s, err := signature.LoadECDSASigner(priv.(*ecdsa.PrivateKey), crypto.SHA256) + if err != nil { + t.Fatal(err) + } + + signer, err := dsse.NewEnvelopeSigner(&verifier{ + s: s, }) if err != nil { t.Fatal(err) diff --git a/tests/intoto_multi_dsse.json b/tests/intoto_multi_dsse.json index 7ddfa907c..7e245117a 100644 --- a/tests/intoto_multi_dsse.json +++ b/tests/intoto_multi_dsse.json @@ -1,6 +1,6 @@ {"payloadType":"application/vnd.in-toto+json", -"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJmb29iYXIiLCJkaWdlc3QiOnsiZm9vIjoiYmFyIn19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJmb29ISzFiZ2Y1WC8xckNxZz09In0sImJ1aWxkVHlwZSI6IiIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", +"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJmb29iYXIiLCJkaWdlc3QiOnsiZm9vIjoiYmFyIn19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJmb29BNi9QWW1CNmNCQXRZQT09In0sImJ1aWxkVHlwZSI6IiIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", "signatures":[{ - "keyid":"first","sig":"MEQCIAIlnxHC3eU4jmUsqJExxfzqyy8bk+61btgnRiGcRDxgAiBwmdnJ/GX1yCYhYAvwAtkuYN0yFlVPQVAx9R6JpUUBiA=="}, - {"keyid":"second","sig":"MEQCIAIlnxHC3eU4jmUsqJExxfzqyy8bk+61btgnRiGcRDxgAiBwmdnJ/GX1yCYhYAvwAtkuYN0yFlVPQVAx9R6JpUUBiA=="}] + "keyid":"","sig":"MEUCIQC4/1MwOahpGT/oh6DZFuwZiG9yXpfo22wh+7mGn9lQ4gIgIL2LFt0lxYbFngf4ze/FNvkybYzwtmkX2XrcnGMpOqo="}, + {"keyid":"","sig":"PhNAdsvKEVqv+cF/QvjEOz+fTtoNWQfa9gCWnOpm5yWVFRiu6he5jvw6A8ESRXxV3KnEcyBFCfCbITNK2fpXQEOc0gNDcsil1m/Pzv/JonhtH/TwIjdJ8zy4WEUVHZMfj5IIeibp3U2ACvmCc9HEUPCc6VM0hq7ri/VnKLcCCGbKmxwHrVXArv1hKBrcP7s52tuxCsVr5+3Z7eKPx5WkycOhpUhSVhMHjOCj9e3mveiw4dYuwdrgQHtqZJhUg0WFlVCQTQdcLxIII1g7BudA38yzfOgkbwgfoZ2Dh3iF7uRHIUdFxxpqF2oQeU2uDv3dT9cCfRd/kDtU6hbuVgixgw=="}] } diff --git a/tests/intoto_multi_pub2.pem b/tests/intoto_multi_pub2.pem new file mode 100644 index 000000000..313580684 --- /dev/null +++ b/tests/intoto_multi_pub2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3wqI/TysUiKTgY1bz+wd +JfEOil4MEsRASKGzJddZ6x9hb+rn2UVoJmuxN62XI0TMoMn4mukgfCgY6jgTB58V ++/LaeSA8Wz1p4gOxhk1mcgbF4HyxR+xlRgYfH4iSbXy+Ez/8ZjM2OO68fKr4JZEA +5LXZkhJr32JqH+UiFw/wgSPWA8aV0AfRAXHdekJ48B1ChxJTrOJWSPTnj/E0lfLV +srJKtXDuC8T0vFmVU726tI6fODsEE6VrSahvw1ENUHzI34sbfrmrggwPO4iMAQvq +wu2gn2lx6ajWsh806FItiXN+DuizMnx4KMBI0IJynoQpWOFbstGiV0LygZkQ6soz +vwIDAQAB +-----END PUBLIC KEY----- diff --git a/tests/x509.go b/tests/x509.go index 0ffefbaae..d4a8de18f 100644 --- a/tests/x509.go +++ b/tests/x509.go @@ -19,8 +19,8 @@ package e2e import ( + "bytes" "crypto" - "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha256" @@ -30,7 +30,8 @@ import ( "io/ioutil" "testing" - "github.com/secure-systems-lab/go-securesystemslib/dsse" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" ) // Generated with: @@ -156,34 +157,63 @@ func createdX509SignedArtifact(t *testing.T, artifactPath, sigPath string) { } } -type IntotoSigner struct { - priv *ecdsa.PrivateKey +type verifier struct { + s signature.Signer + v signature.Verifier } -var _ dsse.SignVerifier = &IntotoSigner{} +func (v *verifier) KeyID() (string, error) { + return "", nil +} + +func (v *verifier) Public() crypto.PublicKey { + return v.v.PublicKey +} -func (it *IntotoSigner) Sign(data []byte) ([]byte, error) { - h := sha256.Sum256(data) - sig, err := it.priv.Sign(rand.Reader, h[:], crypto.SHA256) +func (v *verifier) Sign(data []byte) (sig []byte, err error) { + if v.s == nil { + return nil, errors.New("nil signer") + } + sig, err = v.s.SignMessage(bytes.NewReader(data), options.WithCryptoSignerOpts(crypto.SHA256)) if err != nil { return nil, err } return sig, nil } -func (it *IntotoSigner) KeyID() (string, error) { - return "", nil -} - -func (it *IntotoSigner) Public() crypto.PublicKey { - return it.priv.Public() -} - -func (it *IntotoSigner) Verify(data, sig []byte) error { - h := sha256.Sum256(data) - ok := ecdsa.VerifyASN1(&it.priv.PublicKey, h[:], sig) - if ok { - return nil +func (v *verifier) Verify(data, sig []byte) error { + if v.v == nil { + return errors.New("nil verifier") } - return errors.New("invalid signature") + return v.v.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data)) } + +// type IntotoSigner struct { +// priv *ecdsa.PrivateKey +// } + +// func (it *IntotoSigner) Sign(data []byte) ([]byte, error) { +// h := sha256.Sum256(data) +// sig, err := it.priv.Sign(rand.Reader, h[:], crypto.SHA256) +// if err != nil { +// return nil, err +// } +// return sig, nil +// } + +// func (it *IntotoSigner) KeyID() (string, error) { +// return "", nil +// } + +// func (it *IntotoSigner) Public() crypto.PublicKey { +// return it.priv.Public() +// } + +// func (it *IntotoSigner) Verify(data, sig []byte) error { +// h := sha256.Sum256(data) +// ok := ecdsa.VerifyASN1(&it.priv.PublicKey, h[:], sig) +// if ok { +// return nil +// } +// return errors.New("invalid signature") +// } From 3070576b2473da87e4bba312992f495902b19659 Mon Sep 17 00:00:00 2001 From: pxp928 Date: Thu, 25 Aug 2022 15:31:53 -0400 Subject: [PATCH 5/8] changes to if statements Signed-off-by: pxp928 --- cmd/rekor-cli/app/search.go | 2 ++ pkg/generated/models/intoto_v002_schema.go | 16 +--------- pkg/generated/restapi/embedded_spec.go | 3 -- pkg/types/alpine/v0.0.1/entry.go | 20 ++++++------ pkg/types/cose/v0.0.1/entry.go | 23 ++++++-------- pkg/types/hashedrekord/v0.0.1/entry.go | 20 +++++------- pkg/types/helm/v0.0.1/entry.go | 18 +++++------ pkg/types/intoto/v0.0.1/entry.go | 19 +++++------- pkg/types/intoto/v0.0.2/entry.go | 31 +++++++++++-------- pkg/types/intoto/v0.0.2/entry_test.go | 2 +- .../intoto/v0.0.2/intoto_v0_0_2_schema.json | 2 +- pkg/types/rekord/v0.0.1/entry.go | 24 ++++++-------- pkg/types/rpm/v0.0.1/entry.go | 24 ++++++-------- tests/e2e_test.go | 4 +-- tests/harness_test.go | 2 +- tests/x509.go | 30 ------------------ 16 files changed, 88 insertions(+), 152 deletions(-) diff --git a/cmd/rekor-cli/app/search.go b/cmd/rekor-cli/app/search.go index b59639c45..1e85dbd8b 100644 --- a/cmd/rekor-cli/app/search.go +++ b/cmd/rekor-cli/app/search.go @@ -176,6 +176,8 @@ var searchCmd = &cobra.Command{ } params.Query.PublicKey.Content = strfmt.Base64(keyBytes) } + } else { + return nil, errors.New("only one public key must be provided") } } diff --git a/pkg/generated/models/intoto_v002_schema.go b/pkg/generated/models/intoto_v002_schema.go index 3b95dab47..3e3b7bb69 100644 --- a/pkg/generated/models/intoto_v002_schema.go +++ b/pkg/generated/models/intoto_v002_schema.go @@ -314,9 +314,8 @@ func (m *IntotoV002SchemaContent) UnmarshalBinary(b []byte) error { type IntotoV002SchemaContentEnvelope struct { // payload of the envelope - // Required: true // Format: byte - Payload *strfmt.Base64 `json:"payload"` + Payload strfmt.Base64 `json:"payload,omitempty"` // type describing the payload // Required: true @@ -332,10 +331,6 @@ type IntotoV002SchemaContentEnvelope struct { func (m *IntotoV002SchemaContentEnvelope) Validate(formats strfmt.Registry) error { var res []error - if err := m.validatePayload(formats); err != nil { - res = append(res, err) - } - if err := m.validatePayloadType(formats); err != nil { res = append(res, err) } @@ -350,15 +345,6 @@ func (m *IntotoV002SchemaContentEnvelope) Validate(formats strfmt.Registry) erro return nil } -func (m *IntotoV002SchemaContentEnvelope) validatePayload(formats strfmt.Registry) error { - - if err := validate.Required("content"+"."+"envelope"+"."+"payload", "body", m.Payload); err != nil { - return err - } - - return nil -} - func (m *IntotoV002SchemaContentEnvelope) validatePayloadType(formats strfmt.Registry) error { if err := validate.Required("content"+"."+"envelope"+"."+"payloadType", "body", m.PayloadType); err != nil { diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index b7f6261b5..ef318051e 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -1864,7 +1864,6 @@ func init() { "description": "dsse envelope", "type": "object", "required": [ - "payload", "payloadType", "signatures" ], @@ -1940,7 +1939,6 @@ func init() { "description": "dsse envelope", "type": "object", "required": [ - "payload", "payloadType", "signatures" ], @@ -3162,7 +3160,6 @@ func init() { "description": "dsse envelope", "type": "object", "required": [ - "payload", "payloadType", "signatures" ], diff --git a/pkg/types/alpine/v0.0.1/entry.go b/pkg/types/alpine/v0.0.1/entry.go index 28f00918f..cc0b88e47 100644 --- a/pkg/types/alpine/v0.0.1/entry.go +++ b/pkg/types/alpine/v0.0.1/entry.go @@ -323,22 +323,20 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.AlpineModel.PublicKey = &models.AlpineV001SchemaPublicKey{} publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) > 1 { + if len(props.PublicKeyPath) != 1 { return nil, errors.New("only one public key must be provided") } - if len(props.PublicKeyPath) == 1 { - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - publicKeyBytes = append(publicKeyBytes, keyBytes) - re.AlpineModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) } - - } else { - re.AlpineModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + publicKeyBytes = append(publicKeyBytes, keyBytes) + } else if len(publicKeyBytes) != 1 { + return nil, errors.New("only one public key byte must be provided") } + re.AlpineModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + if err := re.validate(); err != nil { return nil, err } diff --git a/pkg/types/cose/v0.0.1/entry.go b/pkg/types/cose/v0.0.1/entry.go index 2638cba74..6e86a51dc 100644 --- a/pkg/types/cose/v0.0.1/entry.go +++ b/pkg/types/cose/v0.0.1/entry.go @@ -317,23 +317,18 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A } publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) == 0 { - return nil, errors.New("public key must be provided to verify signature") + if len(props.PublicKeyPath) != 1 { + return nil, errors.New("only one public key must be provided to verify signature") } - if len(props.PublicKeyPath) > 1 { - return nil, errors.New("only one public key must be provided") - } - if len(props.PublicKeyPath) == 1 { - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - publicKeyBytes = append(publicKeyBytes, keyBytes) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) } + publicKeyBytes = append(publicKeyBytes, keyBytes) + } else if len(publicKeyBytes) != 1 { + return nil, errors.New("only one public key byte must be provided") } - if err != nil { - return nil, err - } + kb := strfmt.Base64(publicKeyBytes[0]) mb := strfmt.Base64(messageBytes) diff --git a/pkg/types/hashedrekord/v0.0.1/entry.go b/pkg/types/hashedrekord/v0.0.1/entry.go index 88ac83e83..a0df10687 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry.go +++ b/pkg/types/hashedrekord/v0.0.1/entry.go @@ -218,23 +218,19 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.HashedRekordObj.Signature.PublicKey = &models.HashedrekordV001SchemaSignaturePublicKey{} publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) == 0 { - return nil, errors.New("public key must be provided to verify detached signature") + if len(props.PublicKeyPath) != 1 { + return nil, errors.New("only one public key must be provided to verify detached signature") } - if len(props.PublicKeyPath) > 1 { - return nil, errors.New("only one public key must be provided") - } - if len(props.PublicKeyPath) == 1 { - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - publicKeyBytes = append(publicKeyBytes, keyBytes) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) } + publicKeyBytes = append(publicKeyBytes, keyBytes) + } else if len(publicKeyBytes) != 1 { + return nil, errors.New("only one public key byte must be provided") } re.HashedRekordObj.Signature.PublicKey.Content = strfmt.Base64(publicKeyBytes[0]) - re.HashedRekordObj.Data.Hash = &models.HashedrekordV001SchemaDataHash{ Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), Value: swag.String(props.ArtifactHash), diff --git a/pkg/types/helm/v0.0.1/entry.go b/pkg/types/helm/v0.0.1/entry.go index 1f0056bda..2f1f1f0ea 100644 --- a/pkg/types/helm/v0.0.1/entry.go +++ b/pkg/types/helm/v0.0.1/entry.go @@ -318,21 +318,19 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.HelmObj.PublicKey = &models.HelmV001SchemaPublicKey{} publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) > 1 { + if len(props.PublicKeyPath) != 1 { return nil, errors.New("only one public key must be provided") } - if len(props.PublicKeyPath) == 1 { - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - publicKeyBytes = append(publicKeyBytes, keyBytes) - re.HelmObj.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) } - } else { - re.HelmObj.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + publicKeyBytes = append(publicKeyBytes, keyBytes) + } else if len(publicKeyBytes) != 1 { + return nil, errors.New("only one public key byte must be provided") } + re.HelmObj.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) if err := re.validate(); 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 8aa3471f5..deb4b55eb 100644 --- a/pkg/types/intoto/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.1/entry.go @@ -293,19 +293,16 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A } publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) == 0 { - return nil, errors.New("public key must be provided to verify signature") + if len(props.PublicKeyPath) != 1 { + return nil, errors.New("only one public key must be provided to verify signature") } - if len(props.PublicKeyPath) > 1 { - return nil, errors.New("only one public key must be provided") - } - if len(props.PublicKeyPath) == 1 { - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - publicKeyBytes = append(publicKeyBytes, keyBytes) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) } + publicKeyBytes = append(publicKeyBytes, keyBytes) + } else if len(publicKeyBytes) != 1 { + return nil, errors.New("only one public key byte must be provided") } kb := strfmt.Base64(publicKeyBytes[0]) diff --git a/pkg/types/intoto/v0.0.2/entry.go b/pkg/types/intoto/v0.0.2/entry.go index da037d50d..b0c131ff5 100644 --- a/pkg/types/intoto/v0.0.2/entry.go +++ b/pkg/types/intoto/v0.0.2/entry.go @@ -77,31 +77,36 @@ func (v V002Entry) IndexKeys() ([]string, error) { } for _, sig := range v.IntotoObj.Content.Envelope.Signatures { - keyHash := sha256.Sum256(sig.PublicKey) - result = append(result, "sha256:"+hex.EncodeToString(keyHash[:])) - keyObj, err := x509.NewPublicKey(bytes.NewReader(sig.PublicKey)) if err != nil { - return nil, err + return result, err } + canonKey, err := keyObj.CanonicalValue() + if err != nil { + return result, fmt.Errorf("could not canonicize key: %w", err) + } + + keyHash := sha256.Sum256(canonKey) + result = append(result, "sha256:"+hex.EncodeToString(keyHash[:])) + result = append(result, keyObj.Subjects()...) } payloadKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.PayloadHash.Algorithm, *v.IntotoObj.Content.PayloadHash.Value)) result = append(result, payloadKey) + hashkey := strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.Hash.Algorithm, *v.IntotoObj.Content.Hash.Value)) + result = append(result, hashkey) + switch *v.IntotoObj.Content.Envelope.PayloadType { case in_toto.PayloadType: - hashkey := strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.Hash.Algorithm, *v.IntotoObj.Content.Hash.Value)) - result = append(result, hashkey) - if v.IntotoObj.Content.Envelope.Payload == nil { log.Logger.Info("IntotoObj DSSE payload is empty") return result, nil } - decodedPayload, err := base64.StdEncoding.DecodeString(string(*v.IntotoObj.Content.Envelope.Payload)) + decodedPayload, err := base64.StdEncoding.DecodeString(string(v.IntotoObj.Content.Envelope.Payload)) if err != nil { return result, fmt.Errorf("could not decode envelope payload: %w", err) } @@ -164,12 +169,12 @@ func (v *V002Entry) Unmarshal(pe models.ProposedEntry) error { return err } - if string(*v.IntotoObj.Content.Envelope.Payload) == "" { - return errors.New("DSSE envelope does not contain a payload") + if string(v.IntotoObj.Content.Envelope.Payload) == "" { + return nil } env := &dsse.Envelope{ - Payload: string(*v.IntotoObj.Content.Envelope.Payload), + Payload: string(v.IntotoObj.Content.Envelope.Payload), PayloadType: *v.IntotoObj.Content.Envelope.PayloadType, } @@ -189,7 +194,7 @@ func (v *V002Entry) Unmarshal(pe models.ProposedEntry) error { v.env = *env - decodedPayload, err := base64.StdEncoding.DecodeString(string(*v.IntotoObj.Content.Envelope.Payload)) + decodedPayload, err := base64.StdEncoding.DecodeString(string(v.IntotoObj.Content.Envelope.Payload)) if err != nil { return fmt.Errorf("could not decode envelope payload: %w", err) } @@ -335,7 +340,7 @@ func (v V002Entry) CreateFromArtifactProperties(_ context.Context, props types.A } b64 := strfmt.Base64([]byte(env.Payload)) - re.IntotoObj.Content.Envelope.Payload = &b64 + re.IntotoObj.Content.Envelope.Payload = b64 re.IntotoObj.Content.Envelope.PayloadType = &env.PayloadType for _, sig := range env.Signatures { diff --git a/pkg/types/intoto/v0.0.2/entry_test.go b/pkg/types/intoto/v0.0.2/entry_test.go index e39d9eb23..eb8285a67 100644 --- a/pkg/types/intoto/v0.0.2/entry_test.go +++ b/pkg/types/intoto/v0.0.2/entry_test.go @@ -104,7 +104,7 @@ func createRekorEnvelope(dsseEnv *dsse.Envelope, pub [][]byte) *models.IntotoV00 env := &models.IntotoV002SchemaContentEnvelope{} b64 := strfmt.Base64([]byte(dsseEnv.Payload)) - env.Payload = &b64 + env.Payload = b64 env.PayloadType = &dsseEnv.PayloadType for i, sig := range dsseEnv.Signatures { diff --git a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json index a6fe9cbd6..63f18bdb4 100644 --- a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json +++ b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json @@ -49,7 +49,7 @@ } } }, - "required": ["payload", "payloadType", "signatures"], + "required": ["payloadType", "signatures"], "writeOnly": true }, "hash": { diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index e54b4a381..e43d7a98e 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -392,24 +392,20 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.RekordObj.Signature.PublicKey = &models.RekordV001SchemaSignaturePublicKey{} publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) == 0 { - return nil, errors.New("public key must be provided to verify detached signature") + if len(props.PublicKeyPath) != 1 { + return nil, errors.New("only one public key must be provided to verify detached signature") } - if len(props.PublicKeyPath) > 1 { - return nil, errors.New("only one public key must be provided") - } - if len(props.PublicKeyPath) == 1 { - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - publicKeyBytes = append(publicKeyBytes, keyBytes) - re.RekordObj.Signature.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) } - } else { - re.RekordObj.Signature.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + publicKeyBytes = append(publicKeyBytes, keyBytes) + } else if len(publicKeyBytes) != 1 { + return nil, errors.New("only one public key byte must be provided") } + re.RekordObj.Signature.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + if err := re.validate(); err != nil { return nil, err } diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index 10587b09d..d47f35dc8 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -343,24 +343,20 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.RPMModel.PublicKey = &models.RpmV001SchemaPublicKey{} publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) == 0 { - return nil, errors.New("public key must be provided to verify RPM signature") + if len(props.PublicKeyPath) != 1 { + return nil, errors.New("only one public key must be provided to verify RPM signature") } - if len(props.PublicKeyPath) > 1 { - return nil, errors.New("only one public key must be provided") - } - if len(props.PublicKeyPath) == 1 { - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - publicKeyBytes = append(publicKeyBytes, keyBytes) - re.RPMModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) } - } else { - re.RPMModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + publicKeyBytes = append(publicKeyBytes, keyBytes) + } else if len(publicKeyBytes) != 1 { + return nil, errors.New("only one public key byte must be provided") } + re.RPMModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) + if err := re.validate(); err != nil { return nil, err } diff --git a/tests/e2e_test.go b/tests/e2e_test.go index b762b9220..535497856 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -545,7 +545,7 @@ func TestIntoto(t *testing.T) { attHash := sha256.Sum256(b) - intotoModel := &models.IntotoV001Schema{} + intotoModel := &models.IntotoV002Schema{} if err := types.DecodeEntry(g.Body.(map[string]interface{})["IntotoObj"], intotoModel); err != nil { t.Errorf("could not convert body into intoto type: %v", err) } @@ -567,7 +567,7 @@ func TestIntoto(t *testing.T) { } -func TestIntotoV002(t *testing.T) { +func TestIntotoMultiSig(t *testing.T) { td := t.TempDir() attestationPath := filepath.Join(td, "attestation.json") ecdsapubKeyPath := filepath.Join(td, "ecdsapub.pem") diff --git a/tests/harness_test.go b/tests/harness_test.go index 1f598dd31..88441c41f 100644 --- a/tests/harness_test.go +++ b/tests/harness_test.go @@ -172,7 +172,7 @@ func TestHarnessAddIntoto(t *testing.T) { attHash := sha256.Sum256(b) - intotoModel := &models.IntotoV001Schema{} + intotoModel := &models.IntotoV002Schema{} if err := types.DecodeEntry(g.Body.(map[string]interface{})["IntotoObj"], intotoModel); err != nil { t.Errorf("could not convert body into intoto type: %v", err) } diff --git a/tests/x509.go b/tests/x509.go index d4a8de18f..81914b9fe 100644 --- a/tests/x509.go +++ b/tests/x509.go @@ -187,33 +187,3 @@ func (v *verifier) Verify(data, sig []byte) error { } return v.v.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data)) } - -// type IntotoSigner struct { -// priv *ecdsa.PrivateKey -// } - -// func (it *IntotoSigner) Sign(data []byte) ([]byte, error) { -// h := sha256.Sum256(data) -// sig, err := it.priv.Sign(rand.Reader, h[:], crypto.SHA256) -// if err != nil { -// return nil, err -// } -// return sig, nil -// } - -// func (it *IntotoSigner) KeyID() (string, error) { -// return "", nil -// } - -// func (it *IntotoSigner) Public() crypto.PublicKey { -// return it.priv.Public() -// } - -// func (it *IntotoSigner) Verify(data, sig []byte) error { -// h := sha256.Sum256(data) -// ok := ecdsa.VerifyASN1(&it.priv.PublicKey, h[:], sig) -// if ok { -// return nil -// } -// return errors.New("invalid signature") -// } From 4c10b1aed4dcac8bff606542d4844622182c8d15 Mon Sep 17 00:00:00 2001 From: pxp928 Date: Fri, 26 Aug 2022 08:35:38 -0400 Subject: [PATCH 6/8] changed to PublicKeyPaths Signed-off-by: pxp928 --- cmd/rekor-cli/app/pflag_groups.go | 2 +- pkg/types/alpine/v0.0.1/entry.go | 4 +-- pkg/types/cose/v0.0.1/entry.go | 4 +-- pkg/types/entries.go | 2 +- pkg/types/hashedrekord/v0.0.1/entry.go | 4 +-- pkg/types/helm/v0.0.1/entry.go | 4 +-- pkg/types/intoto/v0.0.1/entry.go | 4 +-- pkg/types/intoto/v0.0.2/entry.go | 4 +-- pkg/types/rekord/v0.0.1/entry.go | 4 +-- pkg/types/rpm/v0.0.1/entry.go | 4 +-- pkg/types/tuf/v0.0.1/entry.go | 35 +++++++++++--------------- 11 files changed, 32 insertions(+), 39 deletions(-) diff --git a/cmd/rekor-cli/app/pflag_groups.go b/cmd/rekor-cli/app/pflag_groups.go index 8057a4d68..ccbf2be62 100644 --- a/cmd/rekor-cli/app/pflag_groups.go +++ b/cmd/rekor-cli/app/pflag_groups.go @@ -160,7 +160,7 @@ func CreatePropsFromPflags() *types.ArtifactProperties { collectedKeys = append(collectedKeys, &url.URL{Path: key}) } } - props.PublicKeyPath = collectedKeys + props.PublicKeyPaths = collectedKeys } props.PKIFormat = viper.GetString("pki-format") diff --git a/pkg/types/alpine/v0.0.1/entry.go b/pkg/types/alpine/v0.0.1/entry.go index cc0b88e47..db0050514 100644 --- a/pkg/types/alpine/v0.0.1/entry.go +++ b/pkg/types/alpine/v0.0.1/entry.go @@ -323,10 +323,10 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.AlpineModel.PublicKey = &models.AlpineV001SchemaPublicKey{} publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) != 1 { + if len(props.PublicKeyPaths) != 1 { return nil, errors.New("only one public key must be provided") } - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPaths[0].Path)) if err != nil { return nil, fmt.Errorf("error reading public key file: %w", err) } diff --git a/pkg/types/cose/v0.0.1/entry.go b/pkg/types/cose/v0.0.1/entry.go index 6e86a51dc..4427ff76d 100644 --- a/pkg/types/cose/v0.0.1/entry.go +++ b/pkg/types/cose/v0.0.1/entry.go @@ -317,10 +317,10 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A } publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) != 1 { + if len(props.PublicKeyPaths) != 1 { return nil, errors.New("only one public key must be provided to verify signature") } - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPaths[0].Path)) if err != nil { return nil, fmt.Errorf("error reading public key file: %w", err) } diff --git a/pkg/types/entries.go b/pkg/types/entries.go index fad4877fa..55559752d 100644 --- a/pkg/types/entries.go +++ b/pkg/types/entries.go @@ -152,7 +152,7 @@ type ArtifactProperties struct { ArtifactBytes []byte SignaturePath *url.URL SignatureBytes []byte - PublicKeyPath []*url.URL + PublicKeyPaths []*url.URL PublicKeyBytes [][]byte PKIFormat string } diff --git a/pkg/types/hashedrekord/v0.0.1/entry.go b/pkg/types/hashedrekord/v0.0.1/entry.go index a0df10687..0712b5939 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry.go +++ b/pkg/types/hashedrekord/v0.0.1/entry.go @@ -218,10 +218,10 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.HashedRekordObj.Signature.PublicKey = &models.HashedrekordV001SchemaSignaturePublicKey{} publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) != 1 { + if len(props.PublicKeyPaths) != 1 { return nil, errors.New("only one public key must be provided to verify detached signature") } - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPaths[0].Path)) if err != nil { return nil, fmt.Errorf("error reading public key file: %w", err) } diff --git a/pkg/types/helm/v0.0.1/entry.go b/pkg/types/helm/v0.0.1/entry.go index 2f1f1f0ea..4e5dd9242 100644 --- a/pkg/types/helm/v0.0.1/entry.go +++ b/pkg/types/helm/v0.0.1/entry.go @@ -318,10 +318,10 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.HelmObj.PublicKey = &models.HelmV001SchemaPublicKey{} publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) != 1 { + if len(props.PublicKeyPaths) != 1 { return nil, errors.New("only one public key must be provided") } - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPaths[0].Path)) if err != nil { return nil, fmt.Errorf("error reading public key file: %w", err) } diff --git a/pkg/types/intoto/v0.0.1/entry.go b/pkg/types/intoto/v0.0.1/entry.go index deb4b55eb..f2c2da866 100644 --- a/pkg/types/intoto/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.1/entry.go @@ -293,10 +293,10 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A } publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) != 1 { + if len(props.PublicKeyPaths) != 1 { return nil, errors.New("only one public key must be provided to verify signature") } - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPaths[0].Path)) if err != nil { return nil, fmt.Errorf("error reading public key file: %w", err) } diff --git a/pkg/types/intoto/v0.0.2/entry.go b/pkg/types/intoto/v0.0.2/entry.go index b0c131ff5..7313cce39 100644 --- a/pkg/types/intoto/v0.0.2/entry.go +++ b/pkg/types/intoto/v0.0.2/entry.go @@ -319,8 +319,8 @@ func (v V002Entry) CreateFromArtifactProperties(_ context.Context, props types.A allPubKeyBytes = append(allPubKeyBytes, props.PublicKeyBytes...) } - if len(props.PublicKeyPath) > 0 { - for _, path := range props.PublicKeyPath { + if len(props.PublicKeyPaths) > 0 { + for _, path := range props.PublicKeyPaths { if path.IsAbs() { return nil, errors.New("dsse public keys cannot be fetched over HTTP(S)") } diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index e43d7a98e..9c160a3e6 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -392,10 +392,10 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.RekordObj.Signature.PublicKey = &models.RekordV001SchemaSignaturePublicKey{} publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) != 1 { + if len(props.PublicKeyPaths) != 1 { return nil, errors.New("only one public key must be provided to verify detached signature") } - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPaths[0].Path)) if err != nil { return nil, fmt.Errorf("error reading public key file: %w", err) } diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index d47f35dc8..a754bce6a 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -343,10 +343,10 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types re.RPMModel.PublicKey = &models.RpmV001SchemaPublicKey{} publicKeyBytes := props.PublicKeyBytes if len(publicKeyBytes) == 0 { - if len(props.PublicKeyPath) != 1 { + if len(props.PublicKeyPaths) != 1 { return nil, errors.New("only one public key must be provided to verify RPM signature") } - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPaths[0].Path)) if err != nil { return nil, fmt.Errorf("error reading public key file: %w", err) } diff --git a/pkg/types/tuf/v0.0.1/entry.go b/pkg/types/tuf/v0.0.1/entry.go index 029219b47..f50964860 100644 --- a/pkg/types/tuf/v0.0.1/entry.go +++ b/pkg/types/tuf/v0.0.1/entry.go @@ -334,31 +334,24 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types rootBytes := props.PublicKeyBytes re.TufObj.Root = &models.TUFV001SchemaRoot{} if len(rootBytes) == 0 { - if len(props.PublicKeyPath) == 0 { - return nil, errors.New("path to root file must be specified") - } - if len(props.PublicKeyPath) > 1 { + if len(props.PublicKeyPaths) != 1 { return nil, errors.New("only one path to root file must be specified") } - if len(props.PublicKeyPath) == 1 { - keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPath[0].Path)) - if err != nil { - return nil, fmt.Errorf("error reading root file: %w", err) - } - rootBytes = append(rootBytes, keyBytes) - } - s := &data.Signed{} - if err := json.Unmarshal(rootBytes[0], s); err != nil { - return nil, err - } - re.TufObj.Root.Content = s - } else { - s := &data.Signed{} - if err := json.Unmarshal(rootBytes[0], s); err != nil { - return nil, err + keyBytes, err := ioutil.ReadFile(filepath.Clean(props.PublicKeyPaths[0].Path)) + if err != nil { + return nil, fmt.Errorf("error reading root file: %w", err) } - re.TufObj.Root.Content = s + rootBytes = append(rootBytes, keyBytes) + + } else if len(rootBytes) != 1 { + return nil, errors.New("only one root key byte must be provided") + } + + root := &data.Signed{} + if err := json.Unmarshal(rootBytes[0], root); err != nil { + return nil, err } + re.TufObj.Root.Content = root if err := re.Validate(); err != nil { return nil, err From 77a7cde8766c8182b019ecba58a632fe421ad239 Mon Sep 17 00:00:00 2001 From: pxp928 Date: Fri, 26 Aug 2022 17:26:40 -0400 Subject: [PATCH 7/8] added updated based on linked PRs Signed-off-by: pxp928 --- pkg/generated/restapi/embedded_spec.go | 173 +++++++++++++++++++++++++ pkg/types/intoto/intoto.go | 7 + pkg/types/intoto/intoto_schema.json | 3 + pkg/types/intoto/v0.0.2/entry_test.go | 34 +++++ tests/e2e-test.sh | 2 +- 5 files changed, 218 insertions(+), 1 deletion(-) diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index ef318051e..77da71031 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -1857,6 +1857,104 @@ func init() { } } }, + "IntotoV001SchemaContent": { + "type": "object", + "properties": { + "envelope": { + "description": "envelope", + "type": "string", + "writeOnly": true + }, + "hash": { + "description": "Specifies the hash algorithm and value encompassing the entire signed envelope", + "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 archive", + "type": "string" + } + }, + "readOnly": true + }, + "payloadHash": { + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", + "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 envelope's payload", + "type": "string" + } + }, + "readOnly": true + } + } + }, + "IntotoV001SchemaContentHash": { + "description": "Specifies the hash algorithm and value encompassing the entire signed envelope", + "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 archive", + "type": "string" + } + }, + "readOnly": true + }, + "IntotoV001SchemaContentPayloadHash": { + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", + "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 envelope's payload", + "type": "string" + } + }, + "readOnly": true + }, "IntotoV002SchemaContent": { "type": "object", "properties": { @@ -3138,6 +3236,9 @@ func init() { "type": "object", "title": "Intoto Schema", "oneOf": [ + { + "$ref": "#/definitions/intotoV001Schema" + }, { "$ref": "#/definitions/intotoV002Schema" } @@ -3145,6 +3246,78 @@ func init() { "$schema": "http://json-schema.org/draft-07/schema", "$id": "http://rekor.sigstore.dev/types/intoto/intoto_schema.json" }, + "intotoV001Schema": { + "description": "Schema for intoto object", + "type": "object", + "title": "intoto v0.0.1 Schema", + "required": [ + "publicKey", + "content" + ], + "properties": { + "content": { + "type": "object", + "properties": { + "envelope": { + "description": "envelope", + "type": "string", + "writeOnly": true + }, + "hash": { + "description": "Specifies the hash algorithm and value encompassing the entire signed envelope", + "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 archive", + "type": "string" + } + }, + "readOnly": true + }, + "payloadHash": { + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", + "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 envelope's payload", + "type": "string" + } + }, + "readOnly": true + } + } + }, + "publicKey": { + "description": "The public key that can verify the signature", + "type": "string", + "format": "byte" + } + }, + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://rekor.sigstore.dev/types/intoto/intoto_v0_0_1_schema.json" + }, "intotoV002Schema": { "description": "Schema for intoto object", "type": "object", diff --git a/pkg/types/intoto/intoto.go b/pkg/types/intoto/intoto.go index f682f7ede..e6f39ba55 100644 --- a/pkg/types/intoto/intoto.go +++ b/pkg/types/intoto/intoto.go @@ -72,3 +72,10 @@ func (it *BaseIntotoType) CreateProposedEntry(ctx context.Context, version strin func (it BaseIntotoType) DefaultVersion() string { return "0.0.2" } + +// SupportedVersions returns the supported versions for this type; +// it deliberately omits 0.0.1 from the list of supported versions as that +// version did not persist signatures inside the log entry +func (it BaseIntotoType) SupportedVersions() []string { + return []string{"0.0.2"} +} diff --git a/pkg/types/intoto/intoto_schema.json b/pkg/types/intoto/intoto_schema.json index 852ba660d..16f6172af 100644 --- a/pkg/types/intoto/intoto_schema.json +++ b/pkg/types/intoto/intoto_schema.json @@ -5,6 +5,9 @@ "description": "Intoto for Rekord objects", "type": "object", "oneOf": [ + { + "$ref": "v0.0.1/intoto_v0_0_1_schema.json" + }, { "$ref": "v0.0.2/intoto_v0_0_2_schema.json" } diff --git a/pkg/types/intoto/v0.0.2/entry_test.go b/pkg/types/intoto/v0.0.2/entry_test.go index eb8285a67..66bd21a23 100644 --- a/pkg/types/intoto/v0.0.2/entry_test.go +++ b/pkg/types/intoto/v0.0.2/entry_test.go @@ -16,6 +16,8 @@ package intoto import ( + "bytes" + "context" "crypto" "crypto/ecdsa" "crypto/elliptic" @@ -33,13 +35,16 @@ import ( "strings" "testing" + "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/in-toto/in-toto-golang/in_toto" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/sigstore/pkg/signature" "go.uber.org/goleak" ) @@ -297,6 +302,35 @@ func TestV002Entry_Unmarshal(t *testing.T) { if !reflect.DeepEqual(got, want) { t.Errorf("V002Entry.IndexKeys() = %v, want %v", got, want) } + payloadBytes, _ := v.env.DecodeB64Payload() + payloadSha := sha256.Sum256(payloadBytes) + payloadHash := hex.EncodeToString(payloadSha[:]) + + canonicalBytes, err := v.Canonicalize(context.Background()) + if err != nil { + t.Errorf("error canonicalizing entry: %v", err) + } + + pe, err := models.UnmarshalProposedEntry(bytes.NewReader(canonicalBytes), runtime.JSONConsumer()) + if err != nil { + t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tt.name, err) + } + canonicalEntry, err := types.UnmarshalEntry(pe) + if err != nil { + t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tt.name, err) + } + canonicalV002 := canonicalEntry.(*V002Entry) + fmt.Printf("%v", canonicalV002.IntotoObj.Content) + if *canonicalV002.IntotoObj.Content.Hash.Value != *tt.it.Content.Hash.Value { + t.Errorf("envelope hashes do not match post canonicalization: %v %v", *canonicalV002.IntotoObj.Content.Hash.Value, *tt.it.Content.Hash.Value) + } + if canonicalV002.AttestationKey() != "" && *canonicalV002.IntotoObj.Content.PayloadHash.Value != payloadHash { + t.Errorf("payload hashes do not match post canonicalization: %v %v", canonicalV002.IntotoObj.Content.PayloadHash.Value, payloadHash) + } + canonicalIndexKeys, _ := canonicalV002.IndexKeys() + if !cmp.Equal(got, canonicalIndexKeys, cmpopts.SortSlices(func(x, y string) bool { return x < y })) { + t.Errorf("index keys from hydrated object do not match those generated from canonicalized (and re-hydrated) object: %v %v", got, canonicalIndexKeys) + } return nil } diff --git a/tests/e2e-test.sh b/tests/e2e-test.sh index f930e4b41..1e9b8e0cf 100755 --- a/tests/e2e-test.sh +++ b/tests/e2e-test.sh @@ -18,7 +18,7 @@ set -e testdir=$(dirname "$0") echo "starting services" -docker-compose up -d +docker-compose up -d --build echo "building CLI and server" go build -o rekor-cli ./cmd/rekor-cli From 390abd727d3b38d718e709611e8cbf932cd16e05 Mon Sep 17 00:00:00 2001 From: pxp928 Date: Mon, 29 Aug 2022 11:26:55 -0400 Subject: [PATCH 8/8] added test to check for v001 blocking Signed-off-by: pxp928 --- cmd/rekor-cli/app/pflags_test.go | 5 + pkg/generated/restapi/embedded_spec.go | 9 +- pkg/types/alpine/v0.0.1/entry.go | 2 +- pkg/types/cose/v0.0.1/entry.go | 2 +- pkg/types/hashedrekord/v0.0.1/entry.go | 2 +- pkg/types/helm/v0.0.1/entry.go | 2 +- pkg/types/intoto/intoto.go | 6 + pkg/types/intoto/v0.0.1/entry.go | 2 +- .../intoto/v0.0.2/intoto_v0_0_2_schema.json | 3 +- pkg/types/rekord/v0.0.1/entry.go | 2 +- pkg/types/rpm/v0.0.1/entry.go | 2 +- pkg/types/tuf/v0.0.1/entry.go | 2 +- tests/e2e-test.sh | 2 +- tests/e2e_test.go | 109 ++++++++++++++++++ 14 files changed, 133 insertions(+), 17 deletions(-) diff --git a/cmd/rekor-cli/app/pflags_test.go b/cmd/rekor-cli/app/pflags_test.go index a6a769abe..c48efc3f9 100644 --- a/cmd/rekor-cli/app/pflags_test.go +++ b/cmd/rekor-cli/app/pflags_test.go @@ -762,6 +762,11 @@ func TestParseTypeFlag(t *testing.T) { { caseDesc: "explicit intoto v0.0.1", typeStr: "intoto:0.0.1", + expectSuccess: false, + }, + { + caseDesc: "explicit intoto v0.0.2", + typeStr: "intoto:0.0.2", expectSuccess: true, }, { diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index 77da71031..6744d7cee 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -1984,8 +1984,7 @@ func init() { "$ref": "#/definitions/IntotoV002SchemaContentEnvelopeSignaturesItems0" } } - }, - "writeOnly": true + } }, "hash": { "description": "Specifies the hash algorithm and value encompassing the entire signed envelope", @@ -2059,8 +2058,7 @@ func init() { "$ref": "#/definitions/IntotoV002SchemaContentEnvelopeSignaturesItems0" } } - }, - "writeOnly": true + } }, "IntotoV002SchemaContentEnvelopeSignaturesItems0": { "description": "a signature of the envelope's payload along with the public key for the signature", @@ -3355,8 +3353,7 @@ func init() { "$ref": "#/definitions/IntotoV002SchemaContentEnvelopeSignaturesItems0" } } - }, - "writeOnly": true + } }, "hash": { "description": "Specifies the hash algorithm and value encompassing the entire signed envelope", diff --git a/pkg/types/alpine/v0.0.1/entry.go b/pkg/types/alpine/v0.0.1/entry.go index db0050514..09da6c9e6 100644 --- a/pkg/types/alpine/v0.0.1/entry.go +++ b/pkg/types/alpine/v0.0.1/entry.go @@ -332,7 +332,7 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types } publicKeyBytes = append(publicKeyBytes, keyBytes) } else if len(publicKeyBytes) != 1 { - return nil, errors.New("only one public key byte must be provided") + return nil, errors.New("only one public key must be provided") } re.AlpineModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) diff --git a/pkg/types/cose/v0.0.1/entry.go b/pkg/types/cose/v0.0.1/entry.go index 4427ff76d..48b4296cb 100644 --- a/pkg/types/cose/v0.0.1/entry.go +++ b/pkg/types/cose/v0.0.1/entry.go @@ -326,7 +326,7 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A } publicKeyBytes = append(publicKeyBytes, keyBytes) } else if len(publicKeyBytes) != 1 { - return nil, errors.New("only one public key byte must be provided") + return nil, errors.New("only one public key must be provided") } kb := strfmt.Base64(publicKeyBytes[0]) diff --git a/pkg/types/hashedrekord/v0.0.1/entry.go b/pkg/types/hashedrekord/v0.0.1/entry.go index 0712b5939..74d6654e8 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry.go +++ b/pkg/types/hashedrekord/v0.0.1/entry.go @@ -227,7 +227,7 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types } publicKeyBytes = append(publicKeyBytes, keyBytes) } else if len(publicKeyBytes) != 1 { - return nil, errors.New("only one public key byte must be provided") + return nil, errors.New("only one public key must be provided") } re.HashedRekordObj.Signature.PublicKey.Content = strfmt.Base64(publicKeyBytes[0]) diff --git a/pkg/types/helm/v0.0.1/entry.go b/pkg/types/helm/v0.0.1/entry.go index 4e5dd9242..000cb2c70 100644 --- a/pkg/types/helm/v0.0.1/entry.go +++ b/pkg/types/helm/v0.0.1/entry.go @@ -327,7 +327,7 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types } publicKeyBytes = append(publicKeyBytes, keyBytes) } else if len(publicKeyBytes) != 1 { - return nil, errors.New("only one public key byte must be provided") + return nil, errors.New("only one public key must be provided") } re.HelmObj.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) diff --git a/pkg/types/intoto/intoto.go b/pkg/types/intoto/intoto.go index e6f39ba55..81d2ceeaa 100644 --- a/pkg/types/intoto/intoto.go +++ b/pkg/types/intoto/intoto.go @@ -22,6 +22,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" + "golang.org/x/exp/slices" ) const ( @@ -79,3 +80,8 @@ func (it BaseIntotoType) DefaultVersion() string { func (it BaseIntotoType) SupportedVersions() []string { return []string{"0.0.2"} } + +// IsSupportedVersion returns true if the version can be inserted into the log, and false if not +func (it *BaseIntotoType) IsSupportedVersion(proposedVersion string) bool { + return slices.Contains(it.SupportedVersions(), proposedVersion) +} diff --git a/pkg/types/intoto/v0.0.1/entry.go b/pkg/types/intoto/v0.0.1/entry.go index f2c2da866..438f08392 100644 --- a/pkg/types/intoto/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.1/entry.go @@ -302,7 +302,7 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A } publicKeyBytes = append(publicKeyBytes, keyBytes) } else if len(publicKeyBytes) != 1 { - return nil, errors.New("only one public key byte must be provided") + return nil, errors.New("only one public key must be provided") } kb := strfmt.Base64(publicKeyBytes[0]) diff --git a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json index 63f18bdb4..0008e46fb 100644 --- a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json +++ b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json @@ -49,8 +49,7 @@ } } }, - "required": ["payloadType", "signatures"], - "writeOnly": true + "required": ["payloadType", "signatures"] }, "hash": { "description": "Specifies the hash algorithm and value encompassing the entire signed envelope", diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index 9c160a3e6..95feb3ba4 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) CreateFromArtifactProperties(ctx context.Context, props types } publicKeyBytes = append(publicKeyBytes, keyBytes) } else if len(publicKeyBytes) != 1 { - return nil, errors.New("only one public key byte must be provided") + return nil, errors.New("only one public key must be provided") } re.RekordObj.Signature.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index a754bce6a..1523a4297 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -352,7 +352,7 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types } publicKeyBytes = append(publicKeyBytes, keyBytes) } else if len(publicKeyBytes) != 1 { - return nil, errors.New("only one public key byte must be provided") + return nil, errors.New("only one public key must be provided") } re.RPMModel.PublicKey.Content = (*strfmt.Base64)(&publicKeyBytes[0]) diff --git a/pkg/types/tuf/v0.0.1/entry.go b/pkg/types/tuf/v0.0.1/entry.go index f50964860..8c3a9ff15 100644 --- a/pkg/types/tuf/v0.0.1/entry.go +++ b/pkg/types/tuf/v0.0.1/entry.go @@ -344,7 +344,7 @@ func (v V001Entry) CreateFromArtifactProperties(ctx context.Context, props types rootBytes = append(rootBytes, keyBytes) } else if len(rootBytes) != 1 { - return nil, errors.New("only one root key byte must be provided") + return nil, errors.New("only one root key must be provided") } root := &data.Signed{} diff --git a/tests/e2e-test.sh b/tests/e2e-test.sh index 1e9b8e0cf..f930e4b41 100755 --- a/tests/e2e-test.sh +++ b/tests/e2e-test.sh @@ -18,7 +18,7 @@ set -e testdir=$(dirname "$0") echo "starting services" -docker-compose up -d --build +docker-compose up -d echo "building CLI and server" go build -o rekor-cli ./cmd/rekor-cli diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 535497856..4e99d272a 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -33,16 +33,19 @@ import ( "fmt" "io/ioutil" "net/http" + "net/url" "os" "os/exec" "path/filepath" "reflect" + "runtime" "strconv" "strings" "testing" "time" "golang.org/x/sync/errgroup" + "sigs.k8s.io/release-utils/version" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/go-openapi/strfmt" @@ -57,6 +60,7 @@ import ( "github.com/sigstore/rekor/pkg/sharding" "github.com/sigstore/rekor/pkg/signer" "github.com/sigstore/rekor/pkg/types" + _ "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" rekord "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1" "github.com/sigstore/rekor/pkg/util" "github.com/sigstore/sigstore/pkg/cryptoutils" @@ -523,6 +527,10 @@ func TestIntoto(t *testing.T) { write(t, string(eb), attestationPath) write(t, ecdsaPub, pubKeyPath) + // ensure that we can't upload a intoto v0.0.1 entry + v001out := runCliErr(t, "upload", "--artifact", attestationPath, "--type", "intoto:0.0.1", "--public-key", pubKeyPath) + outputContains(t, v001out, "type intoto does not support version 0.0.1") + // If we do it twice, it should already exist out := runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", pubKeyPath) outputContains(t, out, "Created entry at") @@ -653,6 +661,10 @@ func TestIntotoMultiSig(t *testing.T) { write(t, ecdsaPub, ecdsapubKeyPath) write(t, pubKey, rsapubKeyPath) + // ensure that we can't upload a intoto v0.0.1 entry + v001out := runCliErr(t, "upload", "--artifact", attestationPath, "--type", "intoto:0.0.1", "--public-key", ecdsapubKeyPath, "--public-key", rsapubKeyPath) + outputContains(t, v001out, "type intoto does not support version 0.0.1") + // If we do it twice, it should already exist out := runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", ecdsapubKeyPath, "--public-key", rsapubKeyPath) outputContains(t, out, "Created entry at") @@ -697,6 +709,103 @@ func TestIntotoMultiSig(t *testing.T) { } +func TestIntotoBlockV001(t *testing.T) { + td := t.TempDir() + attestationPath := filepath.Join(td, "attestation.json") + pubKeyPath := filepath.Join(td, "pub.pem") + + // Get some random data so it's unique each run + d := randomData(t, 10) + id := base64.StdEncoding.EncodeToString(d) + + it := in_toto.ProvenanceStatement{ + StatementHeader: in_toto.StatementHeader{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []in_toto.Subject{ + { + Name: "foobar", + Digest: slsa.DigestSet{ + "foo": "bar", + }, + }, + }, + }, + Predicate: slsa.ProvenancePredicate{ + Builder: slsa.ProvenanceBuilder{ + ID: "foo" + id, + }, + }, + } + + b, err := json.Marshal(it) + if err != nil { + t.Fatal(err) + } + + pb, _ := pem.Decode([]byte(ecdsaPriv)) + priv, err := x509.ParsePKCS8PrivateKey(pb.Bytes) + if err != nil { + t.Fatal(err) + } + + s, err := signature.LoadECDSASigner(priv.(*ecdsa.PrivateKey), crypto.SHA256) + if err != nil { + t.Fatal(err) + } + + signer, err := dsse.NewEnvelopeSigner(&verifier{ + s: s, + }) + if err != nil { + t.Fatal(err) + } + + env, err := signer.SignPayload(in_toto.PayloadType, b) + if err != nil { + t.Fatal(err) + } + + eb, err := json.Marshal(env) + if err != nil { + t.Fatal(err) + } + + uaString := fmt.Sprintf("rekor-cli/%s (%s; %s)", version.GetVersionInfo().GitVersion, runtime.GOOS, runtime.GOARCH) + + write(t, string(eb), attestationPath) + write(t, ecdsaPub, pubKeyPath) + + rekorClient, err := client.GetRekorClient("http://localhost:3000", client.WithUserAgent(uaString)) + if err != nil { + t.Fatal(err) + } + var entry models.ProposedEntry + params := entries.NewCreateLogEntryParams() + params.SetTimeout(time.Duration(30) * time.Second) + + props := &types.ArtifactProperties{} + + props.ArtifactPath = &url.URL{Path: attestationPath} + + collectedKeys := []*url.URL{{Path: pubKeyPath}} + props.PublicKeyPaths = collectedKeys + + entry, err = types.NewProposedEntry(context.Background(), "intoto", "0.0.1", *props) + if err != nil { + t.Fatal(err) + } + params.SetProposedEntry(entry) + + _, err = rekorClient.Entries.CreateLogEntry(params) + if err == nil { + t.Fatal("insertion of v0.0.1 entry should fail") + } + if !strings.Contains(err.Error(), "entry kind 'intoto' does not support inserting entries of version '0.0.1'") { + t.Errorf("Expected error as intoto v0.0.1 should not be allowed to be entered into rekor") + } +} + func TestTimestampArtifact(t *testing.T) { var out string out = runCli(t, "upload", "--type", "rfc3161", "--artifact", "test.tsr")