From 5ca4493ce85591f2df6640f47b7bca6af6b49218 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Fri, 22 Dec 2023 15:08:22 +0100 Subject: [PATCH 1/3] Support for Delivered Public Key Certificates (#592) * prepare signing utils * switch to pkix issuer * generic key types + migrate issuer * take key from signature * signing context for verify * fixes + command line tests * algin cert option names + usage of central root certs * basic tsa support * tsa support for signing * introduce timestampSpec + various fixes * extract DN from signature name * adapt routing slips to new signing --- .../common/options/keyoption/option.go | 48 ++ cmds/ocm/commands/misccmds/hash/cmd.go | 109 +--- cmds/ocm/commands/misccmds/hash/sign/cmd.go | 152 ++++++ .../misccmds/hash/{ => sign}/cmd_test.go | 6 +- .../commands/misccmds/hash/sign/suite_test.go | 17 + cmds/ocm/commands/misccmds/rsakeypair/cmd.go | 120 +++- .../commands/misccmds/rsakeypair/cmd_test.go | 78 ++- .../ocmcmds/common/cmds/signing/cmd.go | 3 + .../common/options/signoption/option.go | 53 +- .../ocmcmds/components/sign/cmd_test.go | 69 +++ .../commands/ocmcmds/routingslips/add/cmd.go | 10 +- .../routingslips/common/typehandler.go | 7 +- .../ocmcmds/routingslips/get/cmd_test.go | 56 +- cmds/ocm/commands/verbs/sign/cmd.go | 4 +- cmds/ocm/topics/common/credentials/topic.go | 4 +- docs/reference/ocm.md | 12 + docs/reference/ocm_configfile.md | 18 + docs/reference/ocm_create_rsakeypair.md | 21 +- docs/reference/ocm_credential-handling.md | 4 +- docs/reference/ocm_hash.md | 4 +- docs/reference/ocm_sign_componentversions.md | 6 +- docs/reference/ocm_sign_hash.md | 2 + .../reference/ocm_verify_componentversions.md | 3 +- go.mod | 1 + go.sum | 2 + .../ocm/accessmethods/ociartifact/method.go | 6 +- pkg/contexts/ocm/attrs/signingattr/config.go | 83 ++- .../ocm/compdesc/meta/v1/signature.go | 15 +- pkg/contexts/ocm/compdesc/signing.go | 100 +++- .../v3alpha1/jsonscheme/bindata.go | 2 +- .../versions/v2/jsonscheme/bindata.go | 2 +- pkg/contexts/ocm/labels/routingslip/entry.go | 12 +- .../ocm/labels/routingslip/entrytypes_test.go | 2 +- .../ocm/labels/routingslip/interface.go | 2 +- pkg/contexts/ocm/labels/routingslip/label.go | 32 +- pkg/contexts/ocm/labels/routingslip/slip.go | 73 ++- .../ocm/labels/routingslip/slip_test.go | 7 +- pkg/contexts/ocm/resolver.go | 21 + pkg/contexts/ocm/signing/digestctx.go | 23 +- pkg/contexts/ocm/signing/handle.go | 153 +++++- pkg/contexts/ocm/signing/options.go | 171 +++++- pkg/contexts/ocm/signing/options_test.go | 36 +- pkg/contexts/ocm/signing/signing_test.go | 204 ++++++- .../handlers/simplelistmerge/config.go | 8 +- .../handlers/simplelistmerge/handler.go | 19 +- pkg/env/builder/rsa_keypair.go | 6 +- pkg/env/keypair.go | 6 +- pkg/signing/cert.go | 191 ++----- pkg/signing/cert_test.go | 36 +- pkg/signing/deprecated.go | 34 ++ .../handlers/rsa-signingservice/README.md | 2 +- .../handlers/rsa-signingservice/client.go | 39 +- .../handlers/rsa-signingservice/handler.go | 7 +- pkg/signing/handlers/rsa/certhelper.go | 54 ++ pkg/signing/handlers/rsa/format.go | 11 +- pkg/signing/handlers/rsa/handler.go | 122 ++--- pkg/signing/handlers/sigstore/handler.go | 7 +- pkg/signing/registry.go | 149 ++++- pkg/signing/signing_test.go | 23 +- pkg/signing/signutils/certs.go | 263 +++++++++ pkg/signing/signutils/certs_test.go | 87 +++ pkg/signing/signutils/names.go | 142 +++++ pkg/signing/signutils/names_test.go | 90 +++ pkg/signing/signutils/signature.go | 67 +++ .../signing/signutils}/suite_test.go | 4 +- pkg/signing/signutils/types.go | 47 ++ pkg/signing/signutils/utils.go | 514 ++++++++++++++++++ pkg/signing/tsa/pem.go | 45 ++ pkg/signing/tsa/tsa.go | 112 ++++ pkg/signing/tsa/types.go | 14 + pkg/signing/types.go | 52 +- pkg/testutils/utils.go | 5 + pkg/utils/path.go | 1 + pkg/utils/utils.go | 6 +- .../component-descriptor-ocm-v3-schema.yaml | 11 + resources/component-descriptor-v2-schema.yaml | 13 + 76 files changed, 3333 insertions(+), 607 deletions(-) create mode 100644 cmds/ocm/commands/misccmds/hash/sign/cmd.go rename cmds/ocm/commands/misccmds/hash/{ => sign}/cmd_test.go (89%) create mode 100644 cmds/ocm/commands/misccmds/hash/sign/suite_test.go create mode 100644 pkg/signing/deprecated.go create mode 100644 pkg/signing/handlers/rsa/certhelper.go create mode 100644 pkg/signing/signutils/certs.go create mode 100644 pkg/signing/signutils/certs_test.go create mode 100644 pkg/signing/signutils/names.go create mode 100644 pkg/signing/signutils/names_test.go create mode 100644 pkg/signing/signutils/signature.go rename {cmds/ocm/commands/misccmds/hash => pkg/signing/signutils}/suite_test.go (84%) create mode 100644 pkg/signing/signutils/types.go create mode 100644 pkg/signing/signutils/utils.go create mode 100644 pkg/signing/tsa/pem.go create mode 100644 pkg/signing/tsa/tsa.go create mode 100644 pkg/signing/tsa/types.go diff --git a/cmds/ocm/commands/common/options/keyoption/option.go b/cmds/ocm/commands/common/options/keyoption/option.go index 26e9ac7f52..2701d954fd 100644 --- a/cmds/ocm/commands/common/options/keyoption/option.go +++ b/cmds/ocm/commands/common/options/keyoption/option.go @@ -5,6 +5,8 @@ package keyoption import ( + "fmt" + "reflect" "strings" "github.com/spf13/pflag" @@ -14,6 +16,7 @@ import ( ocmsign "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/signing" + "github.com/open-component-model/ocm/pkg/signing/signutils" "github.com/open-component-model/ocm/pkg/utils" ) @@ -33,12 +36,16 @@ type Option struct { DefaultName string publicKeys []string privateKeys []string + issuers []string + rootCAs []string Keys signing.KeyRegistry } func (o *Option) AddFlags(fs *pflag.FlagSet) { fs.StringArrayVarP(&o.publicKeys, "public-key", "k", nil, "public key setting") fs.StringArrayVarP(&o.privateKeys, "private-key", "K", nil, "private key setting") + fs.StringArrayVarP(&o.issuers, "issuer", "I", nil, "issuer name or distinguished name (DN) (optionally for dedicated signature) ([:=]") + fs.StringArrayVarP(&o.rootCAs, "ca-cert", "", nil, "additional root certificate authorities") } func (o *Option) Configure(ctx clictx.Context) error { @@ -53,6 +60,36 @@ func (o *Option) Configure(ctx clictx.Context) error { if err != nil { return err } + for _, i := range o.issuers { + name := o.DefaultName + is := i + sep := strings.Index(i, ":=") + if sep >= 0 { + name = i[:sep] + is = i[sep+1:] + } + old := o.Keys.GetIssuer(name) + dn, err := signutils.ParseDN(is) + if err != nil { + return errors.Wrapf(err, "issuer %q", i) + } + if old != nil && !reflect.DeepEqual(old, dn) { + return fmt.Errorf("issuer already set (%s)", i) + } + + o.Keys.RegisterIssuer(name, dn) + } + + for _, r := range o.rootCAs { + data, err := utils.ReadFile(r, ctx.FileSystem()) + if err != nil { + return errors.Wrapf(err, "root CA") + } + err = o.Keys.RegisterRootCertificates(data) + if err != nil { + return errors.Wrapf(err, "root CA") + } + } return nil } @@ -98,6 +135,17 @@ name of a component version) Alternatively a key can be specified as base64 encoded string if the argument start with the prefix ! or as direct string with the prefix =. + +With --issuer it is possible to declare expected issuer +constraints for public key certificates provided as part of a signature +required to accept the provisioned public key (besides the successful +validation of the certificate). By default, the issuer constraint is +derived from the signature name. If it is not a formal distinguished name, +it is assumed to be a plain common name. + +With --ca-cert it is possible to define additional root +certificates for signature verification, if public keys are provided +by a certificate delivered with the signature. ` return s } diff --git a/cmds/ocm/commands/misccmds/hash/cmd.go b/cmds/ocm/commands/misccmds/hash/cmd.go index be9f40e09f..ed85415955 100644 --- a/cmds/ocm/commands/misccmds/hash/cmd.go +++ b/cmds/ocm/commands/misccmds/hash/cmd.go @@ -2,113 +2,24 @@ // // SPDX-License-Identifier: Apache-2.0 -package hash +package credentials import ( - "fmt" - "strings" - "github.com/spf13/cobra" - "github.com/spf13/pflag" + "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/hash/sign" "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/errors" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - utils2 "github.com/open-component-model/ocm/pkg/utils" -) - -var ( - Names = names.Hash - Verb = verbs.Create ) -type Command struct { - utils.BaseCommand - - stype string - priv []byte - htype string - hash string - issuer string - - hasher signing.Hasher - signer signing.Signer -} - -var _ utils.OCMCommand = (*Command)(nil) - -// NewCommand creates a new artifact command. -func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { - return utils.SetupCommand(&Command{BaseCommand: utils.NewBaseCommand(ctx)}, utils.Names(Names, names...)...) -} - -func (o *Command) ForName(name string) *cobra.Command { - return &cobra.Command{ - Use: " []", - Short: "sign hash", - Long: ` -Print the signature for a dedicated digest value. - `, - Example: ` -$ ocm sign hash key.priv SHA-256:810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50 -`, - } -} - -func (o *Command) AddFlags(set *pflag.FlagSet) { - set.StringVarP(&o.stype, "algorithm", "S", rsa.Algorithm, "signature algorithm") -} - -func (o *Command) Complete(args []string) error { - var err error - - if len(args) < 2 { - return fmt.Errorf("key file and hash argumnt required") - } - if len(args) > 3 { - return fmt.Errorf("too many arguments") - } - if len(args) == 3 { - o.issuer = args[2] - } - o.priv, err = utils2.ReadFile(args[0], o.FileSystem()) - if err != nil { - return err - } - - if i := strings.Index(args[1], ":"); i <= 0 { - return fmt.Errorf("hash type missing for hash string") - } else { - o.htype = args[1][:i] - o.hash = args[1][i+1:] - } - - reg := signingattr.Get(o.Context) - o.hasher = reg.GetHasher(o.htype) - if o.hasher == nil { - return errors.ErrUnknown(compdesc.KIND_HASH_ALGORITHM, o.htype) - } - o.signer = reg.GetSigner(o.stype) - if o.signer == nil { - return errors.ErrUnknown(compdesc.KIND_SIGN_ALGORITHM, o.stype) - } - return nil -} +var Names = names.Hash -func (o *Command) Run() error { - sig, err := o.signer.Sign(o.Context.CredentialsContext(), o.hash, o.hasher.Crypto(), o.issuer, o.priv) - if err != nil { - return err - } - out.Outf(o, "algorithm: %s\n", sig.Algorithm) - out.Outf(o, "mediaType: %s\n", sig.MediaType) - out.Outf(o, "value: %s\n", sig.Value) - return nil +// NewCommand creates a new command. +func NewCommand(ctx clictx.Context) *cobra.Command { + cmd := utils.MassageCommand(&cobra.Command{ + Short: "Commands acting on hashes", + }, Names...) + cmd.AddCommand(sign.NewCommand(ctx, sign.Verb)) + return cmd } diff --git a/cmds/ocm/commands/misccmds/hash/sign/cmd.go b/cmds/ocm/commands/misccmds/hash/sign/cmd.go new file mode 100644 index 0000000000..537f000ecf --- /dev/null +++ b/cmds/ocm/commands/misccmds/hash/sign/cmd.go @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package sign + +import ( + "crypto/x509/pkix" + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/names" + "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" + "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" + "github.com/open-component-model/ocm/pkg/contexts/clictx" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" + "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/out" + "github.com/open-component-model/ocm/pkg/signing" + "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "github.com/open-component-model/ocm/pkg/signing/signutils" + utils2 "github.com/open-component-model/ocm/pkg/utils" +) + +var ( + Names = names.Hash + Verb = verbs.Sign +) + +type Command struct { + utils.BaseCommand + + pubFile string + rootFile string + + stype string + priv signutils.GenericPrivateKey + pub signutils.GenericPublicKey + roots signutils.GenericCertificatePool + htype string + hash string + + issuer *pkix.Name + hasher signing.Hasher + signer signing.Signer +} + +var _ utils.OCMCommand = (*Command)(nil) + +// NewCommand creates a new artifact command. +func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { + return utils.SetupCommand(&Command{BaseCommand: utils.NewBaseCommand(ctx)}, utils.Names(Names, names...)...) +} + +func (o *Command) ForName(name string) *cobra.Command { + return &cobra.Command{ + Use: " []", + Short: "sign hash", + Long: ` +Print the signature for a dedicated digest value. + `, + Example: ` +$ ocm sign hash key.priv SHA-256:810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50 +`, + } +} + +func (o *Command) AddFlags(set *pflag.FlagSet) { + set.StringVarP(&o.stype, "algorithm", "S", rsa.Algorithm, "signature algorithm") + set.StringVarP(&o.pubFile, "publicKey", "", "", "public key certificate file") + set.StringVarP(&o.rootFile, "rootCerts", "", "", "root certificates file") +} + +func (o *Command) Complete(args []string) error { + var err error + + if len(args) < 2 { + return fmt.Errorf("key file and hash argumnt required") + } + if len(args) > 3 { + return fmt.Errorf("too many arguments") + } + if len(args) == 3 { + o.issuer, err = signutils.ParseDN(args[2]) + if err != nil { + return errors.Wrapf(err, "issuer") + } + } + + if o.pubFile != "" { + o.pub, err = utils2.ReadFile(o.pubFile, o.FileSystem()) + if err != nil { + return err + } + } + + if o.rootFile != "" { + roots, err := utils2.ReadFile(o.rootFile, o.FileSystem()) + if err != nil { + return err + } + o.roots, err = signutils.GetCertPool(roots, false) + if err != nil { + return err + } + } + + o.priv, err = utils2.ReadFile(args[0], o.FileSystem()) + if err != nil { + return err + } + + if i := strings.Index(args[1], ":"); i <= 0 { + return fmt.Errorf("hash type missing for hash string") + } else { + o.htype = args[1][:i] + o.hash = args[1][i+1:] + } + + reg := signingattr.Get(o.Context) + o.hasher = reg.GetHasher(o.htype) + if o.hasher == nil { + return errors.ErrUnknown(compdesc.KIND_HASH_ALGORITHM, o.htype) + } + o.signer = reg.GetSigner(o.stype) + if o.signer == nil { + return errors.ErrUnknown(compdesc.KIND_SIGN_ALGORITHM, o.stype) + } + return nil +} + +func (o *Command) Run() error { + sctx := &signing.DefaultSigningContext{ + Hash: o.hasher.Crypto(), + PrivateKey: o.priv, + PublicKey: o.pub, + RootCerts: o.roots, + Issuer: o.issuer, + } + sig, err := o.signer.Sign(o.Context.CredentialsContext(), o.hash, sctx) + if err != nil { + return err + } + out.Outf(o, "algorithm: %s\n", sig.Algorithm) + out.Outf(o, "mediaType: %s\n", sig.MediaType) + out.Outf(o, "value: %s\n", sig.Value) + return nil +} diff --git a/cmds/ocm/commands/misccmds/hash/cmd_test.go b/cmds/ocm/commands/misccmds/hash/sign/cmd_test.go similarity index 89% rename from cmds/ocm/commands/misccmds/hash/cmd_test.go rename to cmds/ocm/commands/misccmds/hash/sign/cmd_test.go index 790064ca70..762f14046c 100644 --- a/cmds/ocm/commands/misccmds/hash/cmd_test.go +++ b/cmds/ocm/commands/misccmds/hash/sign/cmd_test.go @@ -1,8 +1,8 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. // // SPDX-License-Identifier: Apache-2.0 -package hash_test +package sign_test import ( "bytes" @@ -19,7 +19,7 @@ var _ = Describe("Test Environment", func() { var env *TestEnv BeforeEach(func() { - env = NewTestEnv(TestData("../../../../../pkg/contexts/ocm/signing/testdata")) + env = NewTestEnv(TestData("../../../../../../pkg/contexts/ocm/signing/testdata")) }) AfterEach(func() { diff --git a/cmds/ocm/commands/misccmds/hash/sign/suite_test.go b/cmds/ocm/commands/misccmds/hash/sign/suite_test.go new file mode 100644 index 0000000000..4e58a574ec --- /dev/null +++ b/cmds/ocm/commands/misccmds/hash/sign/suite_test.go @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package sign_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Sign Hash") +} diff --git a/cmds/ocm/commands/misccmds/rsakeypair/cmd.go b/cmds/ocm/commands/misccmds/rsakeypair/cmd.go index ce7df707ba..71dfc3c74f 100644 --- a/cmds/ocm/commands/misccmds/rsakeypair/cmd.go +++ b/cmds/ocm/commands/misccmds/rsakeypair/cmd.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "fmt" "strings" "time" @@ -20,6 +21,7 @@ import ( "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" + "github.com/open-component-model/ocm/pkg/cobrautils/flag" "github.com/open-component-model/ocm/pkg/contexts/clictx" "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" "github.com/open-component-model/ocm/pkg/encrypt" @@ -27,6 +29,7 @@ import ( "github.com/open-component-model/ocm/pkg/out" "github.com/open-component-model/ocm/pkg/signing" "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "github.com/open-component-model/ocm/pkg/signing/signutils" utils2 "github.com/open-component-model/ocm/pkg/utils" ) @@ -44,13 +47,17 @@ type Command struct { pub string ekey string - attrs map[string]string - cacert string - cakey string + attrs map[string]string + ca bool + rootcerts string + cacert string + cakey string Validity time.Duration - CACert *x509.Certificate - CAKey interface{} + + RootCertPool *x509.CertPool + CAChain []*x509.Certificate + CAKey interface{} Encrypt string CreateEncryptionKey bool @@ -72,10 +79,17 @@ Create an RSA public key pair and save to files. The default for the filename to store the private key is rsa.priv. If no public key file is specified, its name will be derived from the filename for -the private key (suffix .pub for public key or .cert for certificate). -If a certificate authority is given (--cacert) the public key -will be signed. In this case a subject (at least common name/issuer) and a private -key (--cakey) is required. If only a subject is given, the public key will be self-signed. +the private key (suffix .pub for public key or .cert +for certificate). If a certificate authority is given (--ca-cert) +the public key will be signed. In this case a subject (at least common +name/issuer) and a private key (--ca-key) for the ca used to sign the +key is required. + +If only a subject is given and no ca, the public key will be self-signed. +A signed public key always contains the complete certificate chain. If a +non-self-signed ca is used to sign the key, its certificate chain is verified. +Therefore, an additional root certificate (--root-certs) is required, +if no public root certificate was used to create the used ca. For signing the public key the following subject attributes are supported: - CN, common-name, issuer: Common Name/Issuer @@ -95,11 +109,16 @@ $ ocm create rsakeypair mandelsoft.priv mandelsoft.cert issuer=mandelsoft } func (o *Command) AddFlags(set *pflag.FlagSet) { - set.StringVarP(&o.cacert, "cacert", "", "", "certificate authority to sign public key") - set.StringVarP(&o.cakey, "cakey", "", "", "private key for certificate authority") + set.BoolVarP(&o.ca, "ca", "", false, "create certificate for a signing authority") + set.StringVarP(&o.rootcerts, "root-certs", "", "", "root certificates used to validate used certificate authority") + set.StringVarP(&o.cacert, "ca-cert", "", "", "certificate authority to sign public key") + set.StringVarP(&o.cakey, "ca-key", "", "", "private key for certificate authority") set.DurationVarP(&o.Validity, "validity", "", 10*24*365*time.Hour, "certificate validity") set.StringVarP(&o.Encrypt, "encryptionKey", "e", "", "encrypt private key with given key") set.BoolVarP(&o.CreateEncryptionKey, "encrypt", "E", false, "encrypt private key with new key") + + flag.StringVarPF(set, &o.cacert, "cacert", "", "", "certificate authority to sign public key").Hidden = true + flag.StringVarPF(set, &o.cakey, "cakey", "", "", "private key for certificate authority").Hidden = true } func (o *Command) FilterSettings(args ...string) []string { @@ -117,6 +136,24 @@ func (o *Command) Complete(args []string) error { return errors.Newf("only one of --encrypt or --encryptionKey is possible") } + if o.rootcerts != "" { + pool, err := signutils.GetCertPool(o.rootcerts, false) + if err != nil { + path, _ := utils2.ResolvePath(o.rootcerts) + data, err := vfs.ReadFile(o.Context.FileSystem(), path) + if err != nil { + return errors.Wrapf(err, "cannot read root cert file %q", o.rootcerts) + } + pool, err = signutils.GetCertPool(data, false) + if err != nil { + return errors.Wrapf(err, "no root cert in file %q", o.rootcerts) + } + } + o.RootCertPool = pool + } else { + o.RootCertPool = signingattr.Get(o.Context.OCMContext()).GetRootCertPool(true) + } + if o.attrs != nil && len(o.attrs) > 0 { var subject pkix.Name for k, v := range o.attrs { @@ -125,7 +162,7 @@ func (o *Command) Complete(args []string) error { if subject.CommonName == "" { subject.CommonName = v } else { - o.MoreIssuers = append(o.MoreIssuers, v) + return fmt.Errorf("issuer already set") } case "street": subject.StreetAddress = append(subject.StreetAddress, v) @@ -149,39 +186,57 @@ func (o *Command) Complete(args []string) error { } if o.cacert != "" { - cert, err := parse.ParseCertificate(o.cacert) + raw := []byte(o.cacert) + cert, pool, err := signutils.GetCertificate(o.cacert, false) if err != nil { path, _ := utils2.ResolvePath(o.cacert) data, err := vfs.ReadFile(o.Context.FileSystem(), path) if err != nil { return errors.Wrapf(err, "cannot read ca cert file %q", o.cacert) } - cert, err = parse.ParseCertificate(string(data)) + cert, pool, err = signutils.GetCertificate(data, false) if err != nil { return errors.Wrapf(err, "no cert in file %q", o.cacert) } + raw = data + } + + if !signutils.IsSelfSigned(cert) { + opts := x509.VerifyOptions{ + Intermediates: pool, + Roots: o.RootCertPool, + CurrentTime: time.Time{}, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + } + _, err = cert.Verify(opts) + if err != nil { + return err + } + } + o.CAChain, err = signutils.GetCertificateChain(raw, false) + if err != nil { + return err } - o.CACert = cert } if o.cakey != "" { key, err := parse.ParsePrivateKey(o.cakey) if err != nil { - path, _ := utils2.ResolvePath(o.cacert) + path, _ := utils2.ResolvePath(o.cakey) data, err := vfs.ReadFile(o.Context.FileSystem(), path) if err != nil { - return errors.Wrapf(err, "cannot read private key file %q", o.cacert) + return errors.Wrapf(err, "cannot read private key file %q", o.cakey) } key, err = parse.ParsePrivateKey(string(data)) if err != nil { - return errors.Wrapf(err, "unknown private key in file %q", o.cacert) + return errors.Wrapf(err, "unknown private key in file %q", o.cakey) } } o.CAKey = key } - if o.CACert != nil && o.CAKey == nil { + if len(o.CAChain) != 0 && o.CAKey == nil { return errors.Newf("private key required for signing public key") } - if o.CACert == nil && o.CAKey != nil { + if len(o.CAChain) == 0 && o.CAKey != nil { return errors.Newf("ca certificate required for signing public key") } @@ -217,6 +272,8 @@ func (o *Command) Complete(args []string) error { } func (o *Command) Run() error { + raw := false + priv, pub, err := rsa.Handler{}.CreateKeyPair() if err != nil { return err @@ -227,10 +284,27 @@ func (o *Command) Run() error { if o.CAKey == nil { key = priv } - pub, err = signing.CreateCertificate(*o.Subject, nil, o.Validity, pub, o.CACert, key, false, o.MoreIssuers...) + + spec := &signutils.Specification{ + RootCAs: o.RootCertPool, + IsCA: o.ca, + PublicKey: pub, + CAPrivateKey: key, + CAChain: o.CAChain, + Subject: *o.Subject, + Usages: signutils.Usages{x509.ExtKeyUsageCodeSigning}, + Validity: o.Validity, + NotBefore: nil, + } + if len(o.CAChain) == 1 && signutils.IsSelfSigned(o.CAChain[0]) { + o.RootCertPool.AddCert(o.CAChain[0]) + } + + _, pub, err = signutils.CreateCertificate(spec) if err != nil { - return errors.Wrapf(err, "signing failed") + return errors.Wrapf(err, "signing of key pair failed") } + raw = true } var key []byte @@ -274,7 +348,7 @@ func (o *Command) Run() error { if err := o.WriteKey(priv, o.priv, key != nil); err != nil { return errors.Wrapf(err, "failed to write private key file %q", o.priv) } - if err := o.WriteKey(pub, o.pub, false); err != nil { + if err := o.WriteKey(pub, o.pub, raw); err != nil { return errors.Wrapf(err, "failed to write public key file %q", o.pub) } msg := "" diff --git a/cmds/ocm/commands/misccmds/rsakeypair/cmd_test.go b/cmds/ocm/commands/misccmds/rsakeypair/cmd_test.go index 5bef6dfb30..4b39759ad6 100644 --- a/cmds/ocm/commands/misccmds/rsakeypair/cmd_test.go +++ b/cmds/ocm/commands/misccmds/rsakeypair/cmd_test.go @@ -6,6 +6,7 @@ package rsakeypair_test import ( "bytes" + "crypto/x509/pkix" "encoding/pem" . "github.com/onsi/ginkgo/v2" @@ -20,9 +21,11 @@ import ( "github.com/open-component-model/ocm/pkg/encrypt" "github.com/open-component-model/ocm/pkg/signing" "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) -const ISSUER = "mandelsoft" +var ISSUER = &pkix.Name{CommonName: "mandelsoft"} + const KEYNAME = "test" var _ = Describe("Test Environment", func() { @@ -50,14 +53,20 @@ created rsa key pair key.priv[key.pub] pub, err := env.ReadFile("key.pub") Expect(err).To(Succeed()) + sctx := &signing.DefaultSigningContext{ + Hash: 0, + PrivateKey: priv, + PublicKey: nil, + RootCerts: nil, + Issuer: ISSUER, + } d := digest.FromBytes([]byte("digest")) - sig, err := rsa.Handler{}.Sign(defaultContext, d.Hex(), 0, ISSUER, priv) + sig, err := rsa.Handler{}.Sign(defaultContext, d.Hex(), sctx) Expect(err).To(Succeed()) Expect(sig.Algorithm).To(Equal(rsa.Algorithm)) Expect(sig.MediaType).To(Equal(rsa.MediaType)) - Expect(sig.Issuer).To(Equal(ISSUER)) - err = rsa.Handler{}.Verify(d.Hex(), 0, sig, pub) + err = rsa.Handler{}.Verify(d.Hex(), sig, &signing.DefaultSigningContext{PublicKey: pub}) Expect(err).To(Succeed()) }) @@ -73,14 +82,20 @@ created rsa key pair key.priv[key.cert] pub, err := env.ReadFile("key.cert") Expect(err).To(Succeed()) + sctx := &signing.DefaultSigningContext{ + Hash: 0, + PrivateKey: priv, + PublicKey: nil, + RootCerts: nil, + Issuer: ISSUER, + } d := digest.FromBytes([]byte("digest")) - sig, err := rsa.Handler{}.Sign(defaultContext, d.Hex(), 0, "mandelsoft", priv) + sig, err := rsa.Handler{}.Sign(defaultContext, d.Hex(), sctx) Expect(err).To(Succeed()) Expect(sig.Algorithm).To(Equal(rsa.Algorithm)) Expect(sig.MediaType).To(Equal(rsa.MediaType)) - Expect(sig.Issuer).To(Equal(ISSUER)) - err = rsa.Handler{}.Verify(d.Hex(), 0, sig, pub) + err = rsa.Handler{}.Verify(d.Hex(), sig, &signing.DefaultSigningContext{PublicKey: pub}) Expect(err).To(Succeed()) }) @@ -114,8 +129,15 @@ created encrypted rsa key pair key.priv[key.pub][key.priv.ekey] key := Must(signing.ResolvePrivateKey(reg, KEYNAME)) Expect(key).NotTo(BeNil()) + sctx := &signing.DefaultSigningContext{ + Hash: 0, + PrivateKey: key, + PublicKey: nil, + RootCerts: nil, + Issuer: ISSUER, + } d := digest.FromBytes([]byte("digest")) - Must(rsa.Handler{}.Sign(defaultContext, d.Hex(), 0, "mandelsoft", key)) + Must(rsa.Handler{}.Sign(defaultContext, d.Hex(), sctx)) buf.Reset() Expect(env.CatchOutput(buf).Execute("create", "rsakeypair", "-e", KEYNAME, "other.priv")).To(Succeed()) @@ -133,4 +155,44 @@ created encrypted rsa key pair other.priv[other.pub] Expect(block.Type).To(Equal(encrypt.PEM_ENCRYPTED_DATA)) }) }) + + Context("certificate handling", func() { + It("creates chain", func() { + buf := bytes.NewBuffer(nil) + + // create Root CA + Expect(env.CatchOutput(buf).Execute("create", "rsakeypair", "--ca", "CN=cerificate-authority", "root.priv")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +created rsa key pair root.priv[root.cert] +`)) + Expect(env.FileExists("root.priv")).To(BeTrue()) + Expect(env.FileExists("root.cert")).To(BeTrue()) + + // create CA used to create signing certificates + buf.Reset() + Expect(env.CatchOutput(buf).Execute("create", "rsakeypair", "--ca", "CN=acme.org", "--ca-key", "root.priv", "--ca-cert", "root.cert", "ca.priv")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +created rsa key pair ca.priv[ca.cert] +`)) + Expect(env.FileExists("ca.priv")).To(BeTrue()) + Expect(env.FileExists("ca.cert")).To(BeTrue()) + + // create signing vcertificate from CA + buf.Reset() + Expect(env.CatchOutput(buf).Execute("create", "rsakeypair", "--ca", "CN=mandelsoft", "C=DE", "--ca-key", "ca.priv", "--ca-cert", "ca.cert", "--root-certs", "root.cert", "key.priv")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +created rsa key pair key.priv[key.cert] +`)) + Expect(env.FileExists("key.priv")).To(BeTrue()) + Expect(env.FileExists("key.cert")).To(BeTrue()) + + root := Must(env.ReadFile("root.cert")) + certs := Must(env.ReadFile("key.cert")) + + chain := Must(signutils.GetCertificateChain(certs, false)) + Expect(len(chain)).To(Equal(3)) + MustBeSuccessful(signing.VerifyCertDN(chain[1:], root, &pkix.Name{CommonName: "mandelsoft", Country: []string{"DE"}}, chain[0])) + ExpectError(signing.VerifyCertDN(chain[1:], root, &pkix.Name{CommonName: "mandelsoft", Country: []string{"US"}}, chain[0])).To(MatchError(`country "US" not found`)) + }) + }) }) diff --git a/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go b/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go index 69b8beffea..27cc0a4627 100644 --- a/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go +++ b/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go @@ -88,6 +88,9 @@ func (o *SignatureCommand) Run() (rerr error) { lookup := lookupoption.From(o) handler := comphdlr.NewTypeHandler(o.Context.OCM(), session, repo, comphdlr.OptionsFor(o)) sopts := signing.NewOptions(sign, signing.Resolver(repo, lookup.Resolver)) + if !o.spec.sign { + sopts.VerifySignature = true + } err = sopts.Complete(o.Context.OCMContext()) if err != nil { return err diff --git a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go index 2df369c59d..522b4040b0 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go @@ -6,8 +6,10 @@ package signoption import ( "crypto/x509" + "fmt" "strings" + "github.com/open-component-model/ocm/pkg/signing/signutils" "github.com/spf13/pflag" "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/keyoption" @@ -23,7 +25,6 @@ import ( "github.com/open-component-model/ocm/pkg/signing" "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" - "github.com/open-component-model/ocm/pkg/utils" ) func From(o options.OptionSetProvider) *Option { @@ -41,11 +42,10 @@ func New(sign bool) *Option { type Option struct { keyoption.Option - rootca []string - local bool + local bool + SignMode bool signAlgorithm string - Issuer string RootCerts *x509.CertPool // Verify the digests Verify bool @@ -58,6 +58,9 @@ type Option struct { Update bool Signer signing.Signer + UseTSA bool + TSAUrl string + Hash hashoption.Option Keyless bool @@ -69,14 +72,14 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { if o.SignMode { o.Hash.AddFlags(fs) fs.StringVarP(&o.signAlgorithm, "algorithm", "S", rsa.Algorithm, "signature handler") - fs.StringVarP(&o.Issuer, "issuer", "I", "", "issuer name") fs.BoolVarP(&o.Update, "update", "", o.SignMode, "update digest in component versions") fs.BoolVarP(&o.Recursively, "recursive", "R", false, "recursively sign component versions") + fs.BoolVarP(&o.UseTSA, "tsa", "", false, fmt.Sprintf("use timestamp authority (default server: %s)", signing.DEFAULT_TSA_URL)) + fs.StringVarP(&o.TSAUrl, "tsa-url", "", "", "TSA server URL") } else { fs.BoolVarP(&o.local, "local", "L", false, "verification based on information found in component versions, only") } fs.BoolVarP(&o.Verify, "verify", "V", o.SignMode, "verify existing digests") - fs.StringArrayVarP(&o.rootca, "ca-cert", "", o.rootca, "additional root certificates") fs.BoolVar(&o.Keyless, "keyless", false, "use keyless signing") } @@ -84,7 +87,11 @@ func (o *Option) Configure(ctx clictx.Context) error { if len(o.SignatureNames) > 0 { for i, n := range o.SignatureNames { n = strings.TrimSpace(n) - o.SignatureNames[i] = n + dn, err := signutils.ParseDN(n) + if err != nil { + return err + } + o.SignatureNames[i] = signutils.NormalizeDN(*dn) if n == "" { return errors.Newf("empty signature name (name %d) not possible", i) } @@ -117,22 +124,8 @@ func (o *Option) Configure(ctx clictx.Context) error { return err } - if len(o.rootca) > 0 { - pool, err := signing.BaseRootPool() - if err != nil { - return err - } - for _, r := range o.rootca { - data, err := utils.ReadFile(r, ctx.FileSystem()) - if err != nil { - return errors.Wrapf(err, "cannot read ca file %q", r) - } - ok := pool.AppendCertsFromPEM(data) - if !ok { - return errors.Newf("cannot add rot certs from %q", r) - } - } - o.RootCerts = pool + if o.Keys.HasRootCertificates() { + o.RootCerts = o.Keys.GetRootCertPool(true) } return nil } @@ -187,8 +180,18 @@ func (o *Option) ApplySigningOption(opts *ocmsign.Options) { opts.Keys = o.Keys opts.NormalizationAlgo = o.Hash.NormAlgorithm opts.Hasher = o.Hash.Hasher - if o.Issuer != "" { - opts.Issuer = o.Issuer + + if o.UseTSA || o.TSAUrl != "" { + opts.UseTSA = true + if o.TSAUrl != "" { + opts.TSAUrl = o.TSAUrl + } + } + if o.Keys != nil { + def := o.Keys.GetIssuer("") + if def != nil { + opts.Issuer = def + } } if o.RootCerts != nil { opts.RootCerts = o.RootCerts diff --git a/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go index 099b9a0451..2f4f98f5b3 100644 --- a/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go @@ -27,6 +27,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" "github.com/open-component-model/ocm/pkg/mime" "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) const ARCH = "/tmp/ctf" @@ -250,6 +251,74 @@ Error: signing: github.com/mandelsoft/ref:v1: failed resolving component referen `)) }) }) + + It("keyless verification", func() { + buf := bytes.NewBuffer(nil) + + // create Root CA + Expect(env.CatchOutput(buf).Execute("create", "rsakeypair", "--ca", "CN=cerificate-authority", "root.priv")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +created rsa key pair root.priv[root.cert] +`)) + Expect(env.FileExists("root.priv")).To(BeTrue()) + Expect(env.FileExists("root.cert")).To(BeTrue()) + + // create CA used to create signing certificates + buf.Reset() + Expect(env.CatchOutput(buf).Execute("create", "rsakeypair", "--ca", "CN=acme.org", "--ca-key", "root.priv", "--ca-cert", "root.cert", "ca.priv")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +created rsa key pair ca.priv[ca.cert] +`)) + Expect(env.FileExists("ca.priv")).To(BeTrue()) + Expect(env.FileExists("ca.cert")).To(BeTrue()) + + // create signing vcertificate from CA + buf.Reset() + Expect(env.CatchOutput(buf).Execute("create", "rsakeypair", "CN=mandelsoft", "C=DE", "--ca-key", "ca.priv", "--ca-cert", "ca.cert", "--root-certs", "root.cert", "key.priv")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +created rsa key pair key.priv[key.cert] +`)) + Expect(env.FileExists("key.priv")).To(BeTrue()) + Expect(env.FileExists("key.cert")).To(BeTrue()) + + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMPONENTA, VERSION, func() { + env.Provider("mandelsoft") + }) + }) + + // sigh component with certificate + buf.Reset() + Expect(env.CatchOutput(buf).Execute("sign", "component", ARCH, "-K", "key.priv", "-k", "key.cert", "--ca-cert", "root.cert", "-s", "mandelsoft", "-I", "CN=mandelsoft")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... +successfully signed github.com/mandelsoft/test:v1 (digest SHA-256:5ed8bb27309c3c2fff43f3b0f3ebb56a5737ad6db4bc8ace73c5455cb86faf54) +`)) + // verify component without key + buf.Reset() + Expect(env.CatchOutput(buf).Execute("verify", "component", ARCH, "--ca-cert", "root.cert", "-I", "CN=mandelsoft")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... +no public key found for signature "mandelsoft" -> extract key from signature +successfully verified github.com/mandelsoft/test:v1 (digest SHA-256:5ed8bb27309c3c2fff43f3b0f3ebb56a5737ad6db4bc8ace73c5455cb86faf54) +`)) + + repo := Must(ctf.Open(env, accessobj.ACC_READONLY, ARCH, 0, env)) + defer Close(repo, "repo") + cv := Must(repo.LookupComponentVersion(COMPONENTA, VERSION)) + defer Close(cv, "cv") + + Expect(len(cv.GetDescriptor().Signatures)).To(Equal(1)) + + sig := cv.GetDescriptor().Signatures[0].Signature + + Expect(sig.Algorithm).To(Equal(rsa.Algorithm)) + Expect(sig.MediaType).To(Equal(signutils.MediaTypePEM)) + + _, algo, certs := Must3(signutils.GetSignatureFromPem([]byte(sig.Value))) + Expect(len(certs)).To(Equal(3)) + Expect(algo).To(Equal(rsa.Algorithm)) + }) }) func prepareEnv(env *TestEnv, componentAArchive, componentBArchive string) { diff --git a/cmds/ocm/commands/ocmcmds/routingslips/add/cmd.go b/cmds/ocm/commands/ocmcmds/routingslips/add/cmd.go index 3edfc60369..8a4efd6e2e 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/add/cmd.go @@ -181,7 +181,10 @@ func (a *action) Out() error { links = v.Leaves() break } else { - slip := v.Query(l) + slip, err := v.Query(l) + if err != nil { + return errors.ErrInvalid(routingslip.KIND_ROUTING_SLIP, l) + } if slip != nil { for _, d := range slip.Leaves() { links = append(links, routingslip.Link{ @@ -197,7 +200,10 @@ func (a *action) Out() error { } n := l[:i] d := l[i+1:] - slip := v.Query(n) + slip, err := v.Query(n) + if err != nil { + return errors.ErrInvalid(routingslip.KIND_ROUTING_SLIP, n) + } if slip == nil { return fmt.Errorf("link %q: slip %q not found", l, n) } diff --git a/cmds/ocm/commands/ocmcmds/routingslips/common/typehandler.go b/cmds/ocm/commands/ocmcmds/routingslips/common/typehandler.go index 4cd87c30c2..f5fbdf746c 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/common/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/common/typehandler.go @@ -13,6 +13,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/clictx" "github.com/open-component-model/ocm/pkg/contexts/ocm" "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip" + "github.com/open-component-model/ocm/pkg/errors" utils2 "github.com/open-component-model/ocm/pkg/utils" ) @@ -151,7 +152,11 @@ func (h *TypeHandler) all(c *comphdlr.Object) ([]output.Object, error) { }) } else { for _, n := range utils2.StringMapKeys(slips) { - h.addEntries(&result, c, slips.Get(n)) + s, err := slips.Get(n) + if err != nil { + return nil, errors.ErrInvalid(routingslip.KIND_ROUTING_SLIP, n) + } + h.addEntries(&result, c, s) } } } diff --git a/cmds/ocm/commands/ocmcmds/routingslips/get/cmd_test.go b/cmds/ocm/commands/ocmcmds/routingslips/get/cmd_test.go index ba3025a7a6..7fde37874c 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/get/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/get/cmd_test.go @@ -156,7 +156,61 @@ acme.org/test ` + digests(e2c, e2b) + ` name: unit-tests, status: passed It("gets dedicated yaml slip", func() { buf := bytes.NewBuffer(nil) Expect(env.CatchOutput(buf).Execute("get", "routingslip", ARCH, "a.company.com", "-ojson")).To(Succeed()) - Expect(len(buf.String())).To(Equal(3470)) + /* + { + "items": [ + { + "component": "test.de/x", + "version": "v1", + "routingSlip": "a.company.com", + "entry": { + "payload": { + "comment": "first other entry", + "type": "comment" + }, + "timestamp": "2023-12-19T09:21:10Z", + "digest": "sha256:bc27d55d56b83c53cdceb97294cdade29d99354ac1d8a5f7efd0e5cb1a238065", + } + }, + { + "component": "test.de/x", + "version": "v1", + "routingSlip": "a.company.com", + "entry": { + "payload": { + "comment": "second other entry", + "type": "comment" + }, + "timestamp": "2023-12-19T09:21:10Z", + "parent": "sha256:bc27d55d56b83c53cdceb97294cdade29d99354ac1d8a5f7efd0e5cb1a238065", + "digest": "sha256:61971501378144a22e03d12b5f61436026ce11ed7b237dd1dc08b680e9f31c8a", + } + }, + { + "component": "test.de/x", + "version": "v1", + "routingSlip": "a.company.com", + "entry": { + "payload": { + "name": "unit-tests", + "status": "passed", + "type": "acme.org/test" + }, + "timestamp": "2023-12-19T09:21:10Z", + "parent": "sha256:61971501378144a22e03d12b5f61436026ce11ed7b237dd1dc08b680e9f31c8a", + "digest": "sha256:bc61fef066810bb73f51a3811918780736cb8246b1ea5574f5c1c4e12221e7f2", + "signature": { + "algorithm": "RSASSA-PKCS1-V1_5", + "value": "89ef043b6cb61af11f22ca31940473540577d66dc75d6e585160d0e847d0ab086a3623246350a1028df42b2563adaee1407c68ff4c1df873db2a8bab3fcc2efb5bfa46de17ef1b3602b359f28e912ad80e419a5deb12dc6b1573e79613795e1f0baee20b91aa0f32fa0b0150fbf7a4fd4268ea8dba2048ddec0f0b0788ecd7ea6562f2e113643c82702e2221b887078f99153a222038d5042a5e9de92008b9de14f102bc5366abca6e5b9fa1c2bfcbb608e4fda3bc080c658d94852783b8b2e0aa3ef6c428e9e400961d1ff4b5444bc428c2c6f6ad0bf3b39da73f7dc72194c91e32bbe02befb0ed6c27849de26e6c3eca793d8b8f9f6e7a5fa6527f99bce1bb", + "mediaType": "application/vnd.ocm.signature.rsa" + } + } + } + ] + } + */ + fmt.Printf("\n%s\n", buf.String()) + Expect(len(buf.String())).To(Equal(2015)) }) Context("with links", func() { diff --git a/cmds/ocm/commands/verbs/sign/cmd.go b/cmds/ocm/commands/verbs/sign/cmd.go index a833163340..de77edac1a 100644 --- a/cmds/ocm/commands/verbs/sign/cmd.go +++ b/cmds/ocm/commands/verbs/sign/cmd.go @@ -5,9 +5,9 @@ package sign import ( + "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/hash/sign" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/hash" components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/sign" "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" @@ -20,6 +20,6 @@ func NewCommand(ctx clictx.Context) *cobra.Command { Short: "Sign components or hashes", }, verbs.Sign) cmd.AddCommand(components.NewCommand(ctx)) - cmd.AddCommand(hash.NewCommand(ctx)) + cmd.AddCommand(sign.NewCommand(ctx)) return cmd } diff --git a/cmds/ocm/topics/common/credentials/topic.go b/cmds/ocm/topics/common/credentials/topic.go index 66d53a40d9..821761d27c 100644 --- a/cmds/ocm/topics/common/credentials/topic.go +++ b/cmds/ocm/topics/common/credentials/topic.go @@ -59,7 +59,7 @@ The used credential management model is based on four elements: This is again achieved by a set of simple named attributes. There is only one defined property, which must always be present, the type attibute. - It denotes the type of the technical environment credentails are required for. + It denotes the type of the technical environment credentials are required for. For example, for accessing OCI or Git registries. Additionally, there may be any number of arbitrary attributes used to describe the concrete instance of such an environment and access paths in this environment, which @@ -82,7 +82,7 @@ The used credential management model is based on four elements: - *Identity Matchers* - The credential management must resolve crednetial requests against a set + The credential management must resolve credential requests against a set of credential assignments. This is not necessarily a complete attribute match for the involved consumer ids. There is typically some kind of matching involved. For example, an assigment is done for an OCI registry with a dedicated diff --git a/docs/reference/ocm.md b/docs/reference/ocm.md index fdb0bb1ffd..8aac0559ff 100644 --- a/docs/reference/ocm.md +++ b/docs/reference/ocm.md @@ -10,10 +10,12 @@ ocm [] ... ``` -X, --attribute stringArray attribute setting + --ca-cert stringArray additional root certificate authorities --config string configuration file --config-set strings apply configuration set -C, --cred stringArray credential setting -h, --help help for ocm + -I, --issuer stringArray issuer name or distinguished name (DN) (optionally for dedicated signature) ([:=] --logconfig string log config -L, --logfile string set log file --logkeys stringArray log tags/realms(with leading /) to be enabled ([/[+]]name{,[/[+]]name}[=level]) @@ -298,6 +300,15 @@ Alternatively a key can be specified as base64 encoded string if the argument start with the prefix ! or as direct string with the prefix =. +With --issuer it is possible to declare expected issuer +constraints for public key certificates provided as part of a signature +required to accept the provisioned public key (besides the successful +validation of the certificate). + +With --ca-cert it is possible to define additional root +certificates for signature verification, if public keys are provided +by a certificate delivered with the signature. + ### SEE ALSO @@ -328,6 +339,7 @@ start with the prefix ! or as direct string with the prefix * [ocm cache](ocm_cache.md) — Cache related commands * [ocm credentials](ocm_credentials.md) — Commands acting on credentials +* [ocm hash](ocm_hash.md) — Commands acting on hashes * [ocm oci](ocm_oci.md) — Dedicated command flavors for the OCI layer * [ocm ocm](ocm_ocm.md) — Dedicated command flavors for the Open Component Model * [ocm toi](ocm_toi.md) — Dedicated command flavors for the TOI layer diff --git a/docs/reference/ocm_configfile.md b/docs/reference/ocm_configfile.md index 5492fc83a4..239721c52d 100644 --- a/docs/reference/ocm_configfile.md +++ b/docs/reference/ocm_configfile.md @@ -112,7 +112,25 @@ The following configuration types are supported: <name>: data: <base64 encoded key representation> ... + issuers: + <name>: + commonName: acme.org + + Issuers define an expected distinguished name for + public key certificates optionally provided together with + signatures. They support the following fields: + - commonName *string* + - organization *string array* + - organizationalUnit *string array* + - country *string array* + - locality *string array* + - province *string array* + - streetAddress *string array* + - postalCode *string array* + + At least the given values must be present in the certificate + to be accepted for a successful signature validation. - logging.config.ocm.software The config type logging.config.ocm.software can be used to configure the logging aspect of a dedicated context type: diff --git a/docs/reference/ocm_create_rsakeypair.md b/docs/reference/ocm_create_rsakeypair.md index 95cf97f631..38af311b62 100644 --- a/docs/reference/ocm_create_rsakeypair.md +++ b/docs/reference/ocm_create_rsakeypair.md @@ -15,11 +15,13 @@ rsakeypair, rsa ### Options ``` - --cacert string certificate authority to sign public key - --cakey string private key for certificate authority + --ca create certificate for a signing authority + --ca-cert string certificate authority to sign public key + --ca-key string private key for certificate authority -E, --encrypt encrypt private key with new key -e, --encryptionKey string encrypt private key with given key -h, --help help for rsakeypair + --root-certs string root certificates used to validate used certificate authority --validity duration certificate validity (default 87600h0m0s) ``` @@ -30,10 +32,17 @@ Create an RSA public key pair and save to files. The default for the filename to store the private key is rsa.priv. If no public key file is specified, its name will be derived from the filename for -the private key (suffix .pub for public key or .cert for certificate). -If a certificate authority is given (--cacert) the public key -will be signed. In this case a subject (at least common name/issuer) and a private -key (--cakey) is required. If only a subject is given, the public key will be self-signed. +the private key (suffix .pub for public key or .cert +for certificate). If a certificate authority is given (--ca-cert) +the public key will be signed. In this case a subject (at least common +name/issuer) and a private key (--ca-key) for the ca used to sign the +key is required. + +If only a subject is given and no ca, the public key will be self-signed. +A signed public key always contains the complete certificate chain. If a +non-self-signed ca is used to sign the key, its certificate chain is verified. +Therefore, an additional root certificate (--root-certs) is required, +if no public root certificate was used to create the used ca. For signing the public key the following subject attributes are supported: - CN, common-name, issuer: Common Name/Issuer diff --git a/docs/reference/ocm_credential-handling.md b/docs/reference/ocm_credential-handling.md index a0e58ef395..78669506c4 100644 --- a/docs/reference/ocm_credential-handling.md +++ b/docs/reference/ocm_credential-handling.md @@ -35,7 +35,7 @@ The used credential management model is based on four elements: This is again achieved by a set of simple named attributes. There is only one defined property, which must always be present, the type attibute. - It denotes the type of the technical environment credentails are required for. + It denotes the type of the technical environment credentials are required for. For example, for accessing OCI or Git registries. Additionally, there may be any number of arbitrary attributes used to describe the concrete instance of such an environment and access paths in this environment, which @@ -58,7 +58,7 @@ The used credential management model is based on four elements: - *Identity Matchers* - The credential management must resolve crednetial requests against a set + The credential management must resolve credential requests against a set of credential assignments. This is not necessarily a complete attribute match for the involved consumer ids. There is typically some kind of matching involved. For example, an assigment is done for an OCI registry with a dedicated diff --git a/docs/reference/ocm_hash.md b/docs/reference/ocm_hash.md index a50398ae69..9b7264f48e 100644 --- a/docs/reference/ocm_hash.md +++ b/docs/reference/ocm_hash.md @@ -1,4 +1,4 @@ -## ocm hash — Hash And Normalization Operations +## ocm hash — Commands Acting On Hashes ### Synopsis @@ -21,5 +21,5 @@ ocm hash [] ... ##### Sub Commands -* [ocm hash componentversions](ocm_hash_componentversions.md) — hash component version +* ocm hash sign — sign hash diff --git a/docs/reference/ocm_sign_componentversions.md b/docs/reference/ocm_sign_componentversions.md index 839872dbf0..850c290109 100644 --- a/docs/reference/ocm_sign_componentversions.md +++ b/docs/reference/ocm_sign_componentversions.md @@ -16,11 +16,11 @@ componentversions, componentversion, cv, components, component, comps, comp, c ``` -S, --algorithm string signature handler (default "RSASSA-PKCS1-V1_5") - --ca-cert stringArray additional root certificates + --ca-cert stringArray additional root certificate authorities -c, --constraints constraints version constraint -H, --hash string hash algorithm (default "SHA-256") -h, --help help for componentversions - -I, --issuer string issuer name + -I, --issuer stringArray issuer name or distinguished name (DN) (optionally for dedicated signature) ([:=] --keyless use keyless signing --latest restrict component versions to latest --lookup stringArray repository name or spec for closure lookup fallback @@ -30,6 +30,8 @@ componentversions, componentversion, cv, components, component, comps, comp, c -R, --recursive recursively sign component versions --repo string repository name or spec -s, --signature stringArray signature name + --tsa use timestamp authority (default server: http://timestamp.digicert.com) + --tsa-url string TSA server URL --update update digest in component versions (default true) -V, --verify verify existing digests (default true) ``` diff --git a/docs/reference/ocm_sign_hash.md b/docs/reference/ocm_sign_hash.md index 8b27495da2..8360af3b18 100644 --- a/docs/reference/ocm_sign_hash.md +++ b/docs/reference/ocm_sign_hash.md @@ -11,6 +11,8 @@ ocm sign hash [] ``` -S, --algorithm string signature algorithm (default "RSASSA-PKCS1-V1_5") -h, --help help for hash + --publicKey string public key certificate file + --rootCerts string root certificates file ``` ### Description diff --git a/docs/reference/ocm_verify_componentversions.md b/docs/reference/ocm_verify_componentversions.md index a0c152e595..13a70b9ecb 100644 --- a/docs/reference/ocm_verify_componentversions.md +++ b/docs/reference/ocm_verify_componentversions.md @@ -15,9 +15,10 @@ componentversions, componentversion, cv, components, component, comps, comp, c ### Options ``` - --ca-cert stringArray additional root certificates + --ca-cert stringArray additional root certificate authorities -c, --constraints constraints version constraint -h, --help help for componentversions + -I, --issuer stringArray issuer name or distinguished name (DN) (optionally for dedicated signature) ([:=] --keyless use keyless signing --latest restrict component versions to latest -L, --local verification based on information found in component versions, only diff --git a/go.mod b/go.mod index 8cf79e9c69..b1c9bb90d0 100644 --- a/go.mod +++ b/go.mod @@ -79,6 +79,7 @@ require ( ) require ( + github.com/InfiniteLoopSpace/go_S-MIME v0.0.0-20181221134359-3f58f9a4b2b6 github.com/distribution/reference v0.5.0 github.com/imdario/mergo v0.3.16 github.com/mandelsoft/vfs v0.4.0 diff --git a/go.sum b/go.sum index 38b2916440..040b4ee6ff 100644 --- a/go.sum +++ b/go.sum @@ -689,6 +689,8 @@ github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/ github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= github.com/DataDog/sketches-go v1.4.3 h1:ZB9nijteJRFUQixkQfatCqASartGNfiolIlMiEv3u/w= github.com/DataDog/sketches-go v1.4.3/go.mod h1:XR0ns2RtEEF09mDKXiKZiQg+nfZStrq1ZuL1eezeZe0= +github.com/InfiniteLoopSpace/go_S-MIME v0.0.0-20181221134359-3f58f9a4b2b6 h1:TkEaE2dfSBN9onWsQ1pC9EVMmVDJqkYWNUwS6+EYxlM= +github.com/InfiniteLoopSpace/go_S-MIME v0.0.0-20181221134359-3f58f9a4b2b6/go.mod h1:yhh4MGRGdTpTET5RhSJx4XNCEkJljP3k8MxTTB3joQA= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/method.go b/pkg/contexts/ocm/accessmethods/ociartifact/method.go index 5e937f7cf3..e278b6fe74 100644 --- a/pkg/contexts/ocm/accessmethods/ociartifact/method.go +++ b/pkg/contexts/ocm/accessmethods/ociartifact/method.go @@ -293,8 +293,10 @@ func (m *accessMethod) getArtifact() error { art, err := m.repo.LookupArtifact(m.ref.Repository, m.ref.Version()) m.finalizer.Close(art, "artifact for accessing %s", m.reference) m.art, m.err = art, err - m.mime = artdesc.ToContentMediaType(m.art.GetDescriptor().MimeType()) + artifactset.SynthesizedBlobFormat - m.digest = art.Digest() + if art != nil { + m.mime = artdesc.ToContentMediaType(m.art.GetDescriptor().MimeType()) + artifactset.SynthesizedBlobFormat + m.digest = art.Digest() + } } return m.err } diff --git a/pkg/contexts/ocm/attrs/signingattr/config.go b/pkg/contexts/ocm/attrs/signingattr/config.go index eb57849f0b..81bbb6f9d7 100644 --- a/pkg/contexts/ocm/attrs/signingattr/config.go +++ b/pkg/contexts/ocm/attrs/signingattr/config.go @@ -5,11 +5,13 @@ package signingattr import ( + "crypto/x509/pkix" "encoding/base64" "encoding/json" "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" + "golang.org/x/exp/slices" cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" "github.com/open-component-model/ocm/pkg/errors" @@ -28,11 +30,51 @@ func init() { cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) } +type Issuer struct { + CommonName string `json:"commonName,omitempty"` + Organization []string `json:"organization,omitempty"` + OrganizationalUnit []string `json:"organizationalUnit,omitempty"` + + Country []string `json:"country,omitempty"` + Locality []string `json:"locality,omitempty"` + Province []string `json:"province,omitempty"` + StreetAddress []string `json:"streetAddress,omitempty"` + PostalCode []string `json:"postalCode,omitempty"` +} + +func (i *Issuer) Get() *pkix.Name { + return &pkix.Name{ + CommonName: i.CommonName, + + Country: slices.Clone(i.Country), + Organization: slices.Clone(i.Organization), + OrganizationalUnit: slices.Clone(i.OrganizationalUnit), + Locality: slices.Clone(i.Locality), + Province: slices.Clone(i.Province), + StreetAddress: slices.Clone(i.StreetAddress), + PostalCode: slices.Clone(i.PostalCode), + } +} + +func (i *Issuer) Set(issuer *pkix.Name) { + i.CommonName = issuer.CommonName + + i.Country = slices.Clone(issuer.Country) + i.Organization = slices.Clone(issuer.Organization) + i.OrganizationalUnit = slices.Clone(issuer.OrganizationalUnit) + i.Locality = slices.Clone(issuer.Locality) + i.Province = slices.Clone(issuer.Province) + i.StreetAddress = slices.Clone(issuer.StreetAddress) + i.PostalCode = slices.Clone(issuer.PostalCode) +} + // Config describes a memory based repository interface. type Config struct { runtime.ObjectVersionedType `json:",inline"` - PublicKeys map[string]KeySpec `json:"publicKeys"` - PrivateKeys map[string]KeySpec `json:"privateKeys"` + PublicKeys map[string]KeySpec `json:"publicKeys,omitempty"` + PrivateKeys map[string]KeySpec `json:"privateKeys,omitempty"` + Issuers map[string]Issuer `json:"issuers,omitempty"` + TSAUrl string `json:"tsaURL,omitempty"` } type RawData []byte @@ -96,6 +138,16 @@ func (a *Config) GetType() string { return ConfigType } +func (a *Config) AddIssuer(name string, issuer *pkix.Name) { + var i Issuer + + i.Set(issuer) + if a.Issuers == nil { + a.Issuers = map[string]Issuer{} + } + a.Issuers[name] = i +} + func (a *Config) addKey(set *map[string]KeySpec, name string, key interface{}) { if *set == nil { *set = map[string]KeySpec{} @@ -155,7 +207,7 @@ func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { return errors.Wrapf(a.ApplyToRegistry(Get(t)), "applying config failed") } -func (a *Config) ApplyToRegistry(registry signing.KeyRegistryFuncs) error { +func (a *Config) ApplyToRegistry(registry signing.Registry) error { for n, k := range a.PublicKeys { key, err := k.Get() if err != nil { @@ -170,6 +222,12 @@ func (a *Config) ApplyToRegistry(registry signing.KeyRegistryFuncs) error { } registry.RegisterPrivateKey(n, key) } + for n, k := range a.Issuers { + registry.RegisterIssuer(n, k.Get()) + } + if a.TSAUrl != "" { + registry.SetTSAUrl(a.TSAUrl) + } return nil } @@ -190,5 +248,24 @@ public and private keys. A key value might be given by one of the fields: <name>: data: <base64 encoded key representation> ... + issuers: + <name>: + commonName: acme.org + +Issuers define an expected distinguished name for +public key certificates optionally provided together with +signatures. They support the following fields: +- commonName *string* +- organization *string array* +- organizationalUnit *string array* +- country *string array* +- locality *string array* +- province *string array* +- streetAddress *string array* +- postalCode *string array* + +At least the given values must be present in the certificate +to be accepted for a successful signature validation. + ` diff --git a/pkg/contexts/ocm/compdesc/meta/v1/signature.go b/pkg/contexts/ocm/compdesc/meta/v1/signature.go index 3516b45a63..0593192b9b 100644 --- a/pkg/contexts/ocm/compdesc/meta/v1/signature.go +++ b/pkg/contexts/ocm/compdesc/meta/v1/signature.go @@ -166,13 +166,22 @@ func (s *SignatureSpec) ConvertToSigning() *signing.Signature { } } +type TimestampSpec struct { + // Value contains the PEM encoded TSA value. + Value string `json:"value"` + Time *Timestamp `json:"time,omitempty"` + + // later, we can add other storage kind, like localBlob. +} + // Signature defines a digest and corresponding signature, identifiable by name. // +k8s:deepcopy-gen=true // +k8s:openapi-gen=true type Signature struct { - Name string `json:"name"` - Digest DigestSpec `json:"digest"` - Signature SignatureSpec `json:"signature"` + Name string `json:"name"` + Digest DigestSpec `json:"digest"` + Signature SignatureSpec `json:"signature"` + Timestamp *TimestampSpec `json:"timestamp,omitempty"` } // Copy provides a copy of the signature data. diff --git a/pkg/contexts/ocm/compdesc/signing.go b/pkg/contexts/ocm/compdesc/signing.go index f263f51044..6ce41e7b90 100644 --- a/pkg/contexts/ocm/compdesc/signing.go +++ b/pkg/contexts/ocm/compdesc/signing.go @@ -13,16 +13,19 @@ import ( metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/signing" + "github.com/open-component-model/ocm/pkg/signing/signutils" + "github.com/open-component-model/ocm/pkg/utils" ) const ( - KIND_HASH_ALGORITHM = "hash algorithm" - KIND_SIGN_ALGORITHM = "signing algorithm" - KIND_NORM_ALGORITHM = "normalization algorithm" - KIND_VERIFY_ALGORITHM = "signature verification algorithm" - KIND_PUBLIC_KEY = "public key" - KIND_PRIVATE_KEY = "private key" - KIND_SIGNATURE = "signature" + KIND_HASH_ALGORITHM = signutils.KIND_HASH_ALGORITHM + KIND_SIGN_ALGORITHM = signutils.KIND_SIGN_ALGORITHM + KIND_NORM_ALGORITHM = signutils.KIND_NORM_ALGORITHM + KIND_VERIFY_ALGORITHM = signutils.KIND_VERIFY_ALGORITHM + KIND_PUBLIC_KEY = signutils.KIND_PUBLIC_KEY + KIND_PRIVATE_KEY = signutils.KIND_PRIVATE_KEY + KIND_SIGNATURE = signutils.KIND_SIGNATURE + KIND_DIGEST = signutils.KIND_DIGEST ) // IsNormalizeable checks if componentReferences and resources contain digest. @@ -66,6 +69,62 @@ func Hash(cd *ComponentDescriptor, normAlgo string, hash hash.Hash) (string, err return hex.EncodeToString(hash.Sum(nil)), nil } +type CompDescDigest struct { + normAlgo string + hashAlgo string + normalized []byte + digest string +} + +type CompDescDigests struct { + cd *ComponentDescriptor + digests []*CompDescDigest +} + +func NewCompDescDigests(cd *ComponentDescriptor) *CompDescDigests { + return &CompDescDigests{ + cd: cd, + } +} + +func (d *CompDescDigests) Descriptor() *ComponentDescriptor { + return d.cd +} + +func (d *CompDescDigests) Get(normAlgo string, hasher signing.Hasher) ([]byte, string, error) { + var normalized []byte + + for _, e := range d.digests { + if e.normAlgo == normAlgo { + normalized = e.normalized + if e.hashAlgo == hasher.Algorithm() { + return e.normalized, e.digest, nil + } + } + } + + var err error + if normalized == nil { + normalized, err = Normalize(d.cd, normAlgo) + if err != nil { + return nil, "", fmt.Errorf("failed normalising component descriptor %w", err) + } + } + hash := hasher.Create() + if _, err = hash.Write(normalized); err != nil { + return nil, "", fmt.Errorf("failed hashing the normalisedComponentDescriptorJson: %w", err) + } + + e := &CompDescDigest{ + normAlgo: normAlgo, + hashAlgo: hasher.Algorithm(), + normalized: normalized, + digest: hex.EncodeToString(hash.Sum(nil)), + } + d.digests = append(d.digests, e) + return normalized, e.digest, nil +} + func NormHash(cd *ComponentDescriptor, normAlgo string, hash hash.Hash) ([]byte, string, error) { if hash == nil { return nil, metav1.NoDigest, nil @@ -85,13 +144,26 @@ func NormHash(cd *ComponentDescriptor, normAlgo string, hash hash.Hash) ([]byte, // Sign signs the given component-descriptor with the signer. // The component-descriptor has to contain digests for componentReferences and resources. -func Sign(cctx credentials.Context, cd *ComponentDescriptor, privateKey interface{}, signer signing.Signer, hasher signing.Hasher, signatureName, issuer string) error { +func Sign(cctx credentials.Context, cd *ComponentDescriptor, privateKey signutils.GenericPrivateKey, signer signing.Signer, hasher signing.Hasher, signatureName, issuer string) error { digest, err := Hash(cd, JsonNormalisationV1, hasher.Create()) if err != nil { return fmt.Errorf("failed getting hash for cd: %w", err) } - signature, err := signer.Sign(cctx, digest, hasher.Crypto(), issuer, privateKey) + iss, err := signutils.ParseDN(issuer) + if err != nil { + return err + } + + sctx := &signing.DefaultSigningContext{ + Hash: hasher.Crypto(), + PrivateKey: privateKey, + PublicKey: nil, + RootCerts: nil, + Issuer: iss, + } + + signature, err := signer.Sign(cctx, digest, sctx) if err != nil { return fmt.Errorf("failed signing hash of normalised component descriptor, %w", err) } @@ -116,7 +188,7 @@ func Sign(cctx credentials.Context, cd *ComponentDescriptor, privateKey interfac // Verify verifies the signature (selected by signatureName) and hash of the component-descriptor (as specified in the signature). // Does NOT resolve resources or referenced component-descriptors. // Returns error if verification fails. -func Verify(cd *ComponentDescriptor, registry signing.Registry, signatureName string) error { +func Verify(cd *ComponentDescriptor, registry signing.Registry, signatureName string, rootCA ...signutils.GenericCertificatePool) error { // find matching signature matchingSignature := cd.SelectSignatureByName(signatureName) if matchingSignature == nil { @@ -135,7 +207,13 @@ func Verify(cd *ComponentDescriptor, registry signing.Registry, signatureName st return errors.ErrUnknown(KIND_HASH_ALGORITHM, matchingSignature.Digest.HashAlgorithm) } // Verify author of signature - err := verifier.Verify(matchingSignature.Digest.Value, hasher.Crypto(), matchingSignature.ConvertToSigning(), publicKey) + sctx := &signing.DefaultSigningContext{ + Hash: hasher.Crypto(), + PublicKey: publicKey, + RootCerts: utils.Optional(rootCA), + Issuer: registry.GetIssuer(signatureName), + } + err := verifier.Verify(matchingSignature.Digest.Value, matchingSignature.ConvertToSigning(), sctx) if err != nil { return fmt.Errorf("failed verifying: %w", err) } diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go index 564afdabf6..a3ad90c77d 100644 --- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go +++ b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go @@ -78,7 +78,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _ResourcesComponentDescriptorOcmV3SchemaYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x1a\x5d\x6f\xdb\x38\xf2\xdd\xbf\x62\xb0\x29\x20\xa7\x89\xe2\x24\x45\x0f\xa8\x5f\x82\x5c\x8b\x03\x8a\xbb\xdd\x2c\xda\xde\x3d\x5c\x9a\x2b\x68\x69\x64\xb3\x4b\x91\x3e\x92\x76\xe2\xed\xf6\xbf\x1f\x48\x8a\x12\x25\x4b\xb6\x65\xbb\xbd\x3b\xec\xf6\xa1\x31\xc9\xf9\xe2\x70\x66\x38\x33\xe2\x33\x9a\x8e\x21\x9a\x69\x3d\x57\xe3\xd1\x68\x4a\x64\x8a\x1c\xe5\x45\xc2\xc4\x22\x1d\xa9\x64\x86\x39\x51\xa3\x44\xe4\x73\xc1\x91\xeb\x38\x45\x95\x48\x3a\xd7\x42\xc6\x22\xc9\xe3\xe5\x0b\xc2\xe6\x33\x72\x15\x0d\x9e\x39\xd8\x80\xd6\x67\x25\x78\xec\x66\x2f\x84\x9c\x8e\x52\x49\x32\x3d\xba\xbe\xbc\xbe\x8c\xaf\xae\x0b\xd2\xd1\xc0\x13\xa4\x82\x8f\x21\xba\x7b\xfd\x23\xbc\xf6\xcc\xe0\x4d\xc9\x0c\x96\x2f\xa0\xc2\xc8\x28\xa7\x06\x41\x8d\x07\x00\x39\x6a\x62\xfe\x02\xe8\xd5\x1c\xc7\x10\x89\xc9\x67\x4c\x74\x64\xa7\xea\xd4\xcb\x6d\xc0\x12\xa5\xa2\x82\x5b\xe4\x94\x68\xe2\xa0\x25\xfe\x7b\x41\x25\xa6\x8e\x1c\x40\x0c\x11\x27\x39\x46\xd5\xb0\xc0\x73\x33\x24\x4d\xad\x18\x84\xfd\x2c\xc5\x1c\xa5\xa6\xa8\xc6\x90\x11\xa6\xd0\xae\xcf\xab\xd9\x82\x82\xa1\xe6\x7f\x03\x3c\x93\x98\x8d\x21\x3a\x19\x05\x3b\xaa\x54\xfd\x53\xc0\xb9\x60\xbb\x05\x55\x22\x23\x4f\x98\xbe\xc7\x7c\x89\xd2\xa3\x32\x32\x41\xa6\xb6\x60\x3a\x20\x8f\x32\x97\x62\x49\x53\x94\x5b\x90\x3c\x98\x47\x4b\x24\x12\xb3\xf2\x81\x86\x9b\x74\x87\xa2\xb4\xa4\x7c\x5a\x4e\x66\x42\xe6\x44\x8f\x21\x25\x1a\x63\x4d\x73\x1c\xd8\x83\x94\x53\xec\x3c\xc9\x75\x65\x12\x36\x15\x92\xea\x59\x5e\x31\x9b\x13\xad\x51\x9a\xa3\xfe\xd7\x3d\x89\x7f\x7d\x30\xff\x5d\xc6\xaf\x46\x9f\xe2\x87\xb3\x67\xa5\x9c\x82\x67\x74\x3a\x86\x2f\xf0\x75\x87\x63\x0c\xf5\x57\x88\x45\xa4\x24\x2b\x47\x8d\x6a\xcc\x4b\x81\xba\x55\x1b\x79\x42\x9d\xdb\xdb\xc1\xf4\x08\x5b\x60\x97\x2e\xea\x86\xd5\xa2\x73\x8b\x3d\x86\x2f\x5f\xbb\x2c\x2a\x50\xdd\xf2\xfe\x32\x7e\x15\x28\x4c\xd1\x29\xa7\x7c\xda\xa4\x1f\x4d\x84\x60\x48\xb8\x07\x0b\xce\xaf\x53\x1b\x16\x66\xbb\xf7\x0c\xcc\x29\x05\x7e\x50\x53\x9b\xdb\x97\x23\x92\x93\xa7\xbf\x21\x9f\xea\xd9\x18\xae\x5f\xbe\x1c\xb4\xda\x40\xec\x8c\xe0\xe1\xf9\xf0\xfe\xe2\xa1\x31\x75\xfa\xdc\xcf\x7d\xb9\x3e\xff\x3a\x1c\xd5\x96\x3f\xb5\xa0\x7c\x32\x38\xa7\x46\x37\x03\x00\x9a\x22\xd7\x54\xaf\x6e\xb5\x96\x74\xb2\xd0\xf8\x57\x5c\x39\x51\x73\xca\x4b\xb9\xda\xa4\x32\xcc\x87\xf7\xf1\xa7\x33\x2f\x88\x9f\x3c\xbd\x71\xa4\x6b\xbe\xec\x68\x9e\x80\x26\xbf\x20\x87\x4c\x8a\x1c\x94\x5d\x30\x71\x15\x08\x4f\x81\xa4\x9f\x17\x4a\x63\x0a\x5a\x00\x61\x4c\x3c\x02\xe1\x20\xe6\x4e\xbf\xc0\x90\xa4\x94\x4f\x21\x5a\x46\xe7\x90\x93\xcf\x26\x78\x73\xb6\x3a\xb7\xa8\x76\x7c\x91\x53\x5e\xcc\x7a\x5e\x33\xaa\x20\x47\xc2\x15\xe8\x19\x42\x26\x0c\x55\x43\xc4\xa9\x5f\x01\x91\x68\x58\x19\xcb\xa2\x69\x5d\x5e\xe5\x05\xbe\xba\xb8\xbe\x78\x11\xfe\x8e\x33\x21\xce\x26\x44\x16\x73\xcb\x10\x60\xd9\x06\x71\x75\x71\xed\x7f\x95\x60\x01\x7c\xf9\xb3\x86\x16\x2a\x7b\xf9\x70\x33\xbc\xfc\xed\xfe\x2a\x7e\xf5\xf0\x31\x7d\x7e\x3a\xbc\x19\x7f\xbc\x08\x27\x4e\x6f\xda\xa7\xe2\xe1\xf0\x66\x5c\x4d\xfe\xf6\x31\xb5\x67\x74\x1b\xff\x33\x7e\x30\xfe\xe1\x7f\x7b\x92\x3b\x02\x9f\x7a\x8e\x67\xc3\x70\xe1\xcc\x12\xa9\xcd\x58\xc8\xc2\x07\x1b\x96\xdf\x66\x7a\xdb\x02\xe7\xca\xf8\x91\x32\x51\xaf\xd5\x31\xdb\x4c\x39\x82\xaf\xce\x14\xe7\x42\x51\x2d\xe4\xea\xb5\xe0\x1a\x9f\x74\x9f\x20\x66\xa0\xba\x82\x96\xa5\xd0\x0c\x2a\xc1\x1e\x45\x42\xdf\xb5\xf3\x26\x8c\xdd\x65\x15\x97\x8e\xdb\xb0\x81\x5a\xc5\xd2\xa6\x9c\x85\xac\x13\xa2\xf0\xef\x92\x45\x55\x4c\x5c\x13\xd9\xfc\x2b\xc0\xc2\xa9\xd6\xd8\xe4\x6f\x9b\x20\x8e\xfd\x48\xe6\xf3\x5a\x24\xdd\x88\x0a\x80\x7c\x91\x8f\xe1\x3e\x5a\x48\xf6\x33\xd1\xb3\xe8\x1c\x22\x35\x23\xd7\x2f\xff\x14\xa7\x74\x8a\x4a\x47\x0f\x83\x06\x9d\xbe\x94\xad\x8e\xa7\x54\x69\xb9\x32\xd4\xef\x5e\xbf\x2d\x87\x0f\xe6\x0c\x48\x92\xa0\x52\x3b\xe6\x57\x46\x33\x16\xca\x5c\xf0\x05\x2a\x2a\x18\x9a\x11\x3e\x69\xe4\xe6\xca\x51\xa7\x5b\x8c\x65\x00\x30\xa5\x7a\xb6\x98\xdc\x6e\xe6\xbd\xd1\xda\xec\xd0\x98\x40\x70\xa0\x76\x26\xdb\xcb\x1a\x9b\x6a\x73\x02\x96\xea\x2f\x18\x6d\x41\x37\x56\xba\x19\x22\x11\x79\x4e\xf5\x26\x9f\xe0\x82\xe3\x21\x7a\x39\x70\xdf\x3f\x09\x8e\xce\x30\x94\x58\xc8\x04\xdf\x94\x0e\xd7\x43\x1c\x93\xac\x94\x83\x22\x11\x29\xc7\x86\x42\x39\x70\x26\xd4\x23\xe7\x59\x13\x7c\xf7\x60\x57\xa0\xe0\x93\x96\xe4\x6d\x01\xb0\x25\x9b\x59\xa3\x73\x84\x7c\x7d\x87\xe3\xd8\x23\xa5\x0f\xdd\xd8\x8e\xf9\xea\x2e\xab\x87\xbf\x56\x2a\x0e\x2f\xda\x0e\x18\x7a\xec\x0e\xe0\xa6\x46\xf4\xc0\x03\x00\x17\xcd\xde\xcf\x31\xe9\x61\x46\x33\xa2\x66\xb7\xbe\x06\xa8\x8c\xcb\x94\x16\x8c\x2a\x5b\x8a\xac\x2f\xdb\x3c\x78\x87\xb4\xbf\xcd\xe0\x6a\x0c\x37\x66\xdb\xed\x42\xec\x90\xa0\xb7\x43\x0c\x5c\x0e\x4e\xf4\x42\x62\x4f\x25\x91\x0d\x1a\x30\xa3\x1c\x53\x4a\x3e\x78\x9f\xeb\xaf\x93\x96\x22\xac\xe7\xe6\xdc\x54\x29\x47\x05\x55\xbf\x5b\x3e\xcc\xd0\x01\xb9\x0b\x46\x64\x36\x2d\x2d\xd5\x02\x41\x79\xb4\x51\x7f\xfb\xc6\x29\x67\xa2\xe5\xb0\xa4\xb7\xa7\xde\xb6\x16\x6c\x8e\xdf\x16\x27\xaf\xfc\x26\xac\xd5\x82\x7d\x76\x62\xd6\xec\xc9\xdd\x2c\x68\x0a\x88\x37\xfb\x78\x62\x97\xa2\xf6\xa9\x53\x9b\x91\xb3\x05\xe6\x28\x21\xba\xb7\x7e\x4b\x15\x95\xfd\x29\xa7\xab\x3e\x17\xf1\xc6\x9b\xaf\x61\x61\x12\x8b\x2b\xd6\x71\x39\x8a\x9d\x7d\x97\x8e\xd3\x9e\x96\xdb\xd8\xef\xda\x1d\x18\x74\x5d\xa0\xd9\x79\xe9\x64\xd3\x34\x6a\x7b\x8c\x4a\x26\xef\x7c\x2a\xb6\x35\xa7\x25\x26\x6d\x43\x89\x3c\x41\x5b\x5c\xc3\xb0\x6a\x86\x32\x91\x10\x76\x5a\xa4\x42\x5d\xf9\x95\xb7\xc0\xf7\xc8\x30\xd1\x62\x5b\x57\xad\xd3\x60\x7b\xdd\xfa\xb6\x6c\x2b\xc4\xde\x77\xa3\xe5\x3e\x77\xed\x50\xb5\x1a\xd2\xe1\x4d\xd3\x96\x4e\x50\x3f\x5b\xde\x94\x28\xc2\x09\x90\x44\x2f\x08\x63\xab\x71\xc5\x29\xb6\x77\xcc\xe3\x08\xd4\x1c\x13\x4a\x98\x31\x4e\x2d\x69\x62\x99\xfc\xff\xe6\x96\x7b\x24\x8e\x4d\x67\x16\x1c\x9b\x89\x63\xa1\x50\xbe\x60\x6c\x87\xcc\xaf\x11\x52\xbd\xd7\x57\x57\x7f\xcf\x2a\xd3\x13\x50\x7d\x5b\xf8\x70\x62\xf1\xad\x0f\x57\x54\xce\x8b\xc6\xd7\x42\x69\xc8\x89\x4e\x66\x81\x1b\xa8\xb5\x90\xbd\x5e\x70\x32\x9b\xf2\x05\x53\x61\x06\xfd\x47\x0d\x53\xee\xca\xc5\xe0\x23\x45\x79\x47\xac\xba\x48\xdc\x21\xec\x5c\xd4\x5a\x13\x88\xce\x21\xc2\x27\x8d\x92\x13\x56\xd6\xf5\xff\x8b\x95\x96\x48\xe8\x9f\x99\xd8\xbd\xd4\xb2\xbb\xfb\x0b\x65\xa8\x56\x4a\x63\xde\x1f\xf7\xae\x8d\xe1\xb7\x8e\x0b\x22\xa1\x6f\x73\x32\x3d\xa8\xd7\x61\x87\xd4\x50\x79\xe7\x6f\xb6\xa3\x34\x41\xc2\x9e\x99\xb7\x94\x3a\x9b\x2d\x5d\xcd\x4a\x9d\x07\x6c\x8c\x91\x95\xf7\xb8\xc3\xf6\x03\x51\x21\x52\x04\x55\x3f\x2b\xeb\xaa\xc3\x6e\xcd\x06\xea\xa9\x82\x29\xc4\x72\xc2\x69\x86\x4a\x37\x2b\xb0\x06\xd3\x3d\xcb\x3c\xa7\x19\x17\x9a\x9d\xa3\x38\x09\x14\x68\xb1\x85\x63\xd3\x50\xd7\xd9\x39\x08\xcf\x4a\x13\x39\x45\x8d\x29\x24\x82\xeb\x32\xf9\xe9\x24\xaf\xe8\xaf\x1b\xf7\x62\xd6\x81\x72\x98\xac\x34\x2a\xcf\x63\x62\x94\xdd\xa4\xcb\x17\xf9\xc4\x1c\xe8\x00\xa0\xd3\x65\x0f\x30\x97\x8c\x32\xac\x6e\xc2\x43\x2d\xa6\x45\xc2\xca\x7a\x3c\xab\x2e\xbd\xf8\xf5\x50\x1d\xa0\x67\x44\x03\x55\x76\xef\x46\xfd\x94\xdb\xb5\x1f\xcc\xa2\xfa\x01\x52\x2a\x6d\xf6\xbc\xea\x3c\x0f\xaf\xb7\xbb\x23\xf9\xd7\x37\x50\xd8\x5d\xd3\xcf\x36\x1b\x67\xdd\x30\xad\xbf\xc3\x23\xd5\xb3\x42\x35\xc9\x42\x4a\xe4\xba\x4a\x50\xa0\x7a\x9c\xb1\x49\x4b\x3e\xb4\xbe\x2b\x72\x9e\x3e\x3a\xda\xf4\x1c\xa2\x4d\x89\x7f\x64\x3f\xdb\xef\x12\x7b\x18\xc7\x4c\x39\xba\xd2\x86\xe0\x42\xfd\x3e\xd7\xf8\x00\xa0\x6a\xf4\x1e\xe0\x8a\x0b\xff\x0d\xe7\xc0\x8b\xdb\x08\x53\x2a\x7a\xb1\xe1\x7b\xcd\x00\x60\x8a\x1c\x25\x4d\xfe\x8b\xdf\x5a\x0a\x09\xdc\xe7\x96\x62\xf0\xbd\x7d\xf6\x38\x8d\xcd\xdf\x99\x4f\x57\x07\xe7\xe6\xbf\x95\x4b\xd7\x4c\xf4\x7b\x25\xe6\xf5\xc7\x61\x7d\x2d\xf0\x9b\xd8\x53\xdf\xce\x98\xda\xd4\xdd\xae\x5f\xc1\xb6\xff\x93\xd1\xc4\x16\x94\xfe\x26\x2e\x32\x43\x33\x0c\xba\x64\xde\xbc\xf4\xbe\x3b\x2d\x3a\x10\x47\x2a\x89\x1b\x9f\x67\x83\x6f\xd0\x2e\x71\x3f\x12\x1f\x59\xaf\xac\xaa\x86\x4e\x7f\xfa\x6b\x95\xf2\x86\xa7\x1d\x55\xd3\x28\xda\x05\xa1\x99\xf2\xec\x84\xd4\x08\xb9\xd1\x60\xd0\x30\x97\xd0\xd2\x4d\xdc\x9c\xd3\x7f\x54\xb1\x35\x86\xe8\x17\xca\xd3\xe2\x67\xf8\xce\x34\x76\x66\x15\x0d\xea\x26\x50\xa1\xd7\x6c\x33\x34\xf5\xa0\x60\xcb\x2f\x1a\x4f\x75\xcb\x97\xb8\xe7\x6e\x59\x89\x4c\x3f\x12\x89\xd5\x82\xcd\x3a\x8d\x4c\x9d\xf4\x13\xc1\x95\x1e\x43\x54\x7e\xe1\x08\xf6\xe3\x77\xe0\x90\x3b\x1e\xf7\xb9\x0d\xae\xbd\xbc\xd9\xed\xf9\x64\xe3\xfc\xbb\x8f\x72\xed\x51\x50\x04\x27\x3e\x1b\x66\xab\x73\x78\x44\x10\x9c\xad\x8a\x87\x70\xb6\x68\x14\x1c\x6b\x8e\xdf\xee\x33\xc5\xd7\x88\xf2\xdb\xd8\x01\xcf\x3e\x4b\x1a\x51\xe3\xd3\xda\x01\x34\xdb\x3f\x3f\x45\xff\x09\x00\x00\xff\xff\x74\x46\x86\xd2\xc2\x2d\x00\x00") +var _ResourcesComponentDescriptorOcmV3SchemaYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x1a\x5d\x6f\xdb\x38\xf2\xdd\xbf\x62\xb0\x29\x20\xa7\x89\xe2\x24\x45\x0f\xa8\x5f\x82\x5c\x8b\x03\x8a\xbb\xdd\x2c\xda\xde\x3d\x5c\x9a\x2b\x68\x69\x64\xb3\x4b\x91\x3e\x92\x76\xe2\xed\xf6\xbf\x1f\x48\x8a\x12\x25\x4b\xb6\x65\xbb\xbd\x3b\xec\xf6\xa1\x31\xa9\xf9\xe2\x70\x66\x38\x33\xe4\x33\x9a\x8e\x21\x9a\x69\x3d\x57\xe3\xd1\x68\x4a\x64\x8a\x1c\xe5\x45\xc2\xc4\x22\x1d\xa9\x64\x86\x39\x51\xa3\x44\xe4\x73\xc1\x91\xeb\x38\x45\x95\x48\x3a\xd7\x42\xc6\x22\xc9\xe3\xe5\x0b\xc2\xe6\x33\x72\x15\x0d\x9e\x39\xd8\x80\xd6\x67\x25\x78\xec\x66\x2f\x84\x9c\x8e\x52\x49\x32\x3d\xba\xbe\xbc\xbe\x8c\xaf\xae\x0b\xd2\xd1\xc0\x13\xa4\x82\x8f\x21\xba\x7b\xfd\x23\xbc\xf6\xcc\xe0\x4d\xc9\x0c\x96\x2f\xa0\xc2\xc8\x28\xa7\x06\x41\x8d\x07\x00\x39\x6a\x62\xfe\x02\xe8\xd5\x1c\xc7\x10\x89\xc9\x67\x4c\x74\x64\xa7\xea\xd4\xcb\x65\xc0\x12\xa5\xa2\x82\x5b\xe4\x94\x68\xe2\xa0\x25\xfe\x7b\x41\x25\xa6\x8e\x1c\x40\x0c\x11\x27\x39\x46\xd5\xb0\xc0\x73\x33\x24\x4d\xad\x18\x84\xfd\x2c\xc5\x1c\xa5\xa6\xa8\xc6\x90\x11\xa6\xd0\x7e\x9f\x57\xb3\x05\x05\x43\xcd\xff\x06\x78\x26\x31\x1b\x43\x74\x32\x0a\x56\x54\xa9\xfa\xa7\x80\x73\xc1\x76\x0b\xaa\x44\x46\x9e\x30\x7d\x8f\xf9\x12\xa5\x47\x65\x64\x82\x4c\x6d\xc1\x74\x40\x1e\x65\x2e\xc5\x92\xa6\x28\xb7\x20\x79\x30\x8f\x96\x48\x24\xe6\xcb\x07\x1a\x2e\xd2\x6d\x8a\xd2\x92\xf2\x69\x39\x99\x09\x99\x13\x3d\x86\x94\x68\x8c\x35\xcd\x71\x60\x37\x52\x4e\xb1\x73\x27\xd7\x95\x49\xd8\x54\x48\xaa\x67\x79\xc5\x6c\x4e\xb4\x46\x69\xb6\xfa\x5f\xf7\x24\xfe\xf5\xc1\xfc\x77\x19\xbf\x1a\x7d\x8a\x1f\xce\x9e\x95\x72\x0a\x9e\xd1\xe9\x18\xbe\xc0\xd7\x1d\xb6\x31\xd4\x5f\x21\x16\x91\x92\xac\x1c\x35\xaa\x31\x2f\x05\xea\x56\x6d\xe4\x09\x75\x2e\x6f\x07\xd3\x23\x6c\x81\x5d\xba\xa8\x1b\x56\x8b\xce\x2d\xf6\x18\xbe\x7c\xed\xb2\xa8\x40\x75\xcb\xfb\xcb\xf8\x55\xa0\x30\x45\xa7\x9c\xf2\x69\x93\x7e\x34\x11\x82\x21\xe1\x1e\x2c\xd8\xbf\x4e\x6d\x58\x98\xed\xde\x33\x30\xbb\x14\xf8\x41\x4d\x6d\x6e\x5d\x8e\x48\x4e\x9e\xfe\x86\x7c\xaa\x67\x63\xb8\x7e\xf9\x72\xd0\x6a\x03\xb1\x33\x82\x87\xe7\xc3\xfb\x8b\x87\xc6\xd4\xe9\x73\x3f\xf7\xe5\xfa\xfc\xeb\x70\x54\xfb\xfc\xa9\x05\xe5\x93\xc1\x39\x35\xba\x19\x00\xd0\x14\xb9\xa6\x7a\x75\xab\xb5\xa4\x93\x85\xc6\xbf\xe2\xca\x89\x9a\x53\x5e\xca\xd5\x26\x95\x61\x3e\xbc\x8f\x3f\x9d\x79\x41\xfc\xe4\xe9\x8d\x23\x5d\xf3\x65\x47\xf3\x04\x34\xf9\x05\x39\x64\x52\xe4\xa0\xec\x07\x13\x57\x81\xf0\x14\x48\xfa\x79\xa1\x34\xa6\xa0\x05\x10\xc6\xc4\x23\x10\x0e\x62\xee\xf4\x0b\x0c\x49\x4a\xf9\x14\xa2\x65\x74\x0e\x39\xf9\x6c\x82\x37\x67\xab\x73\x8b\x6a\xc7\x17\x39\xe5\xc5\xac\xe7\x35\xa3\x0a\x72\x24\x5c\x81\x9e\x21\x64\xc2\x50\x35\x44\x9c\xfa\x15\x10\x89\x86\x95\xb1\x2c\x9a\xd6\xe5\x55\x5e\xe0\xab\x8b\xeb\x8b\x17\xe1\xef\x38\x13\xe2\x6c\x42\x64\x31\xb7\x0c\x01\x96\x6d\x10\x57\x17\xd7\xfe\x57\x09\x16\xc0\x97\x3f\x6b\x68\xa1\xb2\x97\x0f\x37\xc3\xcb\xdf\xee\xaf\xe2\x57\x0f\x1f\xd3\xe7\xa7\xc3\x9b\xf1\xc7\x8b\x70\xe2\xf4\xa6\x7d\x2a\x1e\x0e\x6f\xc6\xd5\xe4\x6f\x1f\x53\xbb\x47\xb7\xf1\x3f\xe3\x07\xe3\x1f\xfe\xb7\x27\xb9\x23\xf0\xa9\xe7\x78\x36\x0c\x3f\x9c\x59\x22\xb5\x19\x0b\x59\xf8\x60\xc3\xf2\xdb\x4c\x6f\x5b\xe0\x5c\x19\x3f\x52\x26\xea\xb5\x3a\x66\x9b\x29\x47\xf0\xd5\x99\xe2\x5c\x28\xaa\x85\x5c\xbd\x16\x5c\xe3\x93\xee\x13\xc4\x0c\x54\x57\xd0\xb2\x14\x9a\x41\x25\x58\xa3\x48\xe8\xbb\x76\xde\x84\xb1\xbb\xac\xe2\xd2\x71\x1a\x36\x50\xab\x58\xda\x94\xb3\x90\x75\x42\x14\xfe\x5d\xb2\xa8\x8a\x89\x6b\x22\x9b\x7f\x05\x58\x38\xd5\x1a\x9b\xfc\x69\x13\xc4\xb1\x1f\xc9\x7c\x5e\x8b\xa4\x1b\x51\x01\x90\x2f\xf2\x31\xdc\x47\x0b\xc9\x7e\x26\x7a\x16\x9d\x43\xa4\x66\xe4\xfa\xe5\x9f\xe2\x94\x4e\x51\xe9\xe8\x61\xd0\xa0\xd3\x97\xb2\xd5\xf1\x94\x2a\x2d\x57\x86\xfa\xdd\xeb\xb7\xe5\xf0\xc1\xec\x01\x49\x12\x54\x6a\xc7\xfc\xca\x68\xc6\x42\x99\x03\xbe\x40\x45\x05\x43\x33\xc2\x27\x8d\xdc\x1c\x39\xea\x74\x8b\xb1\x0c\x00\xa6\x54\xcf\x16\x93\xdb\xcd\xbc\x37\x5a\x9b\x1d\x1a\x13\x08\x36\xd4\xce\x64\x7b\x59\x63\x53\x6d\x4e\xc0\x52\xfd\x05\xa3\x2d\xe8\xc6\x4a\x37\x43\x24\x22\xcf\xa9\xde\xe4\x13\x5c\x70\x3c\x44\x2f\x07\xae\xfb\x27\xc1\xd1\x19\x86\x12\x0b\x99\xe0\x9b\xd2\xe1\x7a\x88\x63\x92\x95\x72\x50\x24\x22\xe5\xd8\x50\x28\x07\xce\x84\x7a\xe4\x3c\x6b\x82\xef\x1e\xec\x0a\x14\x7c\xd2\x92\xbc\x2d\x00\xb6\x64\x33\x6b\x74\x8e\x90\xaf\xef\xb0\x1d\x7b\xa4\xf4\xa1\x1b\xdb\x31\x5f\xdd\x65\xf5\xf0\xd7\x4a\xc5\xe1\x45\xdb\x01\x43\x8f\xdd\x01\xdc\xd4\x88\x1e\x78\x00\xe0\xa2\xd9\xfb\x39\x26\x3d\xcc\x68\x46\xd4\xec\xd6\xd7\x00\x95\x71\x99\xd2\x82\x51\x65\x4b\x91\xf5\xcf\x36\x0f\xde\x21\xed\x6f\x33\xb8\x1a\xc3\x8d\xd9\x76\xbb\x10\x3b\x24\xe8\xed\x10\x03\x00\x53\x24\x29\x4d\xf2\x79\x53\x49\x4e\x47\x1d\x12\x6f\x22\x5a\x4c\xed\x59\xac\x99\x9a\x80\xe8\x85\xc4\x9e\x9b\x46\x36\xec\x88\x19\xe5\x98\x52\xf2\xc1\xc7\x80\xfe\x7b\xd4\x52\x14\xf6\x54\xb6\x9b\x2a\xe5\xa8\xa0\xea\x67\xdd\x87\x19\x3a\x20\x77\xe0\x89\xcc\xa6\xc9\xa5\x5a\x20\x28\xd7\xda\xf6\xb3\x04\xdc\x37\x6e\x3a\x97\x29\x87\x25\xbd\x3d\xf5\xb6\xb5\x80\x74\xfc\xb6\x04\x9d\xca\x8f\xc3\xda\x31\x58\x67\x27\x66\xcd\x9e\xa2\xc0\x38\xad\xd1\x6f\x41\xae\x39\x87\x3b\x26\xd1\x54\x43\x6f\xf6\x09\x2b\x5d\x5a\xde\xa7\xe8\x6e\x1e\x03\x2d\x30\x47\x39\x6f\x7a\x6f\x4e\xa9\xa2\xb2\xd9\xe6\x74\xd5\x27\xab\xd8\x78\x8c\x37\xcc\x53\x62\x91\x2f\x38\x2e\x47\x31\xd2\xef\xd2\x3e\xdb\xd3\xec\x1b\xeb\x5d\x3b\xd0\x83\x16\x12\x34\xdb\x48\x9d\x6c\x9a\x46\x6d\xb7\x51\xc9\xe4\x9d\xcf\x2b\xb7\x26\xe8\xc4\xe4\xa0\x28\x91\x27\x68\x3b\x05\x30\xac\x3a\xbb\x4c\x24\x84\x9d\x16\x79\x5d\x57\xb2\xe8\x2d\xf0\x3d\x32\x4c\xb4\xd8\xd6\x22\xec\x34\xd8\x5e\x29\x8c\xad\x41\x0b\xb1\xf7\x5d\x68\xb9\xce\x5d\xdb\x6d\xad\x86\x74\x78\x07\xb8\xa5\xad\xd5\xcf\x96\x37\x65\xbd\x70\x02\x24\xd1\x0b\xc2\xd8\x6a\x5c\x71\x8a\xed\x01\xf5\x38\x02\x35\xc7\x84\x12\x66\x8c\x53\x4b\x9a\x58\x26\xff\xbf\x89\xf2\x1e\x59\x70\xd3\x99\x05\xc7\x66\x16\x5c\x28\x94\x2f\x18\xdb\x21\x8d\x6d\x84\x54\xef\xf5\x55\xde\xd0\xb3\x64\xf6\x04\x54\xdf\xfb\x08\x38\xb1\xf8\xd6\x87\x2b\x2a\xe7\x45\x17\x6f\xa1\x34\xe4\x44\x27\xb3\xc0\x0d\xd4\x5a\xc8\x5e\xaf\x9e\x99\xcd\x5f\x83\xa9\xb0\x1c\xf8\xa3\x20\x2b\x57\xe5\x62\xf0\x91\xa2\xbc\x23\x56\x1d\x24\x6e\x13\x76\xae\xd0\xad\x09\x44\xe7\x10\xe1\x93\x46\xc9\x09\x2b\x9b\x14\xff\x8b\x65\xa3\x48\xe8\x9f\x99\xd8\xbd\x6e\xb4\xab\xfb\x0b\x65\xa8\x56\x4a\x63\xde\x1f\xf7\xae\x8d\xe1\xb7\x8e\x0b\x22\xa1\x6f\x73\x32\x3d\xa8\x71\x63\x87\xd4\x50\x79\xe7\x4f\xb6\xa3\x74\x74\xc2\x06\xa0\xb7\x94\x3a\x9b\x2d\x2d\xda\x4a\x9d\x07\x2c\x8c\x91\x95\xf7\xb8\xc3\xd6\x03\x51\x21\x52\x04\x55\x73\x2e\xeb\x2a\xe2\x6e\xcd\x02\xea\xa9\x82\xa9\xe2\x72\xc2\x69\x86\x4a\x37\xcb\xb7\x06\xd3\x3d\x6b\x44\xa7\x19\x17\x9a\x9d\xa3\x38\x09\x14\x68\xb1\x85\x63\xd3\x50\xd7\xd9\x39\x08\xcf\x4a\x13\x39\x45\x8d\x29\x24\x82\xeb\x32\xf9\xe9\x24\xaf\xe8\xaf\x1b\xd7\x62\xbe\x03\xe5\x30\x59\x69\x54\x9e\xc7\xc4\x28\xbb\x49\x97\x2f\xf2\x89\xd9\xd0\x01\x40\xa7\xcb\x1e\x60\x2e\x19\x65\x58\x9d\x84\x87\x5a\x4c\x8b\x84\x95\xf5\x78\x56\x5d\x7a\xf1\xdf\x43\x75\x80\x9e\x11\x0d\x54\xd9\xb5\x1b\xf5\x53\x6e\xbf\xfd\x60\x3e\xaa\x1f\x20\xa5\xd2\x66\xcf\xab\xce\xfd\xf0\x7a\xbb\x3b\x92\x7f\x7d\x03\x85\xdd\x35\xfd\x6c\xb3\x71\xd6\x0d\xd3\xfa\x3b\x3c\x52\x3d\x2b\x54\x93\x2c\xa4\x44\xae\xab\x04\x05\xaa\x97\x26\x9b\xb4\xe4\x43\xeb\xbb\x22\xe7\xe9\xa3\xa3\x4d\x6f\x3b\xda\x94\xf8\x47\xf6\xb3\xfd\x2c\xb1\x9b\x71\xcc\x94\xa3\x2b\x6d\x08\x0e\xd4\xef\x73\x8c\x0f\x00\xaa\xae\xf5\x01\xae\xb8\xf0\x17\x52\x07\x1e\xdc\x46\x98\x52\xd1\x8b\x0d\x97\x4f\x03\x80\x29\x72\x94\x34\xf9\x2f\x5e\x1c\x15\x12\xb8\xbb\xa3\x62\xf0\xbd\x7d\xf6\x38\x5d\xd1\xdf\x99\x4f\x57\x1b\xe7\xe6\xbf\x95\x4b\xd7\x4c\xf4\x7b\x25\xe6\xf5\x97\x6e\x7d\x2d\xf0\x9b\xd8\x53\xdf\xce\x98\xda\xd4\xdd\xae\x1f\xc1\xb6\xff\x93\xd1\xc4\x16\x94\xfe\x24\x2e\x32\x43\x33\x0c\xba\x64\xde\xbc\xf4\xbe\x2b\x2d\x3a\x10\x47\x2a\x89\x1b\x77\xcd\xc1\x85\xba\x4b\xdc\x8f\xc4\x47\xd6\x2b\xab\xaa\xa1\xd3\x9f\xfe\x5a\xa5\xbc\xe1\x9d\x4a\xd5\x34\x8a\x76\x41\x68\xa6\x3c\x3b\x21\x35\x42\x6e\x34\x18\x34\xcc\x25\xb4\x74\x13\x37\xe7\xf4\x1f\x55\x6c\x8d\x21\xfa\x85\xf2\xb4\xf8\x19\x3e\x9a\x8d\x9d\x59\x45\x83\xba\x09\x54\xe8\x35\xdb\x0c\x4d\x3d\x28\xd8\xf2\x8b\xc6\xbb\xe3\xf2\x59\xf1\xb9\xfb\xac\x44\xa6\x1f\x89\xc4\xea\x83\xcd\x3a\x8d\x4c\x9d\xf4\x13\xc1\x95\x1e\x43\x54\xde\x70\x04\xeb\xf1\x2b\x70\xc8\x1d\x2f\x15\xdd\x02\xd7\x9e\x11\xed\xf6\x16\xb4\xb1\xff\xdd\x5b\xb9\xf6\xc2\x29\x82\x13\x9f\x0d\xb3\xd5\x39\x3c\x22\x08\xce\x56\xc5\xab\x3e\x5b\x34\x0a\x8e\x35\xc7\x6f\xf7\x99\xe2\x36\xa2\xbc\x58\x3b\xe0\x0d\x6b\x49\x23\x6a\x5c\xad\x1d\x40\xb3\xfd\xfa\x29\xfa\x4f\x00\x00\x00\xff\xff\x45\xa5\x68\x74\x8f\x2e\x00\x00") func ResourcesComponentDescriptorOcmV3SchemaYamlBytes() ([]byte, error) { return bindataRead( diff --git a/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go b/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go index 8a9f20c216..30332e1c72 100644 --- a/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go +++ b/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go @@ -78,7 +78,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _ResourcesComponentDescriptorV2SchemaYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\xdd\x6f\x1b\xb9\x11\x7f\xd7\x5f\x31\x38\x07\xa0\x1c\x7b\x2d\x47\x45\x0a\x44\x2f\x86\x9b\x43\x81\xa0\xbd\xf3\x21\x49\xfb\x50\xc7\x0d\xa8\xdd\x91\xc4\x94\x4b\xaa\x24\xa5\x58\x97\xf3\xff\x7e\x20\xb9\xdc\xef\x5d\x7d\xd9\xb9\x0b\x10\x3f\x24\x22\x77\x38\x33\x1c\xfe\xe6\x83\xb3\xfb\x8c\x25\x13\x20\x0b\x63\x96\x7a\x32\x1a\xcd\xa9\x4a\x50\xa0\xba\x88\xb9\x5c\x25\x23\x1d\x2f\x30\xa5\x7a\x14\xcb\x74\x29\x05\x0a\x13\x25\xa8\x63\xc5\x96\x46\xaa\x68\x3d\x26\x83\x67\x9e\xa2\xc4\xe1\x93\x96\x22\xf2\xb3\x17\x52\xcd\x47\x89\xa2\x33\x33\x1a\x5f\x8e\x2f\xa3\x17\xe3\x8c\x21\x19\x04\x36\x4c\x8a\x09\x90\x9b\x25\x0a\x78\x1d\x64\xc0\x4f\x32\x41\x0e\xeb\x31\x14\xd4\x33\x26\x98\x25\xd6\x93\x01\x40\x8a\x86\xda\xff\x01\xcc\x66\x89\x13\x20\x72\xfa\x09\x63\x43\xdc\x54\x95\x73\xae\x38\x14\x8a\xbb\xf5\x09\x35\xd4\x2f\x50\xf8\xff\x15\x53\x98\x78\x8e\x00\x11\x10\x2f\xf7\xdf\xa8\x34\x93\xc2\x53\x2d\x95\x5c\xa2\x32\x0c\x75\xa0\xab\x10\x85\xc9\x5c\x25\x6d\x14\x13\x73\x32\x70\xea\xaa\x39\x76\xea\xdb\x64\x4c\xf9\x5c\x2a\x66\x16\x69\xc1\x74\x49\x8d\x41\x65\x37\xf4\xdf\x5b\x1a\xfd\x7a\x67\xff\xb9\x8c\x5e\x8d\x3e\x46\x77\x67\xcf\x48\x46\x16\x4b\x31\x63\xf3\x09\x7c\x81\x07\x37\x43\x93\xc4\xd9\x8c\xf2\x5f\x0a\x19\x30\xa3\x5c\xe3\x00\x80\xd3\x29\xf2\x4e\xad\x5a\x8c\x22\x68\x8a\xa4\x18\xae\x29\x5f\x61\xd7\x16\x2c\x6d\xa7\x49\xfc\xa4\x5b\x3f\x81\x2f\x0f\x61\x5c\x37\x64\x69\xcf\xeb\xdb\xcb\xe8\x55\x69\xa7\x9a\xcd\x05\x13\xf3\x86\x84\xa9\x94\x1c\xa9\x08\x64\x25\xc3\xdb\xbf\x67\x0a\x67\x13\x20\x27\xa3\x12\x9c\x46\x8e\xc6\x1d\x53\x0e\x95\x9f\x73\xe5\x5b\x14\x4f\xe9\xfd\x3f\x51\xcc\xcd\x62\x02\xe3\x97\x2f\x07\xad\x87\x13\xf9\xd3\xb9\x7b\x3e\xbc\xbd\xb8\xab\x4d\x9d\x3e\x0f\x73\x5f\xc6\xe7\x0f\xc3\x51\xe5\xf1\xc7\x96\x25\x1f\xed\x9a\x53\xbb\xf7\x01\x00\x4b\x50\x18\x66\x36\xd7\xc6\x28\x36\x5d\x19\xfc\x07\x6e\xbc\xaa\x29\x13\xb9\x5e\x6d\x5a\x59\xe1\xc3\xdb\xe8\xe3\x59\x50\x24\x4c\x9e\x5e\x79\xd6\x0a\x39\xbd\xc7\xe4\x1d\xa6\x6b\x54\x9e\xe7\x09\x18\xfa\x3f\x14\x30\x53\x32\x05\xed\x1e\x58\x97\x06\x2a\x12\xa0\xc9\xa7\x95\x36\x98\x80\x91\x40\x39\x97\x9f\x81\x0a\x90\x4b\x8f\x37\xe0\x48\x13\x26\xe6\x40\xd6\xe4\x1c\x52\xfa\x49\xaa\x48\x0a\xbe\x39\x77\x4b\xdd\xf8\x22\x65\x22\x9b\x0d\xb2\x16\x4c\x43\x8a\x54\x68\x30\x0b\x84\x99\xb4\x5c\x2d\x13\x6f\x7e\x0d\x54\xa1\x15\x65\x91\xc3\x92\xaa\xbe\x3a\x28\xfc\xe2\x62\x7c\xf1\x97\xf2\xef\x68\x26\xe5\xd9\x94\xaa\x6c\x6e\x5d\x26\x58\xb7\x51\xbc\xb8\x18\x87\x5f\x39\x59\x89\x3e\xff\x59\x59\x56\x36\xf6\xfa\xee\x6a\x78\xf9\xdb\xed\x8b\xe8\xd5\xdd\x87\xe4\xf9\xe9\xf0\x6a\xf2\xe1\xa2\x3c\x71\x7a\xd5\x3e\x15\x0d\x87\x57\x93\x62\xf2\xb7\x0f\x89\x3b\xa3\xeb\xe8\x3f\xd1\x9d\xc5\x7f\xf8\x1d\x58\xee\x48\x7c\x1a\x24\x9e\x0d\xcb\x0f\xce\x1c\x93\xca\x8c\xa3\xcc\x7c\xac\x19\xc5\x1a\xd0\xdb\x16\xd1\x36\xd6\x8f\xb4\x0d\x47\xad\x8e\xd7\x06\x65\x02\x0f\x1e\x8a\x4b\xa9\x99\x91\x6a\xf3\x5a\x0a\x83\xf7\x66\x9f\x30\x65\xa9\xba\xc2\x92\xe3\xd0\x13\xa9\x65\xcc\xde\xb6\xcb\xa6\x9c\xdf\xcc\x0a\x29\xad\x3b\x6a\xa8\x5d\x44\xcb\xba\x9e\x99\xae\x53\xaa\xf1\x5f\x8a\x93\x22\xe6\x35\x54\xb6\x7f\x19\x59\x79\xaa\x23\xa8\xfa\x34\x50\x8a\x63\x3f\xd1\xe5\xb2\x12\x29\x7b\x97\x02\xa0\x58\xa5\x13\xb8\x25\x2b\xc5\x7f\xa1\x66\x41\xce\x81\xe8\x05\x1d\xbf\xfc\x6b\x94\xb0\x39\x6a\x43\xee\x06\x35\x3e\xfb\x72\x76\x36\x9e\x33\x6d\xd4\xc6\x72\xbf\x79\xfd\x26\x1f\xde\xd9\x33\xa0\x71\x8c\x5a\xef\x98\xde\xad\x65\x1c\x15\xcc\xa4\xca\x96\xa2\x86\xa1\x1d\xe1\xbd\x41\x61\x53\x8a\x3e\xdd\x02\x96\x01\xc0\x9c\x99\xc5\x6a\x7a\xdd\x2f\xbb\x17\x6d\x6e\x68\x21\x50\x3a\x50\x37\x33\x3b\x08\x8d\x75\xb3\x79\x05\x73\xf3\x67\x82\xb6\x2c\xb7\x28\xed\xa7\x88\x65\x9a\x32\xd3\xe7\x13\x42\x0a\x3c\xc6\x2e\x47\xee\xfb\x67\x29\xd0\x03\x43\xcb\x95\x8a\xf1\xc7\xdc\xe1\xf6\x50\xc7\x96\x23\xf9\x20\x2b\x34\xf2\xb1\xe5\x90\x0f\x3c\x84\x0e\xaf\x6a\x3a\xaa\x8c\xd6\x60\x97\x2d\xc1\x7b\xa3\xe8\x9b\x8c\x60\x4b\xb5\xd2\xe0\x43\xba\xaa\xa7\x8e\x08\x55\xca\x99\x64\xf7\xe3\x70\xb5\xa2\x6e\x10\x51\xa5\xe8\xa6\xd8\x39\x33\x98\x56\xe2\x56\xab\x0e\x8e\x57\x58\x54\x76\x76\x37\x16\x9b\x9b\x59\x35\x48\xb6\x32\xf1\xeb\xc8\x76\xc2\xb2\x5f\xef\x40\x6e\x2f\x31\x81\x78\x00\xe0\x63\xde\xbb\x25\xc6\x7b\x80\x6d\x41\xf5\xe2\x3a\x94\xf0\x05\x04\xa5\x4a\x29\x67\x9a\x5a\x41\xcd\xc7\xae\x1a\xee\x80\x5d\x85\x61\xfd\x10\xfc\x41\x05\x80\xb6\x0a\xe9\x5d\xe2\xcb\xf0\x76\x8a\x81\xaf\xb4\xa9\x59\x29\xdc\xd3\x08\xb4\x67\x87\x76\x94\x62\xc2\xe8\xfb\xe0\x79\x3b\xdd\x81\xf6\x54\xde\x4f\xe5\x72\x0a\xaa\x6a\x06\x79\xbf\x40\x4f\xe4\xd3\x88\x9c\xb9\xe2\x33\xdf\x36\x94\xae\x39\xbd\xf6\x39\x34\x1a\xe5\x0c\xf6\x88\x39\x95\x1d\x7a\x8c\x6e\x71\xfc\x02\xc8\xe5\x2b\x54\x49\xf1\xce\x95\x15\x00\xf8\x84\x80\xb6\xee\xff\xf1\x10\xd7\xc8\x77\x7e\xc0\x3e\x1b\x11\xae\x85\xe6\x51\x42\xe9\xde\x06\xcd\x6d\x92\x77\x30\xbc\x71\xf6\x49\x98\x9d\x19\x6a\x9b\xa5\x5a\xb5\xab\x94\x80\x8f\x90\x22\x0e\xc4\x98\xc2\x2c\x67\x97\xcd\x01\x47\xe6\x8f\x3a\xfc\x9c\xfd\xb5\x8a\xdf\x86\x5a\x67\x6b\xd1\x48\x6d\x5d\x84\x0a\x45\x8c\xee\xf6\x0a\xc3\xa2\xbd\xc5\x65\x4c\xf9\x69\x56\x6b\x74\x15\x30\x01\x3a\xef\x90\x63\x6c\xa4\x3a\x14\x69\x4f\x90\x56\xcb\x7d\x8c\xb7\x61\x97\x87\xda\x25\xe7\xb4\x6b\x4f\xa8\x15\x77\x11\x90\x75\x7f\x27\xad\xa5\xf3\xb2\x1f\xb4\xfb\x0a\x33\x38\x01\x1a\x9b\x15\xe5\x7c\x33\x29\x24\x45\x2e\xda\x7f\x1e\x81\x5e\x62\xcc\x28\xb7\x58\x35\x8a\xc5\x4e\xc8\xb7\x5b\xcb\x3d\x59\xa1\x56\x8f\x00\x52\x60\xbd\x50\xcb\x64\x89\x15\xe7\x3b\x54\x5a\xb5\x00\x1a\x42\x45\x91\xaa\xf7\xbc\xfb\x05\x06\x7a\xe7\xfe\x65\x86\x49\x38\x71\xeb\x9d\xe3\x17\x5c\xce\xb3\x76\xd4\x4a\x1b\x48\xa9\x89\x17\x25\x67\xd0\x8d\x2b\x44\xf3\x1a\xc8\x5d\x09\x56\x9a\x2a\x57\xac\xdf\x6f\x16\xf9\xae\x7c\xe0\x7e\x24\xc4\x7a\x66\x45\xf6\xf1\x87\xb0\xf3\x55\xd3\x41\x80\x9c\x03\xc1\x7b\x83\x4a\x50\x9e\xdf\xb6\xbf\xdd\xfb\x8f\x8c\xd9\xdf\xb8\xdc\xfd\x02\xe4\x6c\xf0\x77\xc6\x51\x6f\xb4\xc1\x74\xff\xb5\x37\x6d\x02\x9f\x3a\x7a\xc8\x98\xbd\x49\xe9\xfc\xa8\x3e\x85\x1b\x32\xcb\x25\xcf\x9b\x8f\xd2\xc0\x28\xf7\xbb\x02\x9e\xaa\x62\xb6\x74\x24\x0b\x73\x1e\xb1\x31\x4e\x37\xc1\x2f\x8f\xdb\x0f\x90\x4c\x25\x02\x45\x2f\x6a\xd6\x75\xbb\xba\xb6\x1b\xa8\x96\x15\xf6\x7a\x95\x52\xc1\x66\xa8\x4d\xfd\x5e\x55\x13\x7a\xe0\xe5\xcd\x5b\xc6\x07\x70\xef\x28\x5e\x03\x0d\x46\x6e\x91\x58\x07\x6a\x53\x9c\xa7\x08\xa2\x0c\x55\x73\x34\x98\x40\x2c\x85\xc9\x0b\xa5\x4e\xf6\x9a\xfd\xda\xbb\x17\xfb\x1c\x98\x80\xe9\xc6\xa0\x0e\x32\xa6\xd6\xd8\x75\xbe\x62\x95\x4e\xed\x81\x0e\x00\x3a\x5d\xf6\x08\xb8\xcc\x18\xc7\x22\x5f\x1e\x8b\x98\x16\x0d\x0b\xf4\x04\x51\x5d\x76\x09\xcf\xcb\xe6\x00\xb3\xa0\x06\x98\x76\x7b\xb7\xe6\x67\xc2\x3d\xfb\xc1\x3e\xd4\x3f\x40\xc2\x94\x2b\xcc\x37\x9d\xe7\x11\xec\x76\xf3\x48\xfe\xf5\x04\x06\xbb\xa9\xfb\x59\x3f\x38\xab\xc0\x74\xfe\x0e\x9f\x99\x59\x64\xa6\x89\x57\x4a\xa1\x30\xd0\xf6\x42\xbc\xcf\x4a\x21\xb4\xbe\xcd\x2a\xa3\x7d\x6c\xd4\x51\x71\x75\x1a\xf1\x7b\x8d\xb4\x3d\x97\xb8\xc3\xf8\xfa\x85\x49\x57\x71\x51\x4a\xbb\x5f\x27\xd9\x0f\x00\x8a\x26\xed\x11\x0e\xbb\x0a\x6f\x69\x8e\x4c\xef\x56\x99\xfc\x38\x56\x3d\x6f\x64\x06\x00\x73\x14\xa8\x58\xfc\x07\xbe\x4d\xc9\x34\xf0\x2f\x54\xb2\xc1\x77\xcf\xfe\x13\x78\x76\x71\x30\x7e\xfe\x8f\x75\xec\x0a\x50\xbf\x56\x11\x9f\x67\xa6\x9d\xdb\x55\x7b\xf7\xa7\x9a\x38\x6d\xbc\xb3\xd7\xa5\x87\x4b\x25\xd7\x2c\x29\x4e\x34\x02\x52\x69\x32\x54\x7b\x5e\x79\x3d\xaf\x2b\xfc\x2b\x2b\xfe\x14\xdd\xdc\x58\xa1\xbb\x18\xbf\x67\x4d\xb7\xbb\x0d\x08\x3d\xcf\x8e\xb1\x78\xdf\x3f\x93\x2a\xa5\x66\x02\x09\x35\x18\x19\x96\xf7\xab\x9b\x26\xdc\x1b\xb6\x8d\x6b\x6f\xdf\x7d\xb6\xf1\x85\x06\x81\x93\x50\xde\xf0\xcd\x39\x7c\x46\x90\x82\x6f\xb2\xaf\x92\xdc\x2d\x40\x8a\xa0\x6c\x38\xd2\x2d\x8e\xf9\x64\xee\x97\xa1\xe1\x91\xfa\x1d\xb5\x37\xe2\xf9\x01\x37\x21\xf9\x38\x02\x9b\x8c\xeb\xad\xfe\xa7\x3c\xfb\x72\x8f\x90\xec\x08\x96\x4a\xed\xba\xd3\xa2\x5a\x56\x74\xa1\xa9\xdd\xa4\xf0\xe5\x61\x30\x18\xd4\xe2\x54\x39\x08\x45\x40\x52\xf4\x9f\x99\x96\x03\x05\x19\x54\xc3\x40\xf1\x39\x6b\xc7\x17\x8a\x9e\x45\x2d\x3e\xf6\x1f\x10\x29\xbf\x9b\xac\xd6\x1a\xa5\x03\xa9\x1c\x46\xff\xeb\x3f\x52\x7b\xf3\x77\x04\xcf\xf6\x97\x65\xe4\xf7\x00\x00\x00\xff\xff\xa3\xb7\xcb\x40\x89\x2c\x00\x00") +var _ResourcesComponentDescriptorV2SchemaYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\x5f\x6f\x1b\xb9\x11\x7f\xd7\xa7\x18\x9c\x03\x50\x8e\xbd\x96\xa3\x22\x05\xa2\x17\xc3\xcd\xa1\x40\xd0\xde\xf9\x90\xa4\x7d\xa8\xe3\x06\xd4\xee\x48\x62\xba\x4b\xaa\x24\xa5\x58\x97\xf3\x77\x3f\x90\x5c\xee\x72\xff\xea\x9f\x9d\xbb\x00\xf1\x43\x22\x72\x87\x33\xc3\x99\xdf\x0c\x87\xb3\xfb\x8c\x25\x13\x20\x0b\xad\x97\x6a\x32\x1a\xcd\xa9\x4c\x90\xa3\xbc\x88\x53\xb1\x4a\x46\x2a\x5e\x60\x46\xd5\x28\x16\xd9\x52\x70\xe4\x3a\x4a\x50\xc5\x92\x2d\xb5\x90\xd1\x7a\x4c\x06\xcf\x1c\x45\xc0\xe1\x93\x12\x3c\x72\xb3\x17\x42\xce\x47\x89\xa4\x33\x3d\x1a\x5f\x8e\x2f\xa3\x17\xe3\x9c\x21\x19\x78\x36\x4c\xf0\x09\x90\x9b\x25\x72\x78\xed\x65\xc0\x4f\x22\xc1\x14\xd6\x63\x28\xa9\x67\x8c\x33\x43\xac\x26\x03\x80\x0c\x35\x35\xff\x03\xe8\xcd\x12\x27\x40\xc4\xf4\x13\xc6\x9a\xd8\xa9\x2a\xe7\x42\x71\x28\x15\xb7\xeb\x13\xaa\xa9\x5b\x20\xf1\xff\x2b\x26\x31\x71\x1c\x01\x22\x20\x4e\xee\xbf\x51\x2a\x26\xb8\xa3\x5a\x4a\xb1\x44\xa9\x19\x2a\x4f\x57\x21\xf2\x93\x85\x4a\x4a\x4b\xc6\xe7\x64\x60\xd5\x95\x73\xec\xd4\xb7\xc9\x98\xa6\x73\x21\x99\x5e\x64\x25\xd3\x25\xd5\x1a\xa5\xd9\xd0\x7f\x6f\x69\xf4\xeb\x9d\xf9\xe7\x32\x7a\x35\xfa\x18\xdd\x9d\x3d\x23\x39\x59\x2c\xf8\x8c\xcd\x27\xf0\x05\x1e\xec\x0c\x4d\x12\x6b\x33\x9a\xfe\x52\xca\x80\x19\x4d\x15\x0e\x00\x52\x3a\xc5\xb4\x53\xab\x16\xa3\x70\x9a\x21\x29\x87\x6b\x9a\xae\xb0\x6b\x0b\x86\xb6\xd3\x24\x6e\xd2\xae\x9f\xc0\x97\x07\x3f\xae\x1b\x32\xd8\xf3\xfa\xf6\x32\x7a\x15\xec\x54\xb1\x39\x67\x7c\xde\x90\x30\x15\x22\x45\xca\x3d\x59\x60\x78\xf3\xf7\x4c\xe2\x6c\x02\xe4\x64\x14\xc0\x69\x64\x69\xac\x9b\x0a\xa8\xfc\x5c\x28\xdf\xa2\x78\x46\xef\xff\x89\x7c\xae\x17\x13\x18\xbf\x7c\x39\x68\x75\x4e\xe4\xbc\x73\xf7\x7c\x78\x7b\x71\x57\x9b\x3a\x7d\xee\xe7\xbe\x8c\xcf\x1f\x86\xa3\xca\xe3\x8f\x2d\x4b\x3e\x9a\x35\xa7\x66\xef\x03\x00\x96\x20\xd7\x4c\x6f\xae\xb5\x96\x6c\xba\xd2\xf8\x0f\xdc\x38\x55\x33\xc6\x0b\xbd\xda\xb4\x32\xc2\x87\xb7\xd1\xc7\x33\xaf\x88\x9f\x3c\xbd\x72\xac\x25\xa6\xf4\x1e\x93\x77\x98\xad\x51\x3a\x9e\x27\xa0\xe9\xff\x90\xc3\x4c\x8a\x0c\x94\x7d\x60\x42\x1a\x28\x4f\x80\x26\x9f\x56\x4a\x63\x02\x5a\x00\x4d\x53\xf1\x19\x28\x07\xb1\x74\x78\x83\x14\x69\xc2\xf8\x1c\xc8\x9a\x9c\x43\x46\x3f\x09\x19\x09\x9e\x6e\xce\xed\x52\x3b\xbe\xc8\x18\xcf\x67\xbd\xac\x05\x53\x90\x21\xe5\x0a\xf4\x02\x61\x26\x0c\x57\xc3\xc4\x99\x5f\x01\x95\x68\x44\x19\xe4\xb0\xa4\xaa\xaf\xf2\x0a\xbf\xb8\x18\x5f\xfc\x25\xfc\x1d\xcd\x84\x38\x9b\x52\x99\xcf\xad\x43\x82\x75\x1b\xc5\x8b\x8b\xb1\xff\x55\x90\x05\xf4\xc5\xcf\xca\xb2\xd0\xd8\xeb\xbb\xab\xe1\xe5\x6f\xb7\x2f\xa2\x57\x77\x1f\x92\xe7\xa7\xc3\xab\xc9\x87\x8b\x70\xe2\xf4\xaa\x7d\x2a\x1a\x0e\xaf\x26\xe5\xe4\x6f\x1f\x12\xeb\xa3\xeb\xe8\x3f\xd1\x9d\xc1\xbf\xff\xed\x59\xee\x48\x7c\xea\x25\x9e\x0d\xc3\x07\x67\x96\x49\x65\xc6\x52\xe6\x31\xd6\xcc\x62\x0d\xe8\x6d\xcb\x68\x1b\x13\x47\xca\xa4\xa3\xd6\xc0\x6b\x83\x32\x81\x07\x07\xc5\xa5\x50\x4c\x0b\xb9\x79\x2d\xb8\xc6\x7b\xbd\x4f\x9a\x32\x54\x5d\x69\xc9\x72\xe8\xc9\xd4\x22\x66\x6f\xdb\x65\xd3\x34\xbd\x99\x95\x52\x5a\x77\xd4\x50\xbb\xcc\x96\x75\x3d\x73\x5d\xa7\x54\xe1\xbf\x64\x4a\xca\x9c\xd7\x50\xd9\xfc\xe5\x64\xe1\x54\x47\x52\x75\xc7\x40\x90\xc7\x7e\xa2\xcb\x65\x25\x53\xf6\x2e\x05\x40\xbe\xca\x26\x70\x4b\x56\x32\xfd\x85\xea\x05\x39\x07\xa2\x16\x74\xfc\xf2\xaf\x51\xc2\xe6\xa8\x34\xb9\x1b\xd4\xf8\xec\xcb\xd9\xda\x78\xce\x94\x96\x1b\xc3\xfd\xe6\xf5\x9b\x62\x78\x67\x7c\x40\xe3\x18\x95\xda\xf1\x78\x37\x96\xb1\x54\x30\x13\x32\x5f\x8a\x0a\x86\x66\x84\xf7\x1a\xb9\x39\x52\xd4\xe9\x16\xb0\x0c\x00\xe6\x4c\x2f\x56\xd3\xeb\x7e\xd9\xbd\x68\xb3\x43\x03\x81\xc0\xa1\x76\x66\x76\x10\x1a\xeb\x66\x73\x0a\x16\xe6\xcf\x05\x6d\x59\x6e\x50\xda\x4f\x11\x8b\x2c\x63\xba\x2f\x26\xb8\xe0\x78\x8c\x5d\x8e\xdc\xf7\xcf\x82\xa3\x03\x86\x12\x2b\x19\xe3\x8f\x45\xc0\xed\xa1\x8e\x29\x47\x8a\x41\x5e\x68\x14\x63\xc3\xa1\x18\x38\x08\x1d\x5e\xd5\x74\x54\x19\xad\xc9\x2e\x5f\x82\xf7\x5a\xd2\x37\x39\xc1\x96\x6a\xa5\xc1\x87\x74\x55\x4f\x1d\x19\x2a\x38\x33\xc9\xee\xee\xb0\xb5\xa2\x6a\x10\x51\x29\xe9\xa6\xdc\x39\xd3\x98\x55\xf2\x56\xab\x0e\x96\x97\x5f\x14\x06\xbb\x1d\xf3\xcd\xcd\xac\x9a\x24\x5b\x99\xb8\x75\x64\x3b\x61\x18\xd7\x3b\x90\x9b\x4b\x8c\x27\x1e\x00\xb8\x9c\xf7\x6e\x89\xf1\x1e\x60\x5b\x50\xb5\xb8\xf6\x25\x7c\x09\x41\x21\x33\x9a\x32\x45\x8d\xa0\xe6\x63\x5b\x0d\x77\xc0\xae\xc2\xb0\xee\x04\xe7\x28\x0f\xd0\x56\x21\xbd\x4b\x5c\x19\xde\x4e\x31\x00\xd0\x2c\x43\xa5\x69\xb6\xac\x1b\xc1\xd9\xa0\x43\xe3\x3e\xa6\xf9\x14\x6b\x86\x52\x85\x00\x4c\x46\xcf\xa8\x9e\x40\x42\x35\x46\x86\xde\x26\x01\x36\xe7\x54\xaf\x24\xee\xe9\x14\xda\x63\x71\x33\xca\x30\x61\xf4\xbd\xcf\x04\x3b\xdd\xc9\xf6\x34\xa6\x9b\x2a\xe4\x94\x54\xd5\x13\xed\xfd\x02\x1d\x91\x3b\xd6\xc4\xcc\x16\xc3\xc5\xb6\x21\xb8\x76\xb5\xf9\xab\x20\x3c\x34\x3b\x3a\xc8\x17\xc3\x82\x9f\xab\x82\x7a\x6f\x94\xbb\xa6\xcc\x8a\x41\x9c\xbc\x2d\x79\xab\x8c\xc3\xf0\x06\x18\xec\xb3\x73\x65\x05\x2f\x24\x00\x9f\x05\xf5\x96\xc5\x15\xf0\xbb\xc3\x10\xcd\x9d\xe7\xc7\x43\xd2\x42\x61\xe5\x03\x8c\xd4\xc8\xee\x2d\x34\x8f\x72\x8c\xec\xed\x8d\xc2\x26\x45\xf7\xc6\x19\x67\x9f\x62\xa1\xf3\x74\xde\x66\xa9\x56\xed\x2a\xe5\xef\x23\x1c\x8f\x07\x02\x54\x62\x5e\xaf\x84\xe6\x80\x23\xcf\xce\x3a\xfc\xac\xfd\x95\x8c\xdf\xfa\x3a\x6f\x6b\xc1\x4c\x4d\x4d\x88\x12\x79\x8c\xf6\xe6\x0e\xc3\xb2\xb5\x97\x8a\x98\xa6\xa7\x79\x9d\xd5\x55\xbc\x79\xe8\xbc\xc3\x14\x63\x2d\xe4\xa1\x48\x7b\x82\x92\x22\xec\xe1\xbc\xf5\xbb\x3c\xd4\x2e\x05\xa7\x5d\xfb\x61\xad\xb8\x8b\x80\xac\xfb\xbb\x88\x2d\x5d\xa7\xfd\xa0\xdd\x57\x94\xc2\x09\xd0\x58\xaf\x68\x9a\x6e\x26\xa5\xa4\xc8\x9e\x2c\x9f\x47\xa0\x96\x18\x33\x9a\x1a\xac\x6a\xc9\x62\x2b\xe4\xdb\xad\x63\x9f\xac\x48\xad\x67\x00\xc1\xb1\x5e\xa4\xe6\xb2\xf8\x2a\x4d\x77\xa8\x32\x6b\x09\xd4\xa7\x8a\xb2\x2c\xd8\xf3\xde\xeb\x19\xa8\x9d\x7b\xb7\x39\x26\xe1\xc4\xae\xb7\x81\x5f\x72\x39\xcf\x5b\x71\x2b\xa5\x21\xa3\x3a\x5e\x04\xc1\xa0\x1a\xd7\xa7\xe6\x15\x38\xb5\xe5\x67\x30\x15\x56\xeb\xdf\x6f\x55\xc5\xae\x5c\xe2\x7e\x24\xc4\x3a\x66\xe5\xe9\xe3\x9c\xb0\xf3\x35\xdb\x42\x80\x9c\x03\xc1\x7b\x8d\x92\xd3\xb4\xe8\x34\x7c\xbb\x77\x3f\x11\xb3\xbf\xa5\x62\xf7\xcb\x9f\xb5\xc1\xdf\x59\x8a\x6a\xa3\x34\x66\xfb\xaf\xbd\x69\x13\xf8\xd4\xd9\x43\xc4\xec\x4d\x46\xe7\x47\xf5\x68\xec\x90\x19\x2e\xc5\xb9\xf9\x28\xcd\x9b\xb0\xd7\xe7\xf1\x54\x15\xb3\xa5\x1b\x5b\x9a\xf3\x88\x8d\xa5\x74\xe3\xe3\xf2\xb8\xfd\x00\xc9\x55\x22\x50\xf6\xe1\x66\x5d\x37\xb9\x6b\xb3\x81\x6a\x59\x61\xae\x72\x19\xe5\x6c\x86\x4a\xd7\xef\x70\x35\xa1\x07\x5e\x14\x9d\x65\x5c\x02\x77\x81\xe2\x34\x50\xa0\xc5\x16\x89\x75\xa0\x36\xc5\x39\x0a\x2f\x4a\x53\x39\x47\x8d\x09\xc4\x82\xeb\xa2\x50\xea\x64\xaf\xd8\xaf\xbd\x7b\x31\xcf\x81\x71\x98\x6e\x34\x2a\x2f\x63\x6a\x8c\x5d\xe7\xcb\x57\xd9\xd4\x38\x74\x00\xd0\x19\xb2\x47\xc0\x65\xc6\x52\x2c\xcf\xcb\x63\x11\xd3\xa2\x61\x89\x1e\x2f\xaa\xcb\x2e\xfe\x79\x68\x0e\xd0\x0b\xaa\x81\x29\xbb\x77\x63\x7e\xc6\xed\xb3\x1f\xcc\x43\xf5\x03\x24\x4c\xda\xc2\x7c\xd3\xe9\x0f\x6f\xb7\x9b\x47\x8a\xaf\x27\x30\xd8\x4d\x3d\xce\xfa\xc1\x59\x05\xa6\x8d\x77\xf8\xcc\xf4\x22\x37\x4d\xbc\x92\x12\xb9\x86\xb6\x8f\x01\xfa\xac\xe4\x53\xeb\xdb\xbc\x32\xda\xc7\x46\x1d\x15\x57\xa7\x11\xbf\xd7\x48\xdb\xcf\x12\xeb\x8c\xaf\x5f\x98\x74\x15\x17\xc1\xb1\xfb\x75\x0e\xfb\x01\x40\xd9\xa0\x3e\x22\x60\x57\xfe\x0d\xd5\x91\xc7\xbb\x51\xa6\x70\xc7\xaa\xe7\x6d\xd4\x00\x60\x8e\x1c\x25\x8b\xff\xc0\x37\x49\xb9\x06\xee\x65\x52\x3e\xf8\x1e\xd9\x7f\x82\xc8\x2e\x1d\xe3\xe6\xff\xd8\xc0\xae\x00\xf5\x6b\x15\xf1\xc5\xc9\xb4\x73\xbb\x6a\xef\xfe\x54\x13\xa7\x8d\xef\x15\x54\xf0\x70\x29\xc5\x9a\x25\xa5\x47\x23\x20\x95\x26\x43\xb5\xe7\x55\xd4\xf3\xaa\xc2\xbf\xb2\xe2\x4f\xd1\xcd\x8d\x25\xda\x8b\xf1\xfb\x96\xf7\x4f\xb7\x1e\xa1\xe7\xb9\x1b\xcb\x6f\x1d\x9a\xef\xa2\xdc\x7c\xd3\x84\x7b\xc3\xb6\x71\xed\xed\xbb\xcf\x36\xbe\x4e\x21\x70\xe2\xcb\x9b\x74\x73\x0e\x9f\x11\x04\x4f\x37\xf9\x17\x59\xf6\x16\x20\xb8\x57\xd6\xbb\x74\x4b\x60\x3e\x59\xf8\xe5\x68\x78\xa4\x7e\x47\xed\x6b\x80\xc2\xc1\x4d\x48\x3e\x8e\xc0\x26\xe3\x7a\xab\xff\x29\x7d\x1f\xf6\x08\xc9\x8e\x60\xa9\xd4\xae\x3b\x2d\xaa\x9d\x8a\x36\x35\xb5\x9b\x14\xbe\x3c\x0c\x06\x83\x5a\x9e\x0a\x93\x50\x04\x24\x43\xf7\x89\x6d\x98\x28\xc8\xa0\x9a\x06\xca\x4f\x79\x3b\xbe\xce\x74\x2c\x6a\xf9\xb1\xdf\x41\x24\x7c\x0f\x5a\xad\x35\x02\x87\x54\x9c\xd1\xff\xee\x90\xd4\xde\xfc\x1d\xc1\xb3\xfd\x65\x19\xf9\x3d\x00\x00\xff\xff\x69\x79\xe9\x8f\x85\x2d\x00\x00") func ResourcesComponentDescriptorV2SchemaYamlBytes() ([]byte, error) { return bindataRead( diff --git a/pkg/contexts/ocm/labels/routingslip/entry.go b/pkg/contexts/ocm/labels/routingslip/entry.go index fb502bf4dc..6584a6b0b1 100644 --- a/pkg/contexts/ocm/labels/routingslip/entry.go +++ b/pkg/contexts/ocm/labels/routingslip/entry.go @@ -59,12 +59,12 @@ var excludes = signing.MapExcludes{ type HistoryEntries = []HistoryEntry type HistoryEntry struct { - Payload *GenericEntry `json:"payload"` - Timestamp metav1.Timestamp `json:"timestamp"` - Parent *digest.Digest `json:"parent,omitempty"` - Links []Link `json:"links,omitempty"` - Digest digest.Digest `json:"digest"` - Signature metav1.SignatureSpec `json:"signature"` + Payload *GenericEntry `json:"payload"` + Timestamp metav1.Timestamp `json:"timestamp"` + Parent *digest.Digest `json:"parent,omitempty"` + Links []Link `json:"links,omitempty"` + Digest digest.Digest `json:"digest"` + Signature *metav1.SignatureSpec `json:"signature,omitempty"` } func (e *HistoryEntry) Normalize() ([]byte, error) { diff --git a/pkg/contexts/ocm/labels/routingslip/entrytypes_test.go b/pkg/contexts/ocm/labels/routingslip/entrytypes_test.go index a3265b0402..9ee0ad0deb 100644 --- a/pkg/contexts/ocm/labels/routingslip/entrytypes_test.go +++ b/pkg/contexts/ocm/labels/routingslip/entrytypes_test.go @@ -50,7 +50,7 @@ var _ = Describe("routing slip", func() { Payload: Must(internal.ToGenericEntry(New("test"))), Digest: "sha:digest", Timestamp: now, - Signature: metav1.SignatureSpec{ + Signature: &metav1.SignatureSpec{ Algorithm: "algo", Value: "value", MediaType: "mime", diff --git a/pkg/contexts/ocm/labels/routingslip/interface.go b/pkg/contexts/ocm/labels/routingslip/interface.go index d3ad8a7a5f..0e24f87ad6 100644 --- a/pkg/contexts/ocm/labels/routingslip/interface.go +++ b/pkg/contexts/ocm/labels/routingslip/interface.go @@ -18,7 +18,7 @@ type ( ) type SlipAccess interface { - Get(name string) *RoutingSlip + Get(name string) (*RoutingSlip, error) } func DefaultEntryTypeScheme() EntryTypeScheme { diff --git a/pkg/contexts/ocm/labels/routingslip/label.go b/pkg/contexts/ocm/labels/routingslip/label.go index e7f04a83ae..9ab4b93044 100644 --- a/pkg/contexts/ocm/labels/routingslip/label.go +++ b/pkg/contexts/ocm/labels/routingslip/label.go @@ -10,7 +10,7 @@ import ( "github.com/opencontainers/go-digest" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge" + "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge" "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge" "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" "github.com/open-component-model/ocm/pkg/utils" @@ -25,8 +25,8 @@ var spec = utils.Must(hpi.NewSpecification( simplemapmerge.NewConfig( "", utils.Must(hpi.NewSpecification( - simplelistmerge.ALGORITHM, - simplelistmerge.NewConfig(), + maplistmerge.ALGORITHM, + maplistmerge.NewConfig("digest", maplistmerge.MODE_INBOUND), )), )), ) @@ -39,14 +39,14 @@ func (l LabelValue) Has(name string) bool { return l[name] != nil } -func (l LabelValue) Get(name string) *RoutingSlip { - return NewRoutingSlip(name, l, l[name]...) +func (l LabelValue) Get(name string) (*RoutingSlip, error) { + return NewRoutingSlip(name, l) } -func (l LabelValue) Query(name string) *RoutingSlip { +func (l LabelValue) Query(name string) (*RoutingSlip, error) { a := l[name] if a == nil { - return nil + return nil, nil } return l.Get(name) } @@ -55,11 +55,14 @@ func (l LabelValue) Leaves() []Link { var links []Link for k := range l { - for _, d := range l.Get(k).Leaves() { - links = append(links, Link{ - Name: k, - Digest: d, - }) + s, err := l.Get(k) + if err == nil { + for _, d := range s.Leaves() { + links = append(links, Link{ + Name: k, + Digest: d, + }) + } } } sort.Slice(links, func(i, j int) bool { return links[i].Compare(links[j]) < 0 }) @@ -79,7 +82,10 @@ func AddEntry(cv cpi.ComponentVersionAccess, name string, algo string, e Entry, if label == nil { label = LabelValue{} } - slip := label.Get(name) + slip, err := label.Get(name) + if err != nil { + return nil, err + } entry, err := slip.Add(cv.GetContext(), name, algo, e, links, parent...) if err != nil { return nil, err diff --git a/pkg/contexts/ocm/labels/routingslip/slip.go b/pkg/contexts/ocm/labels/routingslip/slip.go index 28aa72fded..2d6e887de6 100644 --- a/pkg/contexts/ocm/labels/routingslip/slip.go +++ b/pkg/contexts/ocm/labels/routingslip/slip.go @@ -5,6 +5,7 @@ package routingslip import ( + "crypto/x509/pkix" "fmt" "github.com/opencontainers/go-digest" @@ -18,6 +19,7 @@ import ( "github.com/open-component-model/ocm/pkg/generics" "github.com/open-component-model/ocm/pkg/signing" "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) const ( @@ -41,7 +43,7 @@ func (s RoutingSlipIndex) Leaves() []digest.Digest { return found.AsArray() } -func (s RoutingSlipIndex) Verify(ctx Context, name string, sig bool, acc SlipAccess) error { +func (s RoutingSlipIndex) Verify(ctx Context, name string, issuer *pkix.Name, sig bool, acc SlipAccess) error { if len(s) == 0 { return nil } @@ -66,7 +68,12 @@ func (s RoutingSlipIndex) Verify(ctx Context, name string, sig bool, acc SlipAcc if handler == nil { return errors.ErrUnknown(compdesc.KIND_VERIFY_ALGORITHM, last.Signature.Algorithm) } - err := handler.Verify(last.Digest.Encoded(), sha256.Handler{}.Crypto(), last.Signature.ConvertToSigning(), key) + sctx := &signing.DefaultSigningContext{ + Hash: sha256.Handler{}.Crypto(), + PublicKey: key, + Issuer: issuer, + } + err := handler.Verify(last.Digest.Encoded(), last.Signature.ConvertToSigning(), sctx) if err != nil { return errors.Wrapf(err, "cannot verify entry %s", d) } @@ -104,11 +111,14 @@ func (s RoutingSlipIndex) verify(ctx Context, name string, id digest.Digest, acc return err } } else { - slip := acc.Get(l.Name) + slip, err := acc.Get(l.Name) + if err != nil { + return errors.ErrInvalidWrap(err, KIND_ROUTING_SLIP, l.Name) + } if slip == nil { return errors.ErrNotFound(KIND_ROUTING_SLIP, l.Name) } - err := slip.Index().verify(ctx, l.Name, l.Digest, acc, found) + err = slip.Index().verify(ctx, l.Name, l.Digest, acc, found) if err != nil { return err } @@ -128,18 +138,31 @@ func (s RoutingSlipIndex) verify(ctx Context, name string, id digest.Digest, acc type RoutingSlip struct { name string + issuer pkix.Name entries []HistoryEntry index RoutingSlipIndex access SlipAccess } -func NewRoutingSlip(name string, acc SlipAccess, entries ...HistoryEntry) *RoutingSlip { +func NewRoutingSlip(name string, acc LabelValue) (*RoutingSlip, error) { + var entries HistoryEntries + dn, err := signutils.ParseDN(name) + if err != nil { + return nil, err + } + name = signutils.NormalizeDN(*dn) + if acc != nil { + entries = acc[name] + } + if err != nil { + return nil, err + } index := RoutingSlipIndex{} for i := range entries { index[entries[i].Digest] = &entries[i] } - return &RoutingSlip{name: name, access: acc, entries: entries, index: index} + return &RoutingSlip{name: name, issuer: *dn, access: acc, entries: entries, index: index}, nil } func (s *RoutingSlip) GetName() string { @@ -174,7 +197,7 @@ func (s *RoutingSlip) Verify(ctx Context, name string, sig bool) error { if len(s.entries) == 0 { return nil } - return s.index.Verify(ctx, name, sig, s.access) + return s.index.Verify(ctx, name, &s.issuer, sig, s.access) } func (s *RoutingSlip) Add(ctx Context, name string, algo string, e Entry, links []Link, parent ...digest.Digest) (*HistoryEntry, error) { @@ -183,6 +206,13 @@ func (s *RoutingSlip) Add(ctx Context, name string, algo string, e Entry, links if handler == nil { return nil, errors.ErrUnknown(compdesc.KIND_SIGN_ALGORITHM, algo) } + + dn, err := signutils.ParseDN(name) + if err != nil { + return nil, err + } + name = signutils.NormalizeDN(*dn) + key, err := signing.ResolvePrivateKey(registry, name) if err != nil { return nil, err @@ -190,6 +220,7 @@ func (s *RoutingSlip) Add(ctx Context, name string, algo string, e Entry, links if key == nil { return nil, errors.ErrUnknown(compdesc.KIND_PRIVATE_KEY, name) } + pub := registry.GetPublicKey(name) err = s.Verify(ctx, name, true) if err != nil { @@ -229,8 +260,8 @@ func (s *RoutingSlip) Add(ctx Context, name string, algo string, e Entry, links Payload: gen, Timestamp: metav1.NewTimestamp(), Digest: "", - Signature: metav1.SignatureSpec{}, } + if base != nil { entry.Parent = &base.Digest if entry.Parent.String() == "" { @@ -239,7 +270,10 @@ func (s *RoutingSlip) Add(ctx Context, name string, algo string, e Entry, links } for _, l := range links { - slip := s.access.Get(l.Name) + slip, err := s.access.Get(l.Name) + if err != nil { + return nil, errors.ErrInvalidWrap(err, KIND_ROUTING_SLIP, l.Name) + } if slip == nil { return nil, errors.ErrNotFound(KIND_ROUTING_SLIP, l.Name) } @@ -258,11 +292,22 @@ func (s *RoutingSlip) Add(ctx Context, name string, algo string, e Entry, links } entry.Digest = d - sig, err := handler.Sign(ctx.CredentialsContext(), d.Encoded(), sha256.Handler{}.Crypto(), name, key) + sctx := &signing.DefaultSigningContext{ + Hash: sha256.Handler{}.Crypto(), + PrivateKey: key, + PublicKey: pub, + RootCerts: nil, + Issuer: dn, + } + sig, err := handler.Sign(ctx.CredentialsContext(), d.Encoded(), sctx) if err != nil { return nil, err } - entry.Signature = *metav1.SignatureSpecFor(sig) + if base != nil { + // keep signatures for leaves, only. + base.Signature = nil + } + entry.Signature = metav1.SignatureSpecFor(sig) s.entries = append(s.entries, *entry) s.index[entry.Digest] = entry return entry, nil @@ -275,11 +320,7 @@ func GetSlip(cv cpi.ComponentVersionAccess, name string) (*RoutingSlip, error) { if err != nil { return nil, err } - return &RoutingSlip{ - name: name, - entries: label[name], - access: label, - }, nil + return NewRoutingSlip(name, label) } func SetSlip(cv cpi.ComponentVersionAccess, slip *RoutingSlip) error { diff --git a/pkg/contexts/ocm/labels/routingslip/slip_test.go b/pkg/contexts/ocm/labels/routingslip/slip_test.go index ddc2afdf82..8d584e4401 100644 --- a/pkg/contexts/ocm/labels/routingslip/slip_test.go +++ b/pkg/contexts/ocm/labels/routingslip/slip_test.go @@ -52,7 +52,6 @@ var _ = Describe("management", func() { Timestamp: t, Parent: &parent, Digest: "xxx", - Signature: metav1.SignatureSpec{}, } fmt.Printf("timestamp: %s\n", t) @@ -61,7 +60,7 @@ var _ = Describe("management", func() { }) It("adds entry", func() { - slip := routingslip.NewRoutingSlip(ORG, nil) + slip := Must(routingslip.NewRoutingSlip(ORG, nil)) e1 := comment.New("start of routing slip") e2 := comment.New("next comment") @@ -78,9 +77,9 @@ var _ = Describe("management", func() { It("adds linked entry", func() { label := routingslip.LabelValue{} - slip := routingslip.NewRoutingSlip(ORG, label) + slip := Must(routingslip.NewRoutingSlip(ORG, label)) label.Set(slip) - lslip := routingslip.NewRoutingSlip(OTHER, label) + lslip := Must(routingslip.NewRoutingSlip(OTHER, label)) label.Set(lslip) e1 := comment.New("start of routing slip") diff --git a/pkg/contexts/ocm/resolver.go b/pkg/contexts/ocm/resolver.go index 31ba4a23fb..63732374af 100644 --- a/pkg/contexts/ocm/resolver.go +++ b/pkg/contexts/ocm/resolver.go @@ -7,11 +7,32 @@ package ocm import ( "sync" + "golang.org/x/exp/slices" + "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" "github.com/open-component-model/ocm/pkg/errors" ) +type DedicatedResolver []ComponentVersionAccess + +var _ ComponentVersionResolver = (*DedicatedResolver)(nil) + +func NewDedicatedResolver(cv ...ComponentVersionAccess) ComponentVersionResolver { + return DedicatedResolver(slices.Clone(cv)) +} + +func (d DedicatedResolver) LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) { + for _, cv := range d { + if cv.GetName() == name && cv.GetVersion() == version { + return cv.Dup() + } + } + return nil, nil +} + +//////////////////////////////////////////////////////////////////////////////// + type CompoundResolver struct { lock sync.RWMutex resolvers []ComponentVersionResolver diff --git a/pkg/contexts/ocm/signing/digestctx.go b/pkg/contexts/ocm/signing/digestctx.go index 6bf302c07e..f307dee066 100644 --- a/pkg/contexts/ocm/signing/digestctx.go +++ b/pkg/contexts/ocm/signing/digestctx.go @@ -13,6 +13,7 @@ import ( metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/signing" + "github.com/open-component-model/ocm/pkg/signing/signutils" "github.com/open-component-model/ocm/pkg/utils" ) @@ -182,7 +183,7 @@ func (dc *DigestContext) ValidFor(ctx *DigestContext) bool { return true } -func (dc *DigestContext) determineSignatureInfo(state WalkingState, opts *Options) (*Options, error) { +func (dc *DigestContext) determineSignatureInfo(state WalkingState, cv ocm.ComponentVersionAccess, opts *Options) (*Options, error) { if opts.SignatureName() != "" { // determine digester type var found bool @@ -222,8 +223,24 @@ func (dc *DigestContext) determineSignatureInfo(state WalkingState, opts *Option opts.Printer.Printf("Warning: digest type %s for signature %q in %s does not match (signature ignored)\n", dc.DigestType.String(), sig.Name, state.History) } } else { - if opts.SignatureConfigured(sig.Name) { - return nil, errors.ErrNotFound(compdesc.KIND_PUBLIC_KEY, sig.Name) + if opts.SignatureConfigured(sig.Name) || opts.SignatureName() == "" { + i := cv.GetDescriptor().GetSignatureIndex(sig.Name) + if i < 0 { + return nil, errors.ErrNotFound(compdesc.KIND_SIGNATURE, sig.Name) + } + s := cv.GetDescriptor().Signatures[i] + if s.Signature.MediaType == signutils.MediaTypePEM { + _, _, _, err := signutils.GetSignatureFromPem([]byte(s.Signature.Value)) + if err != nil { + return nil, errors.Wrapf(err, "cannot decode signature PEM for %q", sig.Name) + } + signatures = append(signatures, sig.Name) + dc.DigestType = DigesterType(&sig.Digest) + } else { + if opts.SignatureName() != "" { + return nil, errors.ErrNotFound(compdesc.KIND_PUBLIC_KEY, sig.Name) + } + } } } } diff --git a/pkg/contexts/ocm/signing/handle.go b/pkg/contexts/ocm/signing/handle.go index 54394ebee2..beab675524 100644 --- a/pkg/contexts/ocm/signing/handle.go +++ b/pkg/contexts/ocm/signing/handle.go @@ -5,8 +5,11 @@ package signing import ( + "crypto" + "encoding/hex" "fmt" "reflect" + "time" "github.com/mandelsoft/logging" @@ -17,7 +20,10 @@ import ( metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/open-component-model/ocm/pkg/generics" "github.com/open-component-model/ocm/pkg/signing" + "github.com/open-component-model/ocm/pkg/signing/signutils" + "github.com/open-component-model/ocm/pkg/signing/tsa" "github.com/open-component-model/ocm/pkg/utils" ) @@ -163,7 +169,7 @@ func _apply(state WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAc } } else { var err error - opts, err = ctx.determineSignatureInfo(state, opts) + opts, err = ctx.determineSignatureInfo(state, cv, opts) if err != nil { return nil, fmt.Errorf("failed to determine signature info: %w", err) } @@ -223,6 +229,8 @@ func _apply(state WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAc } } + digests := compdesc.NewCompDescDigests(cd) + var spec *metav1.DigestSpec legacy := signing.IsLegacyHashAlgorithm(ctx.RootContextInfo.DigestType.HashAlgorithm) && !opts.DoSign() if ctx.Digest == nil { @@ -240,7 +248,7 @@ func _apply(state WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAc if hasher == nil { return nil, fmt.Errorf("unknown hash algorithm %q", dt.HashAlgorithm) } - norm, digest, err := compdesc.NormHash(cd, dt.NormalizationAlgorithm, hasher.Create()) + norm, digest, err := digests.Get(dt.NormalizationAlgorithm, hasher) if err != nil { return nil, errors.Wrapf(err, "failed hashing component descriptor") } @@ -253,7 +261,7 @@ func _apply(state WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAc } if opts.DoVerify() { - dig, err := doVerify(cd, state, signatureNames, opts) + dig, err := doVerify(digests, state, signatureNames, opts) if err != nil { return nil, err } @@ -272,16 +280,27 @@ func _apply(state WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAc if err != nil { return nil, err } - sig, err := opts.Signer.Sign(cv.GetContext().CredentialsContext(), ctx.Digest.Value, opts.Hasher.Crypto(), opts.Issuer, priv) + sctx := &signing.DefaultSigningContext{ + Hash: opts.Hasher.Crypto(), + PrivateKey: priv, + PublicKey: opts.PublicKey(opts.SignatureName()), + RootCerts: opts.RootCerts, + Issuer: opts.GetIssuer(), + } + sig, err := opts.Signer.Sign(cv.GetContext().CredentialsContext(), ctx.Digest.Value, sctx) if err != nil { return nil, errors.Wrapf(err, "failed signing component descriptor") } if sig.Issuer != "" { - if opts.Issuer != "" && opts.Issuer != sig.Issuer { - return nil, errors.Newf("signature issuer %q does not match intended issuer %q", sig.Issuer, opts.Issuer) + iss, err := signutils.ParseDN(sig.Issuer) + if err != nil { + return nil, errors.Wrapf(err, "signature issuer") + } + if sctx.Issuer != nil { + if err := signutils.MatchDN(*iss, *sctx.Issuer); err != nil { + return nil, errors.Newf("signature issuer %q does not match intended issuer %q", sig.Issuer, sctx.Issuer) + } } - } else { - sig.Issuer = opts.Issuer } signature := metav1.Signature{ Name: opts.SignatureName(), @@ -293,6 +312,30 @@ func _apply(state WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAc Issuer: sig.Issuer, }, } + + if url := opts.EffectiveTSAUrl(); url != "" { + h, d, err := DigestInfo(opts, ctx.Digest) + if err != nil { + return nil, err + } + mi, err := tsa.NewMessageImprint(h, d) + if err != nil { + return nil, err + } + + ts, t, err := tsa.Request(url, mi) + if err != nil { + return nil, err + } + data, err := tsa.ToPem(ts) + if err != nil { + return nil, err + } + signature.Timestamp = &metav1.TimestampSpec{ + Value: string(data), + Time: generics.Pointer(compdesc.NewTimestampFor(t)), + } + } if found >= 0 { cd.Signatures[found] = signature } else { @@ -351,26 +394,47 @@ func resMsg(ref *compdesc.Resource, acc string, msg string, args ...interface{}) return fmt.Sprintf("%s %s:%s", fmt.Sprintf(msg, args...), ref.Name, ref.Version) } -func doVerify(cd *compdesc.ComponentDescriptor, state WalkingState, signatureNames []string, opts *Options) (*metav1.DigestSpec, error) { +func DigestInfo(opts *Options, d *metav1.DigestSpec) (crypto.Hash, []byte, error) { + hasher := opts.Registry.GetHasher(d.HashAlgorithm) + if hasher == nil { + return 0, nil, errors.ErrUnknown(compdesc.KIND_HASH_ALGORITHM, d.HashAlgorithm) + } + data, err := hex.DecodeString(d.Value) + if err != nil { + return 0, nil, errors.ErrInvalid(compdesc.KIND_DIGEST, d.Value) + } + return hasher.Crypto(), data, nil +} + +func doVerify(digests *compdesc.CompDescDigests, state WalkingState, signatureNames []string, opts *Options) (*metav1.DigestSpec, error) { var spec *metav1.DigestSpec + + sctx := &signing.DefaultSigningContext{ + Hash: opts.Hasher.Crypto(), + RootCerts: opts.RootCerts, + } + found := []string{} for _, n := range signatureNames { - f := cd.GetSignatureIndex(n) + f := digests.Descriptor().GetSignatureIndex(n) if f < 0 { continue } - var pub any + sig := &digests.Descriptor().Signatures[f] + + sctx.Issuer = opts.IssuerFor(n) if !opts.Keyless { - pub = opts.PublicKey(n) - if pub == nil { - if opts.SignatureConfigured(n) { - return nil, errors.ErrNotFound(compdesc.KIND_PUBLIC_KEY, n) + sctx.PublicKey = opts.PublicKey(n) + if sctx.PublicKey == nil { + var err error + + opts.Printer.Printf("no public key found for signature %q -> extract key from signature\n", n) + sctx.PublicKey, err = GetPublicKeyFromSignature(sig, sctx, opts) + if err != nil { + return nil, errors.Wrapf(err, "public key from signature") } - opts.Printer.Printf("Warning: no public key for signature %q in %s\n", n, state.History) - continue } } - sig := &cd.Signatures[f] verifier := opts.Registry.GetVerifier(sig.Signature.Algorithm) if verifier == nil { if opts.SignatureConfigured(n) { @@ -384,16 +448,19 @@ func doVerify(cd *compdesc.ComponentDescriptor, state WalkingState, signatureNam if hasher == nil { return nil, errors.ErrUnknown(compdesc.KIND_HASH_ALGORITHM, sig.Digest.HashAlgorithm) } - digest, err := compdesc.Hash(cd, sig.Digest.NormalisationAlgorithm, hasher.Create()) + + _, digest, err := digests.Get(sig.Digest.NormalisationAlgorithm, hasher) if err != nil { return nil, errors.Wrapf(err, "failed hashing component descriptor") } if sig.Digest.Value != digest { return nil, errors.Newf("signature digest (%s) does not match found digest (%s)", sig.Digest.Value, digest) } - err = verifier.Verify(sig.Digest.Value, hasher.Crypto(), sig.ConvertToSigning(), pub) + + sctx.Hash = hasher.Crypto() + err = verifier.Verify(sig.Digest.Value, sig.ConvertToSigning(), sctx) if err != nil { - return nil, errors.ErrInvalidWrap(err, compdesc.KIND_SIGNATURE, n) + return nil, errors.Wrapf(err, "signature %q", n) } found = append(found, n) if opts.SignatureName() == sig.Name { @@ -411,6 +478,50 @@ func doVerify(cd *compdesc.ComponentDescriptor, state WalkingState, signatureNam return spec, nil } +func GetPublicKeyFromSignature(sig *compdesc.Signature, sctx signing.SigningContext, opts *Options) (signutils.GenericPublicKey, error) { + if sig.Signature.MediaType != signutils.MediaTypePEM { + return nil, errors.ErrNotFound(compdesc.KIND_PUBLIC_KEY) + } + _, _, certs, err := signutils.GetSignatureFromPem([]byte(sig.Signature.Value)) + if err != nil { + return nil, err + } + if len(certs) == 0 { + return nil, errors.ErrNotFound(compdesc.KIND_PUBLIC_KEY) + } + + cert, pool, err := signutils.GetCertificate(certs, false) + if err != nil { + return nil, err + } + + var timestamp *time.Time + if sig.Timestamp != nil { + ts, err := tsa.FromPem([]byte(sig.Timestamp.Value)) + if err != nil { + return nil, errors.Wrapf(err, "signature timestamp") + } + h, d, err := DigestInfo(opts, &sig.Digest) + if err != nil { + return nil, errors.Wrapf(err, "signature digest") + } + mi, err := tsa.NewMessageImprint(h, d) + if err != nil { + return nil, errors.Wrapf(err, "signature digest") + } + timestamp, err = tsa.Verify(mi, ts, false, sctx.GetRootCerts()) + if err != nil { + return nil, errors.Wrapf(err, "signature timestamp verification") + } + } + + err = signutils.VerifyCertificate(cert, pool, sctx.GetRootCerts(), sctx.GetIssuer(), timestamp) + if err != nil { + return nil, errors.Wrapf(err, "public key certificate") + } + return cert.PublicKey, nil +} + func calculateReferenceDigests(state WalkingState, opts *Options, legacy bool) (rerr error) { var finalize finalizer.Finalizer defer finalize.FinalizeWithErrorPropagation(&rerr) diff --git a/pkg/contexts/ocm/signing/options.go b/pkg/contexts/ocm/signing/options.go index 6b2e0b4deb..f65fb5e448 100644 --- a/pkg/contexts/ocm/signing/options.go +++ b/pkg/contexts/ocm/signing/options.go @@ -5,7 +5,7 @@ package signing import ( - "crypto/x509" + "crypto/x509/pkix" "fmt" "strings" @@ -14,8 +14,10 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/generics" "github.com/open-component-model/ocm/pkg/signing" "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" + "github.com/open-component-model/ocm/pkg/signing/signutils" "github.com/open-component-model/ocm/pkg/utils" ) @@ -303,32 +305,63 @@ func (o *signame) ApplySigningOption(opts *Options) { //////////////////////////////////////////////////////////////////////////////// type issuer struct { - name string + issuer pkix.Name + name string + err error } // Issuer provides an option requesting to use a dedicated issuer name // for a signing operation. -func Issuer(name string) Option { - return &issuer{name} +func Issuer(is string) Option { + dn, err := signutils.ParseDN(is) + if err != nil { + return &issuer{err: err} + } + return &issuer{issuer: *dn} +} + +func IssuerFor(name string, is string) Option { + dn, err := signutils.ParseDN(is) + if err != nil { + return &issuer{err: err} + } + return PKIXIssuerFor(name, *dn) +} + +// PKIXIssuer provides an option requesting to use a dedicated issuer name +// for a signing operation. +func PKIXIssuer(is pkix.Name) Option { + return &issuer{issuer: is} +} + +func PKIXIssuerFor(name string, is pkix.Name) Option { + return &issuer{issuer: is, name: name} } func (o *issuer) ApplySigningOption(opts *Options) { - opts.Issuer = o.name + if o.name != "" { + if opts.Keys == nil { + opts.Keys = signing.NewKeyRegistry() + } + opts.Keys.RegisterIssuer(o.name, generics.Pointer(o.issuer)) + } else { + opts.Issuer = generics.Pointer(o.issuer) + } } //////////////////////////////////////////////////////////////////////////////// -type rootverts struct { - pool *x509.CertPool +type rootcerts struct { + pool signutils.GenericCertificatePool } // RootCertificates provides an option requesting to dedicated root certificates // for a signing/verification operation using certificates. -func RootCertificates(pool *x509.CertPool) Option { - return &rootverts{pool} +func RootCertificates(pool signutils.GenericCertificatePool) Option { + return &rootcerts{pool} } -func (o *rootverts) ApplySigningOption(opts *Options) { +func (o *rootcerts) ApplySigningOption(opts *Options) { opts.RootCerts = o.pool } @@ -380,6 +413,32 @@ func (o *pubkey) ApplySigningOption(opts *Options) { //////////////////////////////////////////////////////////////////////////////// +type tsaOpt struct { + url string + use *bool +} + +// UseTSA enables the usage of a timestamp server authority. +func UseTSA(flag ...bool) Option { + return &tsaOpt{use: utils.BoolP(utils.GetOptionFlag(flag...))} +} + +// TSAUrl selects the TSA server URL to use, if TSA mode is enabled. +func TSAUrl(url string) Option { + return &tsaOpt{url: url} +} + +func (o *tsaOpt) ApplySigningOption(opts *Options) { + if o.url != "" { + opts.TSAUrl = o.url + } + if o.use != nil { + opts.UseTSA = *o.use + } +} + +//////////////////////////////////////////////////////////////////////////////// + type Options struct { Printer common.Printer Update bool @@ -388,9 +447,9 @@ type Options struct { Verify bool SignAlgo string Signer signing.Signer - Issuer string + Issuer *pkix.Name VerifySignature bool - RootCerts *x509.CertPool + RootCerts signutils.GenericCertificatePool HashAlgo string Hasher signing.Hasher Keys signing.KeyRegistry @@ -400,6 +459,8 @@ type Options struct { SignatureNames []string NormalizationAlgo string Keyless bool + TSAUrl string + UseTSA bool effectiveRegistry signing.Registry } @@ -421,6 +482,9 @@ func (o *Options) ApplySigningOption(opts *Options) { if o.Printer != nil { opts.Printer = o.Printer } + if o.Keys != nil { + opts.Keys = o.Keys + } if o.Signer != nil { opts.Signer = o.Signer } @@ -450,7 +514,7 @@ func (o *Options) ApplySigningOption(opts *Options) { opts.SkipAccessTypes[k] = v } } - if o.Issuer != "" { + if o.Issuer != nil { opts.Issuer = o.Issuer } opts.Recursively = o.Recursively @@ -460,6 +524,12 @@ func (o *Options) ApplySigningOption(opts *Options) { if o.NormalizationAlgo != "" { opts.NormalizationAlgo = o.NormalizationAlgo } + if o.TSAUrl != "" { + opts.TSAUrl = o.TSAUrl + } + if o.UseTSA { + opts.UseTSA = o.UseTSA + } } // Complete takes either nil, an ocm.ContextProvider or a signing.Registry. @@ -471,6 +541,7 @@ func (o *Options) Complete(ctx interface{}) error { if ctx == nil { ctx = ocm.DefaultContext() } + switch t := ctx.(type) { case ocm.ContextProvider: reg = signingattr.Get(t.OCMContext()) @@ -487,10 +558,23 @@ func (o *Options) Complete(ctx interface{}) error { } o.effectiveRegistry = o.Registry - if o.Keys != nil && o.Keys.HasKeys() { + if o.Keys != nil && (o.Keys.HasKeys() || o.Keys.HasIssuers() || o.Keys.HasRootCertificates()) { o.effectiveRegistry = signing.RegistryWithPreferredKeys(o.Registry, o.Keys) } + if o.RootCerts == nil && o.effectiveRegistry.HasRootCertificates() { + o.RootCerts = o.effectiveRegistry.GetRootCertPool(true) + } + + if o.RootCerts != nil { + // check root certificates + pool, err := signutils.GetCertPool(o.RootCerts, false) + if err != nil { + return err + } + o.RootCerts = pool + } + if o.SkipAccessTypes == nil { o.SkipAccessTypes = map[string]bool{} } @@ -520,18 +604,20 @@ func (o *Options) Complete(ctx interface{}) error { if o.Signer != nil && !o.VerifySignature { if pub := o.PublicKey(o.SignatureName()); pub != nil { o.VerifySignature = true - if err := o.checkCert(pub, o.SignatureName()); err != nil { + if err := o.checkCert(pub, o.IssuerFor(o.SignatureName())); err != nil { return fmt.Errorf("public key not valid: %w", err) } } } else if o.VerifySignature { for _, n := range o.SignatureNames { pub := o.PublicKey(n) - if pub == nil { - return errors.ErrNotFound(compdesc.KIND_PUBLIC_KEY, n) - } - if err := o.checkCert(pub, n); err != nil { - return fmt.Errorf("public key not valid: %w", err) + // don't check for public key here, anymore, + // because the key might be provided via certificate together with + // the signature. An early failure is therefore not possible anymore. + if pub != nil { + if err := o.checkCert(pub, o.IssuerFor(n)); err != nil { + return fmt.Errorf("public key not valid: %w", err) + } } } } @@ -552,14 +638,17 @@ func (o *Options) Complete(ctx interface{}) error { return nil } -func (o *Options) checkCert(data interface{}, name string) error { - cert, err := signing.GetCertificate(data) +func (o *Options) checkCert(data interface{}, name *pkix.Name) error { + cert, pool, err := signutils.GetCertificate(data, false) if err != nil { return nil } - err = signing.VerifyCert(nil, o.RootCerts, "", cert) + err = signing.VerifyCertDN(pool, o.RootCerts, name, cert) if err != nil { - return errors.Wrapf(err, "public key %q", name) + if name != nil { + return errors.Wrapf(err, "issuer [%s]", name) + } + return err } return nil } @@ -587,6 +676,26 @@ func (o *Options) SignatureName() string { return "" } +func (o *Options) GetIssuer() *pkix.Name { + if o.Issuer != nil { + return o.Issuer + } + if o.effectiveRegistry != nil { + return o.effectiveRegistry.GetIssuer(o.SignatureName()) + } + return nil +} + +func (o *Options) IssuerFor(name string) *pkix.Name { + if o.Issuer != nil && name == o.SignatureName() { + return o.Issuer + } + if o.effectiveRegistry != nil { + return o.effectiveRegistry.GetIssuer(name) + } + return nil +} + func (o *Options) SignatureConfigured(name string) bool { for _, n := range o.SignatureNames { if n == name { @@ -596,14 +705,24 @@ func (o *Options) SignatureConfigured(name string) bool { return false } -func (o *Options) PublicKey(sig string) interface{} { +func (o *Options) PublicKey(sig string) signutils.GenericPublicKey { return o.effectiveRegistry.GetPublicKey(sig) } -func (o *Options) PrivateKey() (interface{}, error) { +func (o *Options) PrivateKey() (signutils.GenericPrivateKey, error) { return signing.ResolvePrivateKey(o.effectiveRegistry, o.SignatureName()) } +func (o *Options) EffectiveTSAUrl() string { + if o.UseTSA { + if o.TSAUrl != "" { + return o.TSAUrl + } + return o.effectiveRegistry.TSAUrl() + } + return "" +} + func (o *Options) Dup() *Options { opts := *o return &opts diff --git a/pkg/contexts/ocm/signing/options_test.go b/pkg/contexts/ocm/signing/options_test.go index 5d0fd8b9d6..dadcc1949d 100644 --- a/pkg/contexts/ocm/signing/options_test.go +++ b/pkg/contexts/ocm/signing/options_test.go @@ -16,33 +16,47 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm" "github.com/open-component-model/ocm/pkg/signing" "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) -const NAME = "test" +const NAME = "mandelsoft" var _ = Describe("options", func() { + defer GinkgoRecover() + capriv, capub, err := rsa.Handler{}.CreateKeyPair() Expect(err).To(Succeed()) - subject := pkix.Name{ - CommonName: "ca-authority", + spec := &signutils.Specification{ + RootCAs: nil, + IsCA: true, + PublicKey: capub, + CAPrivateKey: capriv, + CAChain: nil, + Subject: pkix.Name{ + CommonName: "ca-authority", + }, + Usages: signutils.Usages{x509.ExtKeyUsageCodeSigning}, + Validity: 10 * time.Hour, + NotBefore: nil, } - caData, err := signing.CreateCertificate(subject, nil, 10*time.Hour, capub, nil, capriv, true) - Expect(err).To(Succeed()) - ca, err := x509.ParseCertificate(caData) + + ca, _, err := signutils.CreateCertificate(spec) Expect(err).To(Succeed()) priv, pub, err := rsa.Handler{}.CreateKeyPair() Expect(err).To(Succeed()) - subject = pkix.Name{ - CommonName: "mandelsoft", + spec.Subject = pkix.Name{ + CommonName: NAME, StreetAddress: []string{"some street 21"}, } - certData, err := signing.CreateCertificate(subject, nil, 10*time.Hour, pub, ca, capriv, false) - Expect(err).To(Succeed()) + spec.RootCAs = ca + spec.CAChain = ca + spec.PublicKey = pub + spec.IsCA = false - cert, err := x509.ParseCertificate(certData) + cert, _, err := signutils.CreateCertificate(spec) Expect(err).To(Succeed()) pool := x509.NewCertPool() diff --git a/pkg/contexts/ocm/signing/signing_test.go b/pkg/contexts/ocm/signing/signing_test.go index d3856f0b19..0b23a785ef 100644 --- a/pkg/contexts/ocm/signing/signing_test.go +++ b/pkg/contexts/ocm/signing/signing_test.go @@ -5,7 +5,9 @@ package signing_test import ( + "crypto/x509/pkix" "fmt" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -28,6 +30,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" tenv "github.com/open-component-model/ocm/pkg/env" @@ -36,6 +39,7 @@ import ( "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" "github.com/open-component-model/ocm/pkg/signing/hasher/sha512" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) var DefaultContext = ocm.New() @@ -677,7 +681,7 @@ applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1] resolver := ocm.NewCompoundResolver(src) - log := SignComponent(resolver, COMPONENTD, D_COMPD, DigestMode(c.Mode())) + log := SignComponent(resolver, SIGNATURE, COMPONENTD, D_COMPD, DigestMode(c.Mode())) Expect(log).To(StringEqualTrimmedWithContext(` applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... @@ -728,7 +732,7 @@ applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1] Expect(len(cvb.GetDescriptor().Signatures)).To(Equal(0)) c.Check2Ref(cvb, "ref", D_COMPA) - VerifyComponent(src, COMPONENTD, D_COMPD) + VerifyComponent(src, SIGNATURE, COMPONENTD, D_COMPD) }, Entry(DIGESTMODE_TOP, &EntryTop{}), Entry(DIGESTMODE_LOCAL, &EntryLocal{}), @@ -755,7 +759,7 @@ applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1] resolver := ocm.NewCompoundResolver(src) - log := SignComponent(resolver, COMPONENTB, subst["D_COMPB_X"], DigestMode(DIGESTMODE_TOP), HashByAlgo(sha512.Algorithm)) + log := SignComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"], DigestMode(DIGESTMODE_TOP), HashByAlgo(sha512.Algorithm)) Expect(log).To(StringEqualTrimmedWithContext(` applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1]... no digest found for "github.com/mandelsoft/test:v1" @@ -765,8 +769,8 @@ applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1] resource 0: "name"="data_b": digest ${HASH}:${D_DATAB_X}[genericBlobDigest/v1] `, MergeSubst(localDigests, subst))) - VerifyComponent(resolver, COMPONENTB, subst["D_COMPB_X"]) - log = SignComponent(resolver, COMPONENTD, subst["D_COMPD_X"], DigestMode(DIGESTMODE_TOP)) + VerifyComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"]) + log = SignComponent(resolver, SIGNATURE, COMPONENTD, subst["D_COMPD_X"], DigestMode(DIGESTMODE_TOP)) Expect(log).To(StringEqualTrimmedWithContext(` applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... @@ -811,7 +815,7 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] //////// - VerifyComponent(src, COMPONENTD, subst["D_COMPD_X"]) + VerifyComponent(src, SIGNATURE, COMPONENTD, subst["D_COMPD_X"]) }), Entry("legacy", Substitutions{ "HASH": "SHA-512", @@ -848,7 +852,7 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] resolver := ocm.NewCompoundResolver(src) fmt.Printf("SIGN D\n") - log := SignComponent(resolver, COMPONENTD, digestD, DigestMode(DIGESTMODE_TOP)) + log := SignComponent(resolver, SIGNATURE, COMPONENTD, digestD, DigestMode(DIGESTMODE_TOP)) Expect(log).To(StringEqualTrimmedWithContext(` applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... @@ -869,9 +873,9 @@ applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1] `, MergeSubst(localDigests, subst))) fmt.Printf("SIGN B\n") - SignComponent(resolver, COMPONENTB, subst["D_COMPB_X"], HashByAlgo(sha512.Algorithm), DigestMode(DIGESTMODE_TOP)) + SignComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"], HashByAlgo(sha512.Algorithm), DigestMode(DIGESTMODE_TOP)) fmt.Printf("VERIFY B\n") - VerifyComponent(resolver, COMPONENTB, subst["D_COMPB_X"]) + VerifyComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"]) Defer(arch.Finalize) } @@ -900,7 +904,7 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] //////// fmt.Printf("VERIFY D\n") - VerifyComponent(src, COMPONENTD, digestD) + VerifyComponent(src, SIGNATURE, COMPONENTD, digestD) }, Entry("legacy", Substitutions{ "D_COMPB_X": D_COMPB512, @@ -924,8 +928,8 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] resolver := ocm.NewCompoundResolver(src) fmt.Printf("SIGN B\n") - _ = SignComponent(resolver, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_LOCAL)) - VerifyComponent(src, COMPONENTB, D_COMPB) + _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_LOCAL)) + VerifyComponent(src, SIGNATURE, COMPONENTB, D_COMPB) Check(finalizer.Finalize) } { // check mode @@ -946,8 +950,8 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] resolver := ocm.NewCompoundResolver(src) fmt.Printf("RESIGN B\n") - _ = SignComponent(resolver, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_TOP), SignatureName(SIGNATURE2, true)) - VerifyComponent(src, COMPONENTB, D_COMPB, SignatureName(SIGNATURE2, true)) + _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_TOP), SignatureName(SIGNATURE2, true)) + VerifyComponent(src, SIGNATURE, COMPONENTB, D_COMPB, SignatureName(SIGNATURE2, true)) Check(finalizer.Finalize) } { // check mode @@ -984,8 +988,8 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] resolver := ocm.NewCompoundResolver(src) fmt.Printf("SIGN B\n") - _ = SignComponent(resolver, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_TOP), SignatureName(SIGNATURE2, true)) - VerifyComponent(src, COMPONENTB, D_COMPB, SignatureName(SIGNATURE2, true)) + _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_TOP), SignatureName(SIGNATURE2, true)) + VerifyComponent(src, SIGNATURE, COMPONENTB, D_COMPB, SignatureName(SIGNATURE2, true)) Check(finalizer.Finalize) } { // check mode @@ -1006,8 +1010,8 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] resolver := ocm.NewCompoundResolver(src) fmt.Printf("SIGN D\n") - _ = SignComponent(resolver, COMPONENTD, D_COMPD, Recursive(), DigestMode(DIGESTMODE_LOCAL)) - VerifyComponent(src, COMPONENTD, D_COMPD) + _ = SignComponent(resolver, SIGNATURE, COMPONENTD, D_COMPD, Recursive(), DigestMode(DIGESTMODE_LOCAL)) + VerifyComponent(src, SIGNATURE, COMPONENTD, D_COMPD) Check(finalizer.Finalize) } { // check mode @@ -1090,6 +1094,146 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] _ = buf }) }) + + Context("keyless verification", func() { + ca, capriv := Must2(rsa.CreateRootCertificate(signutils.CommonName("ca-authority"), 10*time.Hour)) + intercert, interpem, interpriv := Must3(rsa.CreateSigningCertificate(signutils.CommonName("acme.org"), ca, ca, capriv, 5*time.Hour, true)) + certIssuer := &pkix.Name{ + CommonName: PROVIDER, + Country: []string{"DE", "US"}, + Locality: []string{"Walldorf d"}, + StreetAddress: []string{"x y"}, + PostalCode: []string{"69169"}, + Province: []string{"BW"}, + } + cert, pemBytes, priv := Must3(rsa.CreateSigningCertificate(certIssuer, interpem, ca, interpriv, time.Hour)) + + certs := Must(signutils.GetCertificateChain(pemBytes, false)) + Expect(len(certs)).To(Equal(3)) + + ctx := ocm.DefaultContext() + + var cv ocm.ComponentVersionAccess + + BeforeEach(func() { + cv = composition.NewComponentVersion(ctx, COMPONENTA, VERSION) + }) + + It("is consistent", func() { + MustBeSuccessful(signutils.VerifyCertificate(intercert, interpem, ca, nil)) + MustBeSuccessful(signutils.VerifyCertificate(cert, pemBytes, ca, nil)) + }) + + It("signs with certificate and default issuer", func() { + digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" + res := ocm.NewDedicatedResolver(cv) + + buf := SignComponent(res, PROVIDER, COMPONENTA, digest, PrivateKey(PROVIDER, priv), PublicKey(PROVIDER, pemBytes), RootCertificates(ca)) + Expect(buf).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... +`)) + + i := cv.GetDescriptor().GetSignatureIndex(PROVIDER) + Expect(i).To(BeNumerically(">=", 0)) + sig := cv.GetDescriptor().Signatures[i].Signature + Expect(sig.MediaType).To(Equal(signutils.MediaTypePEM)) + _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Value))) + Expect(algo).To(Equal(rsa.Algorithm)) + Expect(len(chain)).To(Equal(3)) + + VerifyComponent(res, PROVIDER, COMPONENTA, digest, RootCertificates(ca)) + }) + + It("signs with certificate and explicit CN issuer", func() { + digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" + res := ocm.NewDedicatedResolver(cv) + + buf := SignComponent(res, SIGNATURE, COMPONENTA, digest, PrivateKey(SIGNATURE, priv), PublicKey(SIGNATURE, pemBytes), RootCertificates(ca), Issuer(PROVIDER)) + Expect(buf).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... +`)) + + i := cv.GetDescriptor().GetSignatureIndex(SIGNATURE) + Expect(i).To(BeNumerically(">=", 0)) + sig := cv.GetDescriptor().Signatures[i].Signature + Expect(sig.MediaType).To(Equal(signutils.MediaTypePEM)) + _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Value))) + Expect(algo).To(Equal(rsa.Algorithm)) + Expect(len(chain)).To(Equal(3)) + + VerifyComponent(res, SIGNATURE, COMPONENTA, digest, RootCertificates(ca), Issuer(PROVIDER)) + + FailVerifyComponent(res, SIGNATURE, COMPONENTA, digest, + `github.com/mandelsoft/test:v1: public key from signature: public key certificate: issuer mismatch in public key certificate: common name "mandelsoft" is invalid`, + RootCertificates(ca)) + }) + + It("signs with certificate and issuer", func() { + digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" + res := ocm.NewDedicatedResolver(cv) + issuer := &pkix.Name{ + CommonName: PROVIDER, + Country: []string{"DE"}, + } + + buf := SignComponent(res, SIGNATURE, COMPONENTA, digest, PrivateKey(SIGNATURE, priv), PublicKey(SIGNATURE, pemBytes), RootCertificates(ca), PKIXIssuer(*issuer)) + Expect(buf).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... +`)) + + i := cv.GetDescriptor().GetSignatureIndex(SIGNATURE) + Expect(i).To(BeNumerically(">=", 0)) + sig := cv.GetDescriptor().Signatures[i].Signature + Expect(sig.MediaType).To(Equal(signutils.MediaTypePEM)) + _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Value))) + Expect(algo).To(Equal(rsa.Algorithm)) + Expect(len(chain)).To(Equal(3)) + dn := Must(signutils.ParseDN(sig.Issuer)) + Expect(dn).To(Equal(certIssuer)) + + VerifyComponent(res, SIGNATURE, COMPONENTA, digest, RootCertificates(ca), PKIXIssuer(*issuer)) + + issuer.Country = []string{"XX"} + FailVerifyComponent(res, SIGNATURE, COMPONENTA, digest, + `github.com/mandelsoft/test:v1: public key from signature: public key certificate: issuer mismatch in public key certificate: country "XX" not found`, + RootCertificates(ca), PKIXIssuer(*issuer)) + }) + + It("signs with certificate, issuer and tsa", func() { + digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" + res := ocm.NewDedicatedResolver(cv) + issuer := &pkix.Name{ + CommonName: "mandelsoft", + Country: []string{"DE"}, + } + + buf := SignComponent(res, SIGNATURE, COMPONENTA, digest, PrivateKey(SIGNATURE, priv), PublicKey(SIGNATURE, pemBytes), RootCertificates(ca), PKIXIssuer(*issuer), UseTSA()) + Expect(buf).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... +`)) + + i := cv.GetDescriptor().GetSignatureIndex(SIGNATURE) + Expect(i).To(BeNumerically(">=", 0)) + sig := cv.GetDescriptor().Signatures[i] + Expect(sig.Signature.MediaType).To(Equal(signutils.MediaTypePEM)) + _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Signature.Value))) + Expect(algo).To(Equal(rsa.Algorithm)) + Expect(len(chain)).To(Equal(3)) + dn := Must(signutils.ParseDN(sig.Signature.Issuer)) + Expect(dn).To(Equal(certIssuer)) + + Expect(sig.Timestamp).NotTo(BeNil()) + Expect(sig.Timestamp.Value).NotTo(Equal("")) + Expect(sig.Timestamp.Time).NotTo(BeNil()) + Expect(time.Now().Sub(sig.Timestamp.Time.Time()).Minutes()).To(BeNumerically("<", 2)) + VerifyComponent(res, SIGNATURE, COMPONENTA, digest, RootCertificates(ca), PKIXIssuer(*issuer)) + + issuer.Country = []string{"XX"} + FailVerifyComponent(res, SIGNATURE, COMPONENTA, digest, + `github.com/mandelsoft/test:v1: public key from signature: public key certificate: issuer mismatch in public key certificate: country "XX" not found`, + RootCertificates(ca), PKIXIssuer(*issuer)) + }) + }) }) func HashComponent(resolver ocm.ComponentVersionResolver, name string, digest string, other ...Option) string { @@ -1126,13 +1270,13 @@ func VerifyHashes(resolver ocm.ComponentVersionResolver, name string, digest str ExpectWithOffset(1, dig.Value).To(Equal(digest)) } -func SignComponent(resolver ocm.ComponentVersionResolver, name string, digest string, other ...Option) string { +func SignComponent(resolver ocm.ComponentVersionResolver, signame, name string, digest string, other ...Option) string { cv, err := resolver.LookupComponentVersion(name, VERSION) Expect(err).To(Succeed()) defer cv.Close() opts := NewOptions( - Sign(signingattr.Get(cv.GetContext()).GetSigner(SIGN_ALGO), SIGNATURE), + Sign(signingattr.Get(cv.GetContext()).GetSigner(SIGN_ALGO), signame), Resolver(resolver), VerifyDigests(), ) @@ -1146,13 +1290,13 @@ func SignComponent(resolver ocm.ComponentVersionResolver, name string, digest st return buf.String() } -func VerifyComponent(resolver ocm.ComponentVersionResolver, name string, digest string, other ...Option) { +func VerifyComponent(resolver ocm.ComponentVersionResolver, signame, name string, digest string, other ...Option) { cv, err := resolver.LookupComponentVersion(name, VERSION) ExpectWithOffset(1, err).To(Succeed()) defer cv.Close() opts := NewOptions( - VerifySignature(SIGNATURE), + VerifySignature(signame), Resolver(resolver), VerifyDigests(), ) @@ -1163,6 +1307,22 @@ func VerifyComponent(resolver ocm.ComponentVersionResolver, name string, digest ExpectWithOffset(1, dig.Value).To(Equal(digest)) } +func FailVerifyComponent(resolver ocm.ComponentVersionResolver, signame, name string, digest string, msg string, other ...Option) { + cv, err := resolver.LookupComponentVersion(name, VERSION) + ExpectWithOffset(1, err).To(Succeed()) + defer cv.Close() + + opts := NewOptions( + VerifySignature(signame), + Resolver(resolver), + VerifyDigests(), + ) + opts.Eval(other...) + ExpectWithOffset(1, opts.Complete(cv.GetContext())).To(Succeed()) + _, err = Apply(nil, nil, cv, opts) + ExpectWithOffset(1, err).To(MatchError(msg)) +} + func Check(f func() error) { ExpectWithOffset(1, f()).To(Succeed()) } diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/config.go b/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/config.go index 3fa7f94555..15e2a7ba4c 100644 --- a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/config.go +++ b/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/config.go @@ -8,11 +8,13 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" ) -func NewConfig() *Config { - return &Config{} +func NewConfig(fields ...string) *Config { + return &Config{IgnoredFields: fields} } -type Config struct{} +type Config struct { + IgnoredFields []string `json:"ignoredFields,omitempty"` +} var _ hpi.Config = (*Config)(nil) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/handler.go b/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/handler.go index 82212be7b4..e0f38f7cc4 100644 --- a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/handler.go +++ b/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/handler.go @@ -39,7 +39,7 @@ func merge(ctx hpi.Context, c *Config, lv Value, tv *Value) (bool, error) { outer: for _, le := range lv { for _, te := range *tv { - if reflect.DeepEqual(le, te) { + if equal(c, le, te) { continue outer } } @@ -48,3 +48,20 @@ outer: } return modified, nil } + +func equal(c *Config, le, te Entry) bool { + if c == nil || len(c.IgnoredFields) == 0 { + return reflect.DeepEqual(le, te) + } + + if lm, ok := le.(map[string]interface{}); ok { + if tm, ok := te.(map[string]interface{}); ok { + for _, n := range c.IgnoredFields { + delete(lm, n) + delete(tm, n) + } + return reflect.DeepEqual(lm, tm) + } + } + return reflect.DeepEqual(le, te) +} diff --git a/pkg/env/builder/rsa_keypair.go b/pkg/env/builder/rsa_keypair.go index 6216d5d2d2..379b1a4c61 100644 --- a/pkg/env/builder/rsa_keypair.go +++ b/pkg/env/builder/rsa_keypair.go @@ -8,8 +8,8 @@ import ( "github.com/mandelsoft/filepath/pkg/filepath" "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/signing" "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "github.com/open-component-model/ocm/pkg/signing/signutils" "github.com/open-component-model/ocm/pkg/utils" ) @@ -31,7 +31,7 @@ func (b *Builder) ReadRSAKeyPair(name, path string) { if ok, _ := b.Exists(filepath.Join(path, "rsa.pub")); ok { pubbytes, err := b.ReadFile(filepath.Join(path, "rsa.pub")) b.failOn(err) - pub, err := signing.ParsePublicKey(pubbytes) + pub, err := signutils.ParsePublicKey(pubbytes) b.failOn(err) reg.RegisterPublicKey(name, pub) pubfound = true @@ -39,7 +39,7 @@ func (b *Builder) ReadRSAKeyPair(name, path string) { if ok, _ := b.Exists(filepath.Join(path, "rsa.priv")); ok { privbytes, err := b.ReadFile(filepath.Join(path, "rsa.priv")) b.failOn(err) - priv, err := signing.ParsePrivateKey(privbytes) + priv, err := signutils.ParsePrivateKey(privbytes) b.failOn(err) reg.RegisterPrivateKey(name, priv) if !pubfound { diff --git a/pkg/env/keypair.go b/pkg/env/keypair.go index 4ce21f597a..5354039001 100644 --- a/pkg/env/keypair.go +++ b/pkg/env/keypair.go @@ -8,8 +8,8 @@ import ( "github.com/mandelsoft/filepath/pkg/filepath" "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/signing" "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "github.com/open-component-model/ocm/pkg/signing/signutils" "github.com/open-component-model/ocm/pkg/utils" ) @@ -30,7 +30,7 @@ func (e *Environment) ReadRSAKeyPair(name, path string) { if ok, _ := e.Exists(filepath.Join(path, "rsa.pub")); ok { pubbytes, err := e.ReadFile(filepath.Join(path, "rsa.pub")) e.failOn(err) - pub, err := signing.ParsePublicKey(pubbytes) + pub, err := signutils.ParsePublicKey(pubbytes) e.failOn(err) reg.RegisterPublicKey(name, pub) pubfound = true @@ -38,7 +38,7 @@ func (e *Environment) ReadRSAKeyPair(name, path string) { if ok, _ := e.Exists(filepath.Join(path, "rsa.priv")); ok { privbytes, err := e.ReadFile(filepath.Join(path, "rsa.priv")) e.failOn(err) - priv, err := signing.ParsePrivateKey(privbytes) + priv, err := signutils.ParsePrivateKey(privbytes) e.failOn(err) reg.RegisterPrivateKey(name, priv) if !pubfound { diff --git a/pkg/signing/cert.go b/pkg/signing/cert.go index 7ea98eace9..de1573ada7 100644 --- a/pkg/signing/cert.go +++ b/pkg/signing/cert.go @@ -5,126 +5,42 @@ package signing import ( - "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "fmt" - "math/big" - "net" - "runtime" "time" - parse "github.com/mandelsoft/spiff/dynaml/x509" - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/errors" - "github.com/open-component-model/ocm/pkg/utils" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) -func GetCertificate(data interface{}) (*x509.Certificate, error) { - switch k := data.(type) { - case []byte: - return ParseCertificate(k) - case *x509.Certificate: - return k, nil - default: - return nil, fmt.Errorf("unknown type") - } -} - -func ParsePublicKey(data []byte) (interface{}, error) { - return parse.ParsePublicKey(string(data)) -} - -func ParsePrivateKey(data []byte) (interface{}, error) { - return parse.ParsePrivateKey(string(data)) +func VerifyCert(intermediate signutils.GenericCertificateChain, root signutils.GenericCertificatePool, name string, cert *x509.Certificate) error { + return VerifyCertDN(intermediate, root, signutils.CommonName(name), cert) } -func ParseCertificate(data []byte) (*x509.Certificate, error) { - return parse.ParseCertificate(string(data)) -} - -func IntermediatePool(pemfile string, fss ...vfs.FileSystem) (*x509.CertPool, error) { - if pemfile == "" { - return nil, nil - } - fs := utils.FileSystem(fss...) - pemdata, err := utils.ReadFile(pemfile, fs) +func VerifyCertDN(intermediate signutils.GenericCertificateChain, root signutils.GenericCertificatePool, name *pkix.Name, cert *x509.Certificate) error { + rootPool, err := signutils.GetCertPool(root, false) if err != nil { - return nil, errors.Wrapf(err, "cannot read cert pem file %q", pemfile) - } - pool := x509.NewCertPool() - ok := pool.AppendCertsFromPEM(pemdata) - if !ok { - return nil, errors.Newf("cannot add cert pem file to cert pool") - } - return pool, err -} - -func BaseRootPool() (*x509.CertPool, error) { - pool := x509.NewCertPool() - sys, err := x509.SystemCertPool() - if err != nil { - if runtime.GOOS != "windows" { - return nil, errors.Wrapf(err, "cannot get system cert pool") - } - } else { - pool = sys + return err } - return pool, nil -} - -func RootPool(pemfile string, useOS bool, fss ...vfs.FileSystem) (*x509.CertPool, error) { - fs := utils.FileSystem(fss...) - pemdata, err := utils.ReadFile(pemfile, fs) + interPool, err := signutils.GetCertPool(intermediate, false) if err != nil { - return nil, errors.Wrapf(err, "cannot read cert pem file %q", pemfile) - } - pool := x509.NewCertPool() - if useOS { - sys, err := x509.SystemCertPool() - if err != nil { - if runtime.GOOS != "windows" { - return nil, errors.Wrapf(err, "cannot get system cert pool") - } - } else { - pool = sys - } - } - /* - cert.RawSubject = subjectSeq - subjectRDNs, err := parseName(subjectSeq) - if err != nil { - return nil, err - } - cert.Subject.FillFromRDNSequence(subjectRDNs) - */ - ok := pool.AppendCertsFromPEM(pemdata) - if !ok { - return nil, errors.Newf("cannot add cert pem file to cert pool") + return err } - return pool, err -} - -func VerifyCert(intermediate, root *x509.CertPool, cn string, cert *x509.Certificate) error { opts := x509.VerifyOptions{ - DNSName: cn, - Intermediates: intermediate, - Roots: root, - CurrentTime: time.Now(), + Intermediates: interPool, + Roots: rootPool, + CurrentTime: cert.NotBefore, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, } - _, err := cert.Verify(opts) + _, err = cert.Verify(opts) if err != nil { - if cn != "" { - opts.DNSName = "" - _, err2 := cert.Verify(opts) - if err2 == nil && cert.Subject.CommonName == cn { - return nil - } - } return err } + if name != nil { + if err := signutils.MatchDN(cert.Subject, *name); err != nil { + return err + } + } if cert.KeyUsage&x509.KeyUsageDigitalSignature != 0 { return nil } @@ -136,64 +52,23 @@ func VerifyCert(intermediate, root *x509.CertPool, cn string, cert *x509.Certifi return errors.ErrNotSupported("codesign", "", "certificate") } -func CreateCertificate(subject pkix.Name, validFrom *time.Time, validity time.Duration, - pub interface{}, ca *x509.Certificate, priv interface{}, isCA bool, names ...string, +// Deprecated: use signutils.CreateCertificate. +func CreateCertificate(subject pkix.Name, validFrom *time.Time, + validity time.Duration, pub interface{}, + ca *x509.Certificate, priv interface{}, isCA bool, names ...string, ) ([]byte, error) { - var notBefore time.Time - - if validFrom == nil { - notBefore = time.Now() - } else { - notBefore = *validFrom - } - if validity == 0 { - validity = 24 * 365 * time.Hour - } - notAfter := notBefore.Add(validity) - - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return nil, errors.Wrapf(err, "failed to generate serial number") - } - - template := &x509.Certificate{ - SerialNumber: serialNumber, + spec := &signutils.Specification{ + RootCAs: ca, + IsCA: isCA, + PublicKey: pub, + CAPrivateKey: priv, + CAChain: ca, Subject: subject, - NotBefore: notBefore, - NotAfter: notAfter, - - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, - BasicConstraintsValid: true, - } - - if ca == nil { - ca = template - } - - if isCA { - template.IsCA = true - template.KeyUsage |= x509.KeyUsageCertSign - } - - found := false - for _, h := range names { - if h == subject.CommonName { - found = true - } - if ip := net.ParseIP(h); ip != nil { - template.IPAddresses = append(template.IPAddresses, ip) - } else { - template.DNSNames = append(template.DNSNames, h) - } - } - if !found { - template.DNSNames = append(template.DNSNames, subject.CommonName) - } - derBytes, err := x509.CreateCertificate(rand.Reader, template, ca, pub, priv) - if err != nil { - return nil, errors.Wrapf(err, "Failed to create certificate") + Usages: signutils.Usages{x509.ExtKeyUsageCodeSigning}, + Validity: validity, + NotBefore: validFrom, + Hosts: names, } - return derBytes, err + _, data, err := signutils.CreateCertificate(spec) + return data, err } diff --git a/pkg/signing/cert_test.go b/pkg/signing/cert_test.go index 9175439dd3..e2875fa8a2 100644 --- a/pkg/signing/cert_test.go +++ b/pkg/signing/cert_test.go @@ -14,9 +14,32 @@ import ( "github.com/open-component-model/ocm/pkg/signing" "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) +// CreateCertificate cretes a pem encoded certificate. +func CreateCertificate(subject pkix.Name, validFrom *time.Time, validity time.Duration, + pub interface{}, ca *x509.Certificate, priv interface{}, isCA bool, names ...string, +) ([]byte, error) { + + spec := &signutils.Specification{ + RootCAs: ca, + IsCA: isCA, + PublicKey: pub, + CAPrivateKey: priv, + CAChain: ca, + Subject: subject, + Usages: signutils.Usages{x509.ExtKeyUsageCodeSigning}, + Validity: validity, + NotBefore: validFrom, + Hosts: names, + } + _, data, err := signutils.CreateCertificate(spec) + return data, err +} + var _ = Describe("normalization", func() { + defer GinkgoRecover() capriv, capub, err := rsa.Handler{}.CreateKeyPair() Expect(err).To(Succeed()) @@ -24,9 +47,9 @@ var _ = Describe("normalization", func() { subject := pkix.Name{ CommonName: "ca-authority", } - caData, err := signing.CreateCertificate(subject, nil, 10*time.Hour, capub, nil, capriv, true) + caData, err := CreateCertificate(subject, nil, 10*time.Hour, capub, nil, capriv, true) Expect(err).To(Succeed()) - ca, err := x509.ParseCertificate(caData) + ca, err := signutils.ParseCertificate(caData) Expect(err).To(Succeed()) priv, pub, err := rsa.Handler{}.CreateKeyPair() @@ -38,10 +61,11 @@ var _ = Describe("normalization", func() { } Context("foreignly signed", func() { - certData, err := signing.CreateCertificate(subject, nil, 10*time.Hour, pub, ca, capriv, false) + + certData, err := CreateCertificate(subject, nil, 10*time.Hour, pub, ca, capriv, false) Expect(err).To(Succeed()) - cert, err := x509.ParseCertificate(certData) + cert, err := signutils.ParseCertificate(certData) Expect(err).To(Succeed()) pool := x509.NewCertPool() @@ -61,10 +85,10 @@ var _ = Describe("normalization", func() { }) }) Context("self signed", func() { - certData, err := signing.CreateCertificate(subject, nil, 10*time.Hour, pub, nil, priv, false) + certData, err := CreateCertificate(subject, nil, 10*time.Hour, pub, nil, priv, false) Expect(err).To(Succeed()) - cert, err := x509.ParseCertificate(certData) + cert, err := signutils.ParseCertificate(certData) Expect(err).To(Succeed()) pool := x509.NewCertPool() diff --git a/pkg/signing/deprecated.go b/pkg/signing/deprecated.go new file mode 100644 index 0000000000..ccfadd1a5c --- /dev/null +++ b/pkg/signing/deprecated.go @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package signing + +import ( + "crypto/x509" + + parse "github.com/mandelsoft/spiff/dynaml/x509" + + "github.com/open-component-model/ocm/pkg/signing/signutils" +) + +// Deprecated: use signutils.GetCertificate. +func GetCertificate(in interface{}) (*x509.Certificate, error) { + c, _, err := signutils.GetCertificate(in, false) + return c, err +} + +// Deprecated: use signutils.ParsePublicKey. +func ParsePublicKey(data []byte) (interface{}, error) { + return parse.ParsePublicKey(string(data)) +} + +// Deprecated: use signutils.ParsePrivateKey. +func ParsePrivateKey(data []byte) (interface{}, error) { + return parse.ParsePrivateKey(string(data)) +} + +// Deprecated: use signutils.SystemCertPool. +func BaseRootPool() (*x509.CertPool, error) { + return signutils.SystemCertPool() +} diff --git a/pkg/signing/handlers/rsa-signingservice/README.md b/pkg/signing/handlers/rsa-signingservice/README.md index d8b719f03d..3cfa045350 100644 --- a/pkg/signing/handlers/rsa-signingservice/README.md +++ b/pkg/signing/handlers/rsa-signingservice/README.md @@ -9,7 +9,7 @@ private key. It must has the field `url` with the desired server address. -The required credentials are taken from the crednetials context +The required credentials are taken from the credentials context using the consumer id `Signingserver.gardener.cloud`. If uses a hostpath matcher using the identity attrutes `scheme`, `hostname`, `port` and `pathprefix` derived from the given server URL. diff --git a/pkg/signing/handlers/rsa-signingservice/client.go b/pkg/signing/handlers/rsa-signingservice/client.go index dfc7b15689..4e2e36937f 100644 --- a/pkg/signing/handlers/rsa-signingservice/client.go +++ b/pkg/signing/handlers/rsa-signingservice/client.go @@ -11,7 +11,6 @@ import ( "crypto/tls" "crypto/x509" "encoding/hex" - "encoding/pem" "fmt" "io" "net/http" @@ -22,7 +21,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) const ( @@ -47,7 +46,7 @@ func NewSigningClient(serverURL string) (*SigningServerSigner, error) { return &signer, nil } -func (signer *SigningServerSigner) Sign(cctx credentials.Context, signatureAlgo string, hashAlgo crypto.Hash, digest string, issuer string, key interface{}) (*signing.Signature, error) { +func (signer *SigningServerSigner) Sign(cctx credentials.Context, signatureAlgo string, hashAlgo crypto.Hash, digest string, sctx signing.SigningContext) (*signing.Signature, error) { decodedHash, err := hex.DecodeString(digest) if err != nil { return nil, fmt.Errorf("unable to hex decode hash: %w", err) @@ -142,32 +141,42 @@ func (signer *SigningServerSigner) Sign(cctx credentials.Context, signatureAlgo return nil, fmt.Errorf("request returned with status code %d: %s", res.StatusCode, string(responseBodyBytes)) } - signaturePemBlocks, err := rsa.GetSignaturePEMBlocks(responseBodyBytes) + signature, algorithm, certs, err := signutils.GetSignatureFromPem(responseBodyBytes) if err != nil { return nil, fmt.Errorf("unable to get signature pem block from response: %w", err) } - - if len(signaturePemBlocks) != 1 { - return nil, fmt.Errorf("expected 1 signature pem block, found %d", len(signaturePemBlocks)) - } - signatureBlock := signaturePemBlocks[0] - - signature := signatureBlock.Bytes if len(signature) == 0 { return nil, errors.New("invalid response: signature block doesn't contain signature") } - algorithm := signatureBlock.Headers[rsa.SignaturePEMBlockAlgorithmHeader] if algorithm == "" { - return nil, fmt.Errorf("invalid response: %s header is empty: %s", rsa.SignaturePEMBlockAlgorithmHeader, string(responseBodyBytes)) + return nil, fmt.Errorf("invalid response: %s header is empty: %s", signutils.SignaturePEMBlockAlgorithmHeader, string(responseBodyBytes)) } - encodedSignature := pem.EncodeToMemory(signatureBlock) + encodedSignature := responseBodyBytes + + issuer := sctx.GetIssuer() + var iss string + if issuer != nil { + if len(certs) == 0 { + return nil, errors.Newf("certificates missing in signing response") + } + if err := signutils.MatchDN(certs[0].Subject, *issuer); err != nil { + return nil, errors.Wrapf(err, "unexpected issuer in signing response") + } + iss = issuer.String() + } + if len(certs) > 0 { + err = signutils.VerifyCertificate(certs[0], certs, sctx.GetRootCerts(), issuer) + if err != nil { + return nil, err + } + } return &signing.Signature{ Value: string(encodedSignature), MediaType: MediaTypePEM, Algorithm: algorithm, - Issuer: issuer, + Issuer: iss, }, nil } diff --git a/pkg/signing/handlers/rsa-signingservice/handler.go b/pkg/signing/handlers/rsa-signingservice/handler.go index ab2a4816c9..e7f360d862 100644 --- a/pkg/signing/handlers/rsa-signingservice/handler.go +++ b/pkg/signing/handlers/rsa-signingservice/handler.go @@ -5,7 +5,6 @@ package rsa_signingservice import ( - "crypto" "fmt" "github.com/open-component-model/ocm/pkg/contexts/credentials" @@ -42,8 +41,8 @@ func (h Handler) Algorithm() string { return Algorithm } -func (h Handler) Sign(cctx credentials.Context, digest string, hash crypto.Hash, issuer string, key interface{}) (signature *signing.Signature, err error) { - privateKey, err := PrivateKey(key) +func (h Handler) Sign(cctx credentials.Context, digest string, sctx signing.SigningContext) (signature *signing.Signature, err error) { + privateKey, err := PrivateKey(sctx.GetPrivateKey()) if err != nil { return nil, errors.Wrapf(err, "invalid signing server access configuration") } @@ -51,7 +50,7 @@ func (h Handler) Sign(cctx credentials.Context, digest string, hash crypto.Hash, if err != nil { return nil, err } - return server.Sign(cctx, h.Algorithm(), hash, digest, issuer, key) + return server.Sign(cctx, h.Algorithm(), sctx.GetHash(), digest, sctx) } func PrivateKey(k interface{}) (*Key, error) { diff --git a/pkg/signing/handlers/rsa/certhelper.go b/pkg/signing/handlers/rsa/certhelper.go new file mode 100644 index 0000000000..f89d6b797a --- /dev/null +++ b/pkg/signing/handlers/rsa/certhelper.go @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package rsa + +import ( + "crypto/x509" + "crypto/x509/pkix" + "time" + + "github.com/open-component-model/ocm/pkg/signing/signutils" + "github.com/open-component-model/ocm/pkg/utils" +) + +func CreateRootCertificate(sub *pkix.Name, validity time.Duration) (*x509.Certificate, *PrivateKey, error) { + capriv, _, err := Handler{}.CreateKeyPair() + if err != nil { + return nil, nil, err + } + + spec := &signutils.Specification{ + Subject: *sub, + Validity: validity, + CAPrivateKey: capriv, + IsCA: true, + Usages: []interface{}{x509.ExtKeyUsageCodeSigning, x509.KeyUsageDigitalSignature}, + } + + ca, _, err := signutils.CreateCertificate(spec) + return ca, capriv.(*PrivateKey), err +} + +func CreateSigningCertificate(sub *pkix.Name, intermediate signutils.GenericCertificateChain, roots signutils.GenericCertificatePool, capriv signutils.GenericPrivateKey, validity time.Duration, isCA ...bool) (*x509.Certificate, []byte, *PrivateKey, error) { + priv, pub, err := Handler{}.CreateKeyPair() + if err != nil { + return nil, nil, nil, err + } + spec := &signutils.Specification{ + IsCA: utils.Optional(isCA...), + Subject: *sub, + Validity: validity, + RootCAs: roots, + CAChain: intermediate, + CAPrivateKey: capriv, + PublicKey: pub, + Usages: []interface{}{x509.ExtKeyUsageCodeSigning, x509.KeyUsageDigitalSignature}, + } + cert, pemBytes, err := signutils.CreateCertificate(spec) + if err != nil { + return nil, nil, nil, err + } + return cert, pemBytes, priv.(*PrivateKey), nil +} diff --git a/pkg/signing/handlers/rsa/format.go b/pkg/signing/handlers/rsa/format.go index e7b5741441..a518cefe39 100644 --- a/pkg/signing/handlers/rsa/format.go +++ b/pkg/signing/handlers/rsa/format.go @@ -8,17 +8,16 @@ import ( "bytes" "crypto/rsa" "crypto/x509" + "crypto/x509/pkix" "encoding/pem" "fmt" "io" - "golang.org/x/exp/slices" - "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/utils" ) -func GetPublicKey(key interface{}) (*rsa.PublicKey, []string, error) { +func GetPublicKey(key interface{}) (*rsa.PublicKey, *pkix.Name, error) { var err error if data, ok := key.([]byte); ok { key, err = ParseKey(data) @@ -33,11 +32,7 @@ func GetPublicKey(key interface{}) (*rsa.PublicKey, []string, error) { return &k.PublicKey, nil, nil case *x509.Certificate: if p, ok := k.PublicKey.(*rsa.PublicKey); ok { - names := slices.Clone(k.DNSNames) - if k.Issuer.CommonName != "" { - names = append(names, k.Issuer.CommonName) - } - return p, names, nil + return p, &k.Subject, nil } return nil, nil, fmt.Errorf("unknown key public key %T in certificate", k) default: diff --git a/pkg/signing/handlers/rsa/handler.go b/pkg/signing/handlers/rsa/handler.go index 19b7dfdf0f..cd58cc318d 100644 --- a/pkg/signing/handlers/rsa/handler.go +++ b/pkg/signing/handlers/rsa/handler.go @@ -5,16 +5,15 @@ package rsa import ( - "crypto" "crypto/rand" "crypto/rsa" "encoding/hex" - "encoding/pem" "fmt" "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/signing" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) // Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. @@ -23,14 +22,8 @@ const Algorithm = "RSASSA-PKCS1-V1_5" // MediaType defines the media type for a plain RSA signature. const MediaType = "application/vnd.ocm.signature.rsa" -// MediaTypePEM defines the media type for PEM formatted data. -const MediaTypePEM = "application/x-pem-file" - -// SignaturePEMBlockType defines the type of a signature pem block. -const SignaturePEMBlockType = "SIGNATURE" - -// SignaturePEMBlockAlgorithmHeader defines the header in a signature pem block where the signature algorithm is defined. -const SignaturePEMBlockAlgorithmHeader = "Signature Algorithm" +// MediaTypePEM is used if the signature contains the public key certificate chain. +const MediaTypePEM = signutils.MediaTypePEM func init() { signing.DefaultHandlerRegistry().RegisterSigner(Algorithm, Handler{}) @@ -51,8 +44,8 @@ func (h Handler) Algorithm() string { return Algorithm } -func (h Handler) Sign(cctx credentials.Context, digest string, hash crypto.Hash, issuer string, key interface{}) (signature *signing.Signature, err error) { - privateKey, err := GetPrivateKey(key) +func (h Handler) Sign(cctx credentials.Context, digest string, sctx signing.SigningContext) (signature *signing.Signature, err error) { + privateKey, err := GetPrivateKey(sctx.GetPrivateKey()) if err != nil { return nil, errors.Wrapf(err, "invalid rsa private key") } @@ -60,23 +53,55 @@ func (h Handler) Sign(cctx credentials.Context, digest string, hash crypto.Hash, if err != nil { return nil, fmt.Errorf("failed decoding hash to bytes") } - sig, err := rsa.SignPKCS1v15(rand.Reader, privateKey, hash, decodedHash) + sig, err := rsa.SignPKCS1v15(rand.Reader, privateKey, sctx.GetHash(), decodedHash) if err != nil { return nil, fmt.Errorf("failed signing hash, %w", err) } + + media := MediaType + value := hex.EncodeToString(sig) + + var iss string + pub := sctx.GetPublicKey() + if pub != nil { + var pubKey signutils.GenericPublicKey + certs, err := signutils.GetCertificateChain(pub, false) + if err == nil && len(certs) > 0 { + pubKey, _, err = GetPublicKey(certs[0].PublicKey) + if err != nil { + return nil, errors.ErrInvalidWrap(err, "public key") + } + err = signutils.VerifyCertificate(certs[0], certs[1:], sctx.GetRootCerts(), sctx.GetIssuer()) + if err != nil { + return nil, errors.Wrapf(err, "public key certificate") + } + media = MediaTypePEM + value = string(signutils.SignatureBytesToPem(Algorithm, sig, certs...)) + iss = certs[0].Subject.String() + } else { + pubKey, _, err = GetPublicKey(pub) + if err != nil { + return nil, errors.ErrInvalidWrap(err, "public key") + } + } + if !privateKey.PublicKey.Equal(pubKey) { + return nil, fmt.Errorf("invalid public key for private key") + } + } + return &signing.Signature{ - Value: hex.EncodeToString(sig), - MediaType: MediaType, + Value: value, + MediaType: media, Algorithm: Algorithm, - Issuer: issuer, + Issuer: iss, }, nil } // Verify checks the signature, returns an error on verification failure. -func (h Handler) Verify(digest string, hash crypto.Hash, signature *signing.Signature, key interface{}) (err error) { +func (h Handler) Verify(digest string, signature *signing.Signature, sctx signing.SigningContext) (err error) { var signatureBytes []byte - publicKey, names, err := GetPublicKey(key) + publicKey, name, err := GetPublicKey(sctx.GetPublicKey()) if err != nil { return fmt.Errorf("failed to get public key: %w", err) } @@ -87,15 +112,15 @@ func (h Handler) Verify(digest string, hash crypto.Hash, signature *signing.Sign if err != nil { return fmt.Errorf("unable to get signature value: failed decoding hash %s: %w", digest, err) } - case MediaTypePEM: - signaturePemBlocks, err := GetSignaturePEMBlocks([]byte(signature.Value)) + case signutils.MediaTypePEM: + sig, algo, _, err := signutils.GetSignatureFromPem([]byte(signature.Value)) if err != nil { - return fmt.Errorf("unable to get signature pem blocks: %w", err) + return fmt.Errorf("unable to get signature from pem: %w", err) } - if len(signaturePemBlocks) != 1 { - return fmt.Errorf("expected 1 signature pem block, found %d", len(signaturePemBlocks)) + if algo != "" && algo != Algorithm { + return errors.ErrInvalid(signutils.KIND_SIGN_ALGORITHM, algo) } - signatureBytes = signaturePemBlocks[0].Bytes + signatureBytes = sig default: return fmt.Errorf("invalid signature mediaType %s", signature.MediaType) } @@ -105,60 +130,29 @@ func (h Handler) Verify(digest string, hash crypto.Hash, signature *signing.Sign return fmt.Errorf("failed decoding hash %s: %w", digest, err) } - if names != nil { + if name != nil { if signature.Issuer != "" { - found := false - - for _, n := range names { - if n == signature.Issuer { - found = true - break - } + iss, err := signutils.ParseDN(signature.Issuer) + if err != nil { + return errors.Wrapf(err, "signature issuer") } - - if !found { - return fmt.Errorf("issuer %q does not match %v", signature.Issuer, names) + if signutils.MatchDN(*iss, *name) != nil { + return fmt.Errorf("issuer %s does not match %s", signature.Issuer, name) } } } - if err := rsa.VerifyPKCS1v15(publicKey, hash, decodedHash, signatureBytes); err != nil { + if err := rsa.VerifyPKCS1v15(publicKey, sctx.GetHash(), decodedHash, signatureBytes); err != nil { return fmt.Errorf("signature verification failed, %w", err) } return nil } -// GetSignaturePEMBlocks returns all signature pem blocks from a list of pem blocks. -func GetSignaturePEMBlocks(pemData []byte) ([]*pem.Block, error) { - if len(pemData) == 0 { - return []*pem.Block{}, nil - } - - signatureBlocks := []*pem.Block{} - for { - var currentBlock *pem.Block - currentBlock, pemData = pem.Decode(pemData) - if currentBlock == nil && len(pemData) > 0 { - return nil, fmt.Errorf("unable to decode pem block %s", string(pemData)) - } - - if currentBlock.Type == SignaturePEMBlockType { - signatureBlocks = append(signatureBlocks, currentBlock) - } - - if len(pemData) == 0 { - break - } - } - - return signatureBlocks, nil -} - -func (_ Handler) CreateKeyPair() (priv interface{}, pub interface{}, err error) { +func (_ Handler) CreateKeyPair() (priv signutils.GenericPublicKey, pub signutils.GenericPublicKey, err error) { return CreateKeyPair() } -func CreateKeyPair() (priv interface{}, pub interface{}, err error) { +func CreateKeyPair() (priv signutils.GenericPublicKey, pub signutils.GenericPublicKey, err error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, nil, err diff --git a/pkg/signing/handlers/sigstore/handler.go b/pkg/signing/handlers/sigstore/handler.go index 9ba79b1909..98b1f2de8a 100644 --- a/pkg/signing/handlers/sigstore/handler.go +++ b/pkg/signing/handlers/sigstore/handler.go @@ -57,7 +57,8 @@ func (h Handler) Algorithm() string { } // Sign implements the signing functionality. -func (h Handler) Sign(cctx credentials.Context, digest string, hash crypto.Hash, issuer string, key interface{}) (*signing.Signature, error) { +func (h Handler) Sign(cctx credentials.Context, digest string, sctx signing.SigningContext) (*signing.Signature, error) { + hash := sctx.GetHash() // exit immediately if hash alg is not SHA-256, rekor doesn't currently support other hash functions if hash != crypto.SHA256 { return nil, fmt.Errorf("cannot sign using sigstore. rekor only supports SHA-256 digests: %s provided", hash.String()) @@ -176,12 +177,12 @@ func (h Handler) Sign(cctx credentials.Context, digest string, hash crypto.Hash, Value: base64.StdEncoding.EncodeToString(data), MediaType: MediaType, Algorithm: Algorithm, - Issuer: issuer, + Issuer: "", }, nil } // Verify checks the signature, returns an error on verification failure. -func (h Handler) Verify(digest string, hash crypto.Hash, sig *signing.Signature, key interface{}) (err error) { +func (h Handler) Verify(digest string, sig *signing.Signature, sctx signing.SigningContext) (err error) { ctx := context.Background() data, err := base64.StdEncoding.DecodeString(sig.Value) diff --git a/pkg/signing/registry.go b/pkg/signing/registry.go index d9e0a08291..691e665db8 100644 --- a/pkg/signing/registry.go +++ b/pkg/signing/registry.go @@ -5,6 +5,8 @@ package signing import ( + "crypto/x509" + "crypto/x509/pkix" "sort" "sync" @@ -12,16 +14,26 @@ import ( "golang.org/x/exp/slices" "github.com/open-component-model/ocm/pkg/generics" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) +const DEFAULT_TSA_URL = "http://timestamp.digicert.com" + type Registry interface { + RegistryFuncs + + Copy() Registry +} + +type RegistryFuncs interface { HandlerRegistryFuncs KeyRegistryFuncs HandlerRegistryProvider KeyRegistryProvider - Copy() Registry + TSAUrl() string + SetTSAUrl(url string) } type HasherProvider interface { @@ -82,8 +94,16 @@ type KeyRegistryFuncs interface { RegisterPrivateKey(name string, key interface{}) GetPublicKey(name string) interface{} GetPrivateKey(name string) interface{} - HasKeys() bool + + RegisterIssuer(name string, is *pkix.Name) + GetIssuer(name string) *pkix.Name + HasIssuers() bool + + RegisterRootCertificates(in signutils.GenericCertificateChain) error + GetRootCertificates() []*x509.Certificate + GetRootCertPool(system bool) *x509.CertPool + HasRootCertificates() bool } type HandlerRegistryProvider interface { @@ -344,6 +364,8 @@ type keyRegistry struct { parents []KeyRegistry publicKeys map[string]interface{} privateKeys map[string]interface{} + issuers map[string]*pkix.Name + rootCerts []*x509.Certificate } var _ KeyRegistry = (*keyRegistry)(nil) @@ -353,6 +375,7 @@ func NewKeyRegistry(parents ...KeyRegistry) KeyRegistry { parents: slices.Clone(parents), publicKeys: map[string]interface{}{}, privateKeys: map[string]interface{}{}, + issuers: map[string]*pkix.Name{}, } } @@ -432,6 +455,114 @@ func (r *keyRegistry) GetPrivateKey(name string) interface{} { return nil } +func (r *keyRegistry) RegisterIssuer(name string, is *pkix.Name) { + r.lock.Lock() + defer r.lock.Unlock() + r.issuers[name] = is +} + +func (r *keyRegistry) GetIssuer(name string) *pkix.Name { + r.lock.Lock() + defer r.lock.Unlock() + i := r.issuers[name] + if i != nil { + return i + } + for _, p := range r.parents { + i := p.GetIssuer(name) + if i != nil { + return i + } + } + // if not explicitly overwritten, the signature name + // is interpreted as expected distinguished name for the issuer. + dn, err := signutils.ParseDN(name) + if err == nil { + return dn + } + return nil +} + +func (r *keyRegistry) HasIssuers() bool { + r.lock.Lock() + defer r.lock.Unlock() + if len(r.issuers) > 0 { + return true + } + for _, p := range r.parents { + if p == nil { + continue + } + if p.HasIssuers() { + return true + } + } + return false +} + +func (r *keyRegistry) RegisterRootCertificates(in signutils.GenericCertificateChain) error { + r.lock.Lock() + defer r.lock.Unlock() + + certs, err := signutils.GetCertificateChain(in, false) + if err != nil { + return err + } + r.rootCerts = append(r.rootCerts, certs...) + return nil +} + +func (r *keyRegistry) GetRootCertificates() []*x509.Certificate { + r.lock.Lock() + defer r.lock.Unlock() + + certs := slices.Clone(r.rootCerts) + for _, p := range r.parents { + if p != nil { + certs = append(certs, p.GetRootCertificates()...) + } + } + return certs +} + +func (r *keyRegistry) GetRootCertPool(system bool) *x509.CertPool { + var ( + err error + pool *x509.CertPool + ) + + if system { + pool, err = x509.SystemCertPool() + if err != nil { + pool = x509.NewCertPool() + } + } else { + pool = x509.NewCertPool() + } + + for _, c := range r.GetRootCertificates() { + pool.AddCert(c) + } + return pool +} + +func (r *keyRegistry) HasRootCertificates() bool { + r.lock.Lock() + defer r.lock.Unlock() + if len(r.rootCerts) > 0 { + return true + } + for _, p := range r.parents { + if p == nil { + continue + } + if p.HasRootCertificates() { + return true + } + } + return false +} + var defaultKeyRegistry = NewKeyRegistry() func DefaultKeyRegistry() KeyRegistry { @@ -448,6 +579,8 @@ type ( type registry struct { _HandlerRegistry _KeyRegistry + + tsaUrl string } var _ Registry = (*registry)(nil) @@ -471,9 +604,21 @@ func (r *registry) Copy() Registry { return ®istry{ _HandlerRegistry: r.HandlerRegistry().Copy(), _KeyRegistry: r.KeyRegistry().Copy(), + tsaUrl: r.tsaUrl, } } +func (r *registry) TSAUrl() string { + if r.tsaUrl == "" { + return DEFAULT_TSA_URL + } + return r.tsaUrl +} + +func (r *registry) SetTSAUrl(url string) { + r.tsaUrl = url +} + func RegistryWithPreferredKeys(reg Registry, keys KeyRegistry) Registry { if keys == nil { return reg diff --git a/pkg/signing/signing_test.go b/pkg/signing/signing_test.go index 8615f0a99b..a0eeb286e2 100644 --- a/pkg/signing/signing_test.go +++ b/pkg/signing/signing_test.go @@ -5,6 +5,8 @@ package signing_test import ( + "crypto/x509/pkix" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -18,14 +20,16 @@ var registry = signing.DefaultRegistry() const NAME = "testsignature" -var _ = Describe("normalization", func() { +var ISSUER = &pkix.Name{CommonName: "mandelsoft"} + +var _ = Describe("signing", func() { var defaultContext credentials.Context BeforeEach(func() { defaultContext = credentials.New() }) - It("Normalizes struct without excludes", func() { + It("uses rsa signer", func() { hasher := registry.GetHasher(sha256.Algorithm) hash, _ := signing.Hash(hasher.Create(), []byte("test")) @@ -35,14 +39,21 @@ var _ = Describe("normalization", func() { registry.RegisterPublicKey(NAME, pub) registry.RegisterPrivateKey(NAME, priv) - sig, err := registry.GetSigner(rsa.Algorithm).Sign(defaultContext, hash, hasher.Crypto(), "mandelsoft", registry.GetPrivateKey(NAME)) + sctx := &signing.DefaultSigningContext{ + Hash: hasher.Crypto(), + PrivateKey: registry.GetPrivateKey(NAME), + PublicKey: pub, + RootCerts: nil, + Issuer: ISSUER, + } + sig, err := registry.GetSigner(rsa.Algorithm).Sign(defaultContext, hash, sctx) Expect(err).To(Succeed()) Expect(sig.MediaType).To(Equal(rsa.MediaType)) - Expect(sig.Issuer).To(Equal("mandelsoft")) - Expect(registry.GetVerifier(rsa.Algorithm).Verify(hash, hasher.Crypto(), sig, registry.GetPublicKey(NAME))).To(Succeed()) + sctx.PublicKey = registry.GetPublicKey(NAME) + Expect(registry.GetVerifier(rsa.Algorithm).Verify(hash, sig, sctx)).To(Succeed()) hash = "A" + hash[1:] - Expect(registry.GetVerifier(rsa.Algorithm).Verify(hash, hasher.Crypto(), sig, registry.GetPublicKey(NAME))).To(HaveOccurred()) + Expect(registry.GetVerifier(rsa.Algorithm).Verify(hash, sig, sctx)).To(HaveOccurred()) }) }) diff --git a/pkg/signing/signutils/certs.go b/pkg/signing/signutils/certs.go new file mode 100644 index 0000000000..c650f1a3ae --- /dev/null +++ b/pkg/signing/signutils/certs.go @@ -0,0 +1,263 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package signutils + +import ( + "crypto" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "net" + "reflect" + "time" + + "github.com/modern-go/reflect2" + + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/utils" +) + +type Usages []interface{} + +// Specification specified the context for the certificate creation. +type Specification struct { + // RootCAs is used to verify a certificate chain. + // Self-signed CAs must be added here to be accepted as part + // of a chain. + RootCAs GenericCertificatePool + + // IsCA requests a certificate for a CA. + IsCA bool + + PublicKey GenericPublicKey + + // CAPrivateKey is the private key used for signing. + // It must be the key for the first certificate in the chain + // (if given). + CAPrivateKey GenericPrivateKey + CAChain GenericCertificateChain + + // SkipVerify can be set to true to skip the verification + // of the given certificate chain. + SkipVerify bool + + Subject pkix.Name + Usages Usages + Validity time.Duration + NotBefore *time.Time + + Hosts []string +} + +// CreateCertificate creates a certificate and additionally returns a PEM encoded +// representation. +func CreateCertificate(spec *Specification) (*x509.Certificate, []byte, error) { + var err error + + var rootCerts *x509.CertPool + + if !reflect2.IsNil(spec.RootCAs) { + rootCerts, err = GetCertPool(spec.RootCAs, true) + if err != nil { + return nil, nil, err + } + } + + var pubKey interface{} + if !reflect2.IsNil(spec.PublicKey) { + pubKey, err = GetPublicKey(spec.PublicKey) + if err != nil { + return nil, nil, err + } + } + + var caChain []*x509.Certificate + if !reflect2.IsNil(spec.CAChain) { + caChain, err = GetCertificateChain(spec.CAChain, false) + if err != nil { + return nil, nil, err + } + } + + var caPrivKey interface{} + if !reflect2.IsNil(spec.CAPrivateKey) { + caPrivKey, err = GetPrivateKey(spec.CAPrivateKey) + if err != nil { + return nil, nil, err + } + } + if reflect2.IsNil(caPrivKey) { + return nil, nil, fmt.Errorf("private key required for signing") + } + + if reflect2.IsNil(pubKey) { + pubKey, err = GetPublicKey(caPrivKey) + if err != nil { + return nil, nil, err + } + } + + var notBefore time.Time + if spec.NotBefore == nil { + notBefore = time.Now() + } else { + notBefore, err = GetTime(spec.NotBefore) + if err != nil { + return nil, nil, err + } + } + + if len(caChain) > 0 { + key, ok := caPrivKey.(crypto.Signer) + if !ok { + return nil, nil, errors.Newf("x509: certificate private key does not implement crypto.Signer") + } + if !reflect.DeepEqual(key.Public(), caChain[0].PublicKey) { + return nil, nil, errors.Newf("private key does not match ca certificate") + } + if !spec.SkipVerify { + if rootCerts == nil { + rootCerts, err = x509.SystemCertPool() + if err != nil { + return nil, nil, err + } + } + + intermediates, err := GetCertPool(caChain, false) + if err != nil { + return nil, nil, err + } + + opts := x509.VerifyOptions{ + Intermediates: intermediates, + Roots: rootCerts, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, // x509.ExtKeyUsageCodeSigning ??? + MaxConstraintComparisions: 0, + } + + _, err = caChain[0].Verify(opts) + if err != nil { + return nil, nil, err + } + } + } + + validity := spec.Validity + if validity == 0 { + validity = time.Hour*24 + 365 + } + notAfter := notBefore.Add(validity) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to generate serial number") + } + + template := &x509.Certificate{ + SerialNumber: serialNumber, + Subject: spec.Subject, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: 0, + ExtKeyUsage: []x509.ExtKeyUsage{}, + BasicConstraintsValid: true, + } + + var ca *x509.Certificate + if len(caChain) == 0 { + ca = template // se0fl signed certificate + } else { + ca = caChain[0] + } + + for _, u := range spec.Usages { + k := GetKeyUsage(u) + if k == nil { + return nil, nil, fmt.Errorf("invalid usage key %q", u) + } + k.AddTo(template) + } + + for _, h := range spec.Hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + if spec.IsCA || (template.KeyUsage&x509.KeyUsageCertSign) != 0 { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + } + + derBytes, err := x509.CreateCertificate(rand.Reader, template, ca, pubKey, caPrivKey) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to create certificate") + } + + cert, err := x509.ParseCertificate(derBytes) + if err != nil { + panic("failed to parse generated certificate:" + err.Error()) + } + + pemBytes := CertificateBytesToPem(derBytes) + for _, c := range caChain { + pemBytes = append(pemBytes, CertificateToPem(c)...) + } + return cert, pemBytes, nil +} + +func VerifyCertificate(cert *x509.Certificate, intermediates GenericCertificateChain, rootCerts GenericCertificatePool, name *pkix.Name, ts ...*time.Time) error { + rootPool, err := GetCertPool(rootCerts, false) + if err != nil { + return err + } + interPool, err := GetCertPool(intermediates, false) + if err != nil { + return err + } + timestamp := cert.NotBefore + if ts := utils.Optional(ts...); ts != nil && !ts.IsZero() { + timestamp = *ts + } + opts := x509.VerifyOptions{ + Intermediates: interPool, + Roots: rootPool, + CurrentTime: timestamp, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + MaxConstraintComparisions: 0, + } + + _, err = cert.Verify(opts) + if err != nil { + return err + } + if name != nil { + return errors.Wrapf(MatchDN(cert.Subject, *name), "issuer mismatch in public key certificate") + } + return nil +} + +func CertificateChainToPem(certs []*x509.Certificate) []byte { + var data []byte + for _, c := range certs { + data = append(data, CertificateToPem(c)...) + } + return data +} + +func CertificateToPem(c *x509.Certificate) []byte { + return CertificateBytesToPem(c.Raw) +} + +func CertificateBytesToPem(derBytes []byte) []byte { + return pem.EncodeToMemory(&pem.Block{Type: CertificatePEMBlockType, Bytes: derBytes}) +} diff --git a/pkg/signing/signutils/certs_test.go b/pkg/signing/signutils/certs_test.go new file mode 100644 index 0000000000..8c8c63d4b0 --- /dev/null +++ b/pkg/signing/signutils/certs_test.go @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package signutils_test + +import ( + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/open-component-model/ocm/pkg/signing" + "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "github.com/open-component-model/ocm/pkg/signing/signutils" +) + +var _ = Describe("normalization", func() { + + // root + ca, capriv := Must2(rsa.CreateRootCertificate(signutils.CommonName("ca-authority"), 10*time.Hour)) + + Context("direct", func() { + cert, _, _ := Must3(rsa.CreateSigningCertificate(signutils.CommonName("mandelsoft"), ca, ca, capriv, 1*time.Hour)) + + pool := x509.NewCertPool() + pool.AddCert(ca) + + It("identifies self-signed", func() { + Expect(signutils.IsSelfSigned(ca)).To(BeTrue()) + }) + + It("verifies for issuer", func() { + MustBeSuccessful(signing.VerifyCert(nil, pool, "mandelsoft", cert)) + }) + It("verifies for anonymous", func() { + MustBeSuccessful(signing.VerifyCert(nil, pool, "", cert)) + }) + It("fails for wrong issuer", func() { + MustFailWithMessage(signing.VerifyCert(nil, pool, "x", cert), `common name "mandelsoft" is invalid`) + }) + }) + + Context("chain", func() { + defer GinkgoRecover() + + intercert, interBytes, interpriv := Must3(rsa.CreateSigningCertificate(signutils.CommonName("acme.org"), ca, ca, capriv, 1*time.Hour, true)) + + cert, pemBytes, _ := Must3(rsa.CreateSigningCertificate(&pkix.Name{ + CommonName: "mandelsoft", + Country: []string{"DE", "US"}, + Locality: []string{"Walldorf d"}, + StreetAddress: []string{"x y"}, + PostalCode: []string{"69169"}, + Province: []string{"BW"}, + }, interBytes, ca, interpriv, 1*time.Hour)) + + certs := Must(signutils.GetCertificateChain(pemBytes, false)) + Expect(len(certs)).To(Equal(3)) + + pool := x509.NewCertPool() + pool.AddCert(ca) + + interpool := x509.NewCertPool() + interpool.AddCert(intercert) + + opts := x509.VerifyOptions{ + Intermediates: interpool, + Roots: pool, + CurrentTime: time.Now(), + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + } + + It("identifies non-self-signed", func() { + Expect(signutils.IsSelfSigned(intercert)).To(BeFalse()) + }) + + It("verifies", func() { + fmt.Printf("%s\n", cert.Subject.String()) + MustBeSuccessful(cert.Verify(opts)) + }) + }) +}) diff --git a/pkg/signing/signutils/names.go b/pkg/signing/signutils/names.go new file mode 100644 index 0000000000..205a679d48 --- /dev/null +++ b/pkg/signing/signutils/names.go @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package signutils + +import ( + "crypto/x509/pkix" + "regexp" + "sort" + "strings" + + "github.com/open-component-model/ocm/pkg/errors" +) + +func CommonName(n string) *pkix.Name { + return &pkix.Name{CommonName: n} +} + +var ( + dnRegexp = regexp.MustCompile(`[/;,+]([^=]+)=([^/;,+]+)`) + allDNRegexp = regexp.MustCompile(`^[^=]+=[^/;,+]+([/;,+][^=]+=[^/;,+]+)*$`) +) + +func ParseDN(dn string) (*pkix.Name, error) { + name := pkix.Name{} + + dn = strings.TrimSpace(dn) + if len(dn) == 0 { + return nil, errors.ErrInvalid("distinguished name", dn) + } + if !allDNRegexp.MatchString(dn) { + name.CommonName = dn + } else { + matches := dnRegexp.FindAllStringSubmatch("+"+dn, -1) + for _, match := range matches { + val := match[2] + if val == "" { + continue + } + + switch match[1] { + case "C": + name.Country = append(name.Country, val) + case "O": + name.Organization = append(name.Organization, val) + case "OU": + name.OrganizationalUnit = append(name.OrganizationalUnit, val) + case "L": + name.Locality = append(name.Locality, val) + case "ST": + name.Province = append(name.Province, val) + case "STREET": + name.StreetAddress = append(name.StreetAddress, val) + case "POSTALCODE": + name.PostalCode = append(name.PostalCode, val) + case "SN": + name.SerialNumber = val + case "CN": + name.CommonName = val + default: + return nil, errors.ErrInvalid("attribute", match[1]) + } + } + } + + return &name, nil +} + +func NormalizeDN(dn pkix.Name) string { + sort.Strings(dn.StreetAddress) + sort.Strings(dn.Locality) + sort.Strings(dn.OrganizationalUnit) + sort.Strings(dn.Organization) + sort.Strings(dn.Country) + sort.Strings(dn.Province) + sort.Strings(dn.PostalCode) + return DNAsString(dn) +} + +func DNAsString(dn pkix.Name) string { + s := dn.String() + if len(s) == 3+len(dn.CommonName) { + return dn.CommonName + } + return s +} + +func MatchDN(n pkix.Name, p pkix.Name) error { + if p.CommonName != "" && n.CommonName != p.CommonName { + return errors.ErrInvalid("common name", n.CommonName) + } + if len(p.Country) != 0 { + if err := containsAll("country", n.Country, p.Country); err != nil { + return err + } + } + if len(p.Province) != 0 { + if err := containsAll("province", n.Province, p.Province); err != nil { + return err + } + } + if len(p.Locality) != 0 { + if err := containsAll("locality", n.Locality, p.Locality); err != nil { + return err + } + } + if len(p.PostalCode) != 0 { + if err := containsAll("postal code", n.PostalCode, p.PostalCode); err != nil { + return err + } + } + if len(p.StreetAddress) != 0 { + if err := containsAll("street address", n.StreetAddress, p.StreetAddress); err != nil { + return err + } + } + if len(p.Organization) != 0 { + if err := containsAll("organization", n.Organization, p.Organization); err != nil { + return err + } + } + if len(p.OrganizationalUnit) != 0 { + if err := containsAll("organizational unit", n.OrganizationalUnit, p.OrganizationalUnit); err != nil { + return err + } + } + return nil +} + +func containsAll(key string, n []string, p []string) error { +loop: + for _, ps := range p { + for _, ns := range n { + if ns == ps { + continue loop + } + } + return errors.ErrNotFound(key, ps) + } + return nil +} diff --git a/pkg/signing/signutils/names_test.go b/pkg/signing/signutils/names_test.go new file mode 100644 index 0000000000..b4c33b9e05 --- /dev/null +++ b/pkg/signing/signutils/names_test.go @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package signutils_test + +import ( + "crypto/x509/pkix" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/open-component-model/ocm/pkg/signing/signutils" +) + +var _ = Describe("normalization", func() { + Context("parse", func() { + It("plain", func() { + dn := Must(signutils.ParseDN("mandelsoft")) + Expect(dn.String()).To(Equal("CN=mandelsoft")) + }) + It("single field", func() { + dn := Must(signutils.ParseDN("CN=mandelsoft")) + Expect(dn.String()).To(Equal("CN=mandelsoft")) + }) + It("two fields", func() { + dn := Must(signutils.ParseDN("CN=mandelsoft,C=DE")) + Expect(dn.String()).To(Equal("CN=mandelsoft,C=DE")) + }) + It("three fields", func() { + dn := Must(signutils.ParseDN("CN=mandelsoft,C=DE,ST=BW")) + Expect(dn.String()).To(Equal("CN=mandelsoft,ST=BW,C=DE")) + }) + It("double fields", func() { + dn := Must(signutils.ParseDN("CN=mandelsoft,C=DE+C=US")) + Expect(dn.String()).To(Equal("CN=mandelsoft,C=DE+C=US")) + }) + It("double fields", func() { + dn := Must(signutils.ParseDN("C=DE+C=US,CN=mandelsoft")) + Expect(dn.String()).To(Equal("CN=mandelsoft,C=DE+C=US")) + }) + It("double fields", func() { + dn := Must(signutils.ParseDN("C=DE+C=US,CN=mandelsoft,L=Walldorf,O=mandelsoft")) + Expect(dn.String()).To(Equal("CN=mandelsoft,O=mandelsoft,L=Walldorf,C=DE+C=US")) + }) + }) + + Context("match", func() { + It("complete", func() { + dn := pkix.Name{ + CommonName: "a", + Country: []string{"DE", "US"}, + } + + Expect(signutils.MatchDN(dn, dn)).NotTo(HaveOccurred()) + }) + It("partly", func() { + dn := pkix.Name{ + CommonName: "a", + Country: []string{"DE", "US"}, + } + + p := dn + p.Country = nil + Expect(signutils.MatchDN(dn, p)).NotTo(HaveOccurred()) + }) + It("partly list", func() { + dn := pkix.Name{ + CommonName: "a", + Country: []string{"DE", "US"}, + } + + p := dn + p.Country = []string{"DE"} + Expect(signutils.MatchDN(dn, p)).NotTo(HaveOccurred()) + }) + + It("fails for missing", func() { + dn := pkix.Name{ + CommonName: "a", + Country: []string{"DE", "US"}, + } + + p := dn + p.Country = []string{"EG"} + Expect(signutils.MatchDN(dn, p)).To(MatchError(`country "EG" not found`)) + }) + }) +}) diff --git a/pkg/signing/signutils/signature.go b/pkg/signing/signutils/signature.go new file mode 100644 index 0000000000..e6438613cf --- /dev/null +++ b/pkg/signing/signutils/signature.go @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package signutils + +import ( + "crypto/x509" + "encoding/pem" + "fmt" +) + +// MediaTypePEM defines the media type for PEM formatted signature data. +const MediaTypePEM = "application/x-pem-file" + +// SignaturePEMBlockType defines the type of a signature pem block. +const SignaturePEMBlockType = "SIGNATURE" + +// CertificatePEMBlockType defines the type of a certificate pem block. +const CertificatePEMBlockType = "CERTIFICATE" + +// SignaturePEMBlockAlgorithmHeader defines the header in a signature pem block where the signature algorithm is defined. +const SignaturePEMBlockAlgorithmHeader = "Signature Algorithm" + +// GetSignatureFromPem returns a signature and certificated contained +// in a PEM block list. +func GetSignatureFromPem(pemData []byte) ([]byte, string, []*x509.Certificate, error) { + var signature []byte + var algo string + + if len(pemData) == 0 { + return nil, "", nil, nil + } + + data := pemData + for { + block, rest := pem.Decode(data) + if block == nil { + return nil, "", nil, fmt.Errorf("PEM des not contain signature") + } + if block == nil && len(data) > 0 { + return nil, "", nil, fmt.Errorf("unable to decode pem block %s", string(data)) + } + if block.Type == SignaturePEMBlockType { + signature = block.Bytes + algo = block.Headers[SignaturePEMBlockAlgorithmHeader] + break + } + data = rest + } + + caChain, err := ParseCertificateChain(pemData, true) + if err != nil { + return nil, "", nil, err + } + return signature, algo, caChain, nil +} + +func SignatureBytesToPem(algo string, data []byte, certs ...*x509.Certificate) []byte { + block := &pem.Block{Type: SignaturePEMBlockType, Bytes: data} + if algo != "" { + block.Headers = map[string]string{ + SignaturePEMBlockAlgorithmHeader: algo, + } + } + return append(pem.EncodeToMemory(block), CertificateChainToPem(certs)...) +} diff --git a/cmds/ocm/commands/misccmds/hash/suite_test.go b/pkg/signing/signutils/suite_test.go similarity index 84% rename from cmds/ocm/commands/misccmds/hash/suite_test.go rename to pkg/signing/signutils/suite_test.go index 41978c2157..1cec31790b 100644 --- a/cmds/ocm/commands/misccmds/hash/suite_test.go +++ b/pkg/signing/signutils/suite_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package hash_test +package signutils_test import ( "testing" @@ -13,5 +13,5 @@ import ( func TestConfig(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Sign Hash") + RunSpecs(t, "Signing utils") } diff --git a/pkg/signing/signutils/types.go b/pkg/signing/signutils/types.go new file mode 100644 index 0000000000..6eedf7fb7f --- /dev/null +++ b/pkg/signing/signutils/types.go @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package signutils + +// These types just indicate the intended use case for +// a variable of the dedicated type. +// The values are interpreted by the appropriate GetXXX +// functions, which typically accept various +// kinds of instances: +// - dedicated implementations +// - byte sequences +// - strings +// - other elements or list of elements, which can be mapped appropriately. + +// GenericPublicKey can be everything somebody +// can map to an appropriate PublicKey. +type GenericPublicKey interface{} + +// GenericPrivateKey can be everything somebody +// can map to an appropriate PrivateKey. +type GenericPrivateKey interface{} + +// GenericCertificate can be everything mappable +// by GetCertificate to an appropriate x509.Certificate. +type GenericCertificate interface{} + +// GenericCertificateChain can be everything mappable +// by GetCertificateChain to an appropriate list of x509.Certificates. +// GenericCertificateChain is always a GenericCertificatePool. +type GenericCertificateChain interface{} + +// GenericCertificatePool can be everything mappable +// by GetCertPool to an appropriate x509.CertPool. +type GenericCertificatePool interface{} + +const ( + KIND_HASH_ALGORITHM = "hash algorithm" + KIND_SIGN_ALGORITHM = "signing algorithm" + KIND_NORM_ALGORITHM = "normalization algorithm" + KIND_VERIFY_ALGORITHM = "signature verification algorithm" + KIND_PUBLIC_KEY = "public key" + KIND_PRIVATE_KEY = "private key" + KIND_SIGNATURE = "signature" + KIND_DIGEST = "digest" +) diff --git a/pkg/signing/signutils/utils.go b/pkg/signing/signutils/utils.go new file mode 100644 index 0000000000..dac87429b1 --- /dev/null +++ b/pkg/signing/signutils/utils.go @@ -0,0 +1,514 @@ +package signutils + +import ( + "bytes" + "crypto" + "crypto/dsa" //nolint: staticcheck // yes + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "log" + "os" + "runtime" + "strings" + "time" + + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/modern-go/reflect2" + + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/utils" +) + +func privateKey(block *pem.Block) (interface{}, error) { + x509Encoded := block.Bytes + switch block.Type { + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(x509Encoded) + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(x509Encoded) + default: + return nil, fmt.Errorf("invalid pem block type %q", block.Type) + } +} + +func PemBlockForPrivateKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *ecdsa.PrivateKey: + b, err := x509.MarshalECPrivateKey(k) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) + os.Exit(2) + } + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + log.Fatal("invalid key") + return nil + } +} + +func PemBlockForPublicKey(priv interface{}, gen ...bool) *pem.Block { + switch k := priv.(type) { + case *rsa.PublicKey: + if len(gen) > 0 && gen[0] { + bytes, err := x509.MarshalPKIXPublicKey(k) + if err != nil { + panic(err) + } + return &pem.Block{Type: "PUBLIC KEY", Bytes: bytes} + } + return &pem.Block{Type: "RSA PUBLIC KEY", Bytes: x509.MarshalPKCS1PublicKey(k)} + case *ecdsa.PublicKey: + b, err := x509.MarshalPKIXPublicKey(k) + if err != nil { + return nil + } + return &pem.Block{Type: "ECDSA PUBLIC KEY", Bytes: b} + default: + return nil + } +} + +func ParsePublicKey(data []byte) (interface{}, error) { + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("invalid public key format (expected pem block)") + } + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + pub, err = x509.ParsePKCS1PublicKey(block.Bytes) + if err != nil { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse DER encoded public key") + } + pub = cert.PublicKey + } else { + return pub, nil + } + } + switch pub := pub.(type) { + case *rsa.PublicKey: + return pub, nil + case *dsa.PublicKey: + return pub, nil + case *ecdsa.PublicKey: + return pub, nil + default: + return nil, fmt.Errorf("unknown type of public key") + } +} + +func ParsePrivateKey(data []byte) (interface{}, error) { + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("invalid private key format (expected pem block)") + } + return privateKey(block) +} + +func ParseCertificate(data []byte) (*x509.Certificate, error) { + block, _ := pem.Decode(data) + if block != nil { + if block.Type != CertificatePEMBlockType { + return nil, fmt.Errorf("unexpected pem block type for certificate: %q", block.Type) + } + return x509.ParseCertificate(block.Bytes) + } + return nil, fmt.Errorf("invalid certificate format (expected %s pem block)", CertificatePEMBlockType) +} + +func ParseCertificateChain(data []byte, filter bool) ([]*x509.Certificate, error) { + var chain []*x509.Certificate + for { + block, rest := pem.Decode(data) + if block == nil { + break + } + if block.Type != CertificatePEMBlockType { + if !filter { + return nil, fmt.Errorf("unexpected pem block type for certificate: %q", block.Type) + } + } else { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + chain = append(chain, cert) + } + data = rest + } + + if len(chain) == 0 { + if !filter { + return nil, fmt.Errorf("invalid certificate format (expected CERTIFICATE pem block)") + } + } + return chain, nil +} + +type PublicKeySource interface { + Public() crypto.PublicKey +} + +func GetPrivateKey(key GenericPrivateKey) (interface{}, error) { + switch k := key.(type) { + case []byte: + return ParsePrivateKey(k) + case string: + return ParsePrivateKey([]byte(k)) + case *rsa.PrivateKey: + return k, nil + case *ecdsa.PrivateKey: + return k, nil + default: + return nil, fmt.Errorf("unknown private key specification %T", k) + } +} + +func GetPublicKey(key GenericPublicKey) (interface{}, error) { + switch k := key.(type) { + case []byte: + return ParsePublicKey(k) + case string: + return ParsePublicKey([]byte(k)) + case *rsa.PublicKey: + return k, nil + case *dsa.PublicKey: + return k, nil + case *ecdsa.PublicKey: + return k, nil + case *x509.Certificate: + return k.PublicKey, nil + case PublicKeySource: + return k.Public(), nil + default: + return nil, fmt.Errorf("unknown public key specification %T", k) + } +} + +func GetCertificateChain(in GenericCertificateChain, filter bool) ([]*x509.Certificate, error) { + // unfortunately it is not possible to get certificates from a x509.CertPool + + switch k := in.(type) { + case []byte: + return ParseCertificateChain(k, filter) + case string: + return ParseCertificateChain([]byte(k), filter) + case *x509.Certificate: + return []*x509.Certificate{k}, nil + case []*x509.Certificate: + return k, nil + default: + return nil, fmt.Errorf("unknown certificate chain specification %T", k) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +func SystemCertPool() (*x509.CertPool, error) { + pool := x509.NewCertPool() + sys, err := x509.SystemCertPool() + if err != nil { + if runtime.GOOS != "windows" { + return nil, errors.Wrapf(err, "cannot get system cert pool") + } + } else { + pool = sys + } + return pool, nil +} + +func RootPoolFromFile(pemfile string, useOS bool, fss ...vfs.FileSystem) (*x509.CertPool, error) { + fs := utils.FileSystem(fss...) + pemdata, err := utils.ReadFile(pemfile, fs) + if err != nil { + return nil, errors.Wrapf(err, "cannot read cert pem file %q", pemfile) + } + pool := x509.NewCertPool() + if useOS { + sys, err := SystemCertPool() + if err != nil { + return nil, err + } + pool = sys + } + ok := pool.AppendCertsFromPEM(pemdata) + if !ok { + return nil, errors.Newf("cannot add cert pem file to cert pool") + } + return pool, err +} + +func GetCertPool(in GenericCertificatePool, filter bool) (*x509.CertPool, error) { + var certs []*x509.Certificate + var err error + + if reflect2.IsNil(in) { + return nil, nil + } + switch k := in.(type) { + case []byte: + certs, err = ParseCertificateChain(k, filter) + case string: + certs, err = ParseCertificateChain([]byte(k), filter) + case *x509.Certificate: + certs = []*x509.Certificate{k} + case []*x509.Certificate: + certs = k + case *x509.CertPool: + return k, nil + default: + err = fmt.Errorf("unknown certificate pool specification %T", k) + } + + if err != nil { + return nil, err + } + + pool := x509.NewCertPool() + for _, c := range certs { + pool.AddCert(c) + } + return pool, nil +} + +func GetCertificate(in GenericCertificate, filter bool) (*x509.Certificate, *x509.CertPool, error) { + var certs []*x509.Certificate + var err error + + switch k := in.(type) { + case []byte: + certs, err = ParseCertificateChain(k, filter) + case string: + certs, err = ParseCertificateChain([]byte(k), filter) + case *x509.Certificate: + certs = []*x509.Certificate{k} + case []*x509.Certificate: + certs = k + default: + err = fmt.Errorf("unknown certificate chain specification %T", k) + } + + if err != nil { + return nil, nil, err + } + + if len(certs) == 0 { + return nil, nil, fmt.Errorf("no certificate") + } + + pool := x509.NewCertPool() + for _, c := range certs { + pool.AddCert(c) + } + return certs[0], pool, nil +} + +func IsSelfSigned(cert *x509.Certificate) bool { + if cert.AuthorityKeyId == nil { + return true + } + return bytes.Equal(cert.SubjectKeyId, cert.AuthorityKeyId) +} + +func GetTime(in interface{}) (time.Time, error) { + switch t := in.(type) { + case time.Time: + return t, nil + case *time.Time: + return *t, nil + case string: + notBefore, err := time.Parse("Jan 2 15:04:05 2006", t) + if err != nil { + return time.Time{}, errors.Wrapf(err, "invalid time specification") + } + return notBefore, nil + default: + return time.Time{}, fmt.Errorf("invalid time specification type %T", in) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type KeyUsage interface { + String() string + AddTo(*x509.Certificate) +} + +type _keyUsage x509.KeyUsage + +func (this _keyUsage) AddTo(cert *x509.Certificate) { + cert.KeyUsage |= x509.KeyUsage(this) +} + +func (this _keyUsage) String() string { + switch x509.KeyUsage(this) { + case x509.KeyUsageDigitalSignature: + return "Signature" + case x509.KeyUsageContentCommitment: + return "ContentCommitment" + case x509.KeyUsageKeyEncipherment: + return "KeyEncipherment" + case x509.KeyUsageDataEncipherment: + return "DataEncipherment" + case x509.KeyUsageKeyAgreement: + return "KeyAgreement" + case x509.KeyUsageCertSign: + return "CertSign" + case x509.KeyUsageCRLSign: + return "CRLSign" + case x509.KeyUsageEncipherOnly: + return "EncipherOnly" + case x509.KeyUsageDecipherOnly: + return "DecipherOnly" + default: + return "UnknownKeyUsage" + } +} + +var _keyUsages = []x509.KeyUsage{ + x509.KeyUsageDigitalSignature, + x509.KeyUsageContentCommitment, + x509.KeyUsageKeyEncipherment, + x509.KeyUsageDataEncipherment, + x509.KeyUsageKeyAgreement, + x509.KeyUsageCertSign, + x509.KeyUsageCRLSign, + x509.KeyUsageEncipherOnly, + x509.KeyUsageDecipherOnly, +} + +func KeyUsages(usages x509.KeyUsage) []string { + result := []string{} + for _, u := range _keyUsages { + if usages&u != 0 { + result = append(result, (_keyUsage(u)).String()) + } + } + return result +} + +type _extKeyUsage x509.ExtKeyUsage + +func (this _extKeyUsage) AddTo(cert *x509.Certificate) { + for _, k := range cert.ExtKeyUsage { + if k == x509.ExtKeyUsage(this) { + return + } + } + cert.ExtKeyUsage = append(cert.ExtKeyUsage, x509.ExtKeyUsage(this)) +} + +func (this _extKeyUsage) String() string { + switch x509.ExtKeyUsage(this) { + case x509.ExtKeyUsageAny: + return "Any" + case x509.ExtKeyUsageServerAuth: + return "ServerAuth" + case x509.ExtKeyUsageClientAuth: + return "ClientAuth" + case x509.ExtKeyUsageCodeSigning: + return "CodeSigning" + case x509.ExtKeyUsageEmailProtection: + return "EmailProtection" + case x509.ExtKeyUsageIPSECEndSystem: + return "IPSECEndSystem" + case x509.ExtKeyUsageIPSECTunnel: + return "IPSECTunnel" + case x509.ExtKeyUsageIPSECUser: + return "IPSECUser" + case x509.ExtKeyUsageTimeStamping: + return "TimeStamping" + case x509.ExtKeyUsageOCSPSigning: + return "OCSPSigning" + case x509.ExtKeyUsageMicrosoftServerGatedCrypto: + return "MicrosoftServerGatedCrypto" + case x509.ExtKeyUsageNetscapeServerGatedCrypto: + return "NetscapeServerGatedCrypto" + case x509.ExtKeyUsageMicrosoftCommercialCodeSigning: + return "MicrosoftCommercialCodeSigning" + case x509.ExtKeyUsageMicrosoftKernelCodeSigning: + return "MicrosoftKernelCodeSigning" + default: + return "UnknownExtKeyUsage" + } +} + +func ExtKeyUsages(usages []x509.ExtKeyUsage) []string { + result := []string{} + for _, u := range usages { + result = append(result, (_extKeyUsage(u)).String()) + } + return result +} + +func ParseKeyUsage(name string) KeyUsage { + switch strings.ToLower(name) { + case "signature": + return _keyUsage(x509.KeyUsageDigitalSignature) + case "commitment": + return _keyUsage(x509.KeyUsageContentCommitment) + case "keyencipherment": + return _keyUsage(x509.KeyUsageKeyEncipherment) + case "dataencipherment": + return _keyUsage(x509.KeyUsageDataEncipherment) + case "keyagreement": + return _keyUsage(x509.KeyUsageKeyAgreement) + case "certsign": + return _keyUsage(x509.KeyUsageCertSign) + case "crlsign": + return _keyUsage(x509.KeyUsageCRLSign) + case "encipheronly": + return _keyUsage(x509.KeyUsageEncipherOnly) + case "decipheronly": + return _keyUsage(x509.KeyUsageDecipherOnly) + + case "any": + return _extKeyUsage(x509.ExtKeyUsageAny) + case "serverauth": + return _extKeyUsage(x509.ExtKeyUsageServerAuth) + case "clientauth": + return _extKeyUsage(x509.ExtKeyUsageClientAuth) + case "codesigning": + return _extKeyUsage(x509.ExtKeyUsageCodeSigning) + case "emailprotection": + return _extKeyUsage(x509.ExtKeyUsageEmailProtection) + case "ipsecendsystem": + return _extKeyUsage(x509.ExtKeyUsageIPSECEndSystem) + case "ipsectunnel": + return _extKeyUsage(x509.ExtKeyUsageIPSECTunnel) + case "ipsecuser": + return _extKeyUsage(x509.ExtKeyUsageIPSECUser) + case "timestamping": + return _extKeyUsage(x509.ExtKeyUsageTimeStamping) + case "ocspsigning": + return _extKeyUsage(x509.ExtKeyUsageOCSPSigning) + case "microsoftservergatedcrypto": + return _extKeyUsage(x509.ExtKeyUsageMicrosoftServerGatedCrypto) + case "netscapeservergatedcrypto": + return _extKeyUsage(x509.ExtKeyUsageNetscapeServerGatedCrypto) + case "microsoftcommercialcodesigning": + return _extKeyUsage(x509.ExtKeyUsageMicrosoftCommercialCodeSigning) + case "microsoftkernelcodesigning": + return _extKeyUsage(x509.ExtKeyUsageMicrosoftKernelCodeSigning) + } + return nil +} + +func GetKeyUsage(opt interface{}) KeyUsage { + switch o := opt.(type) { + case x509.ExtKeyUsage: + return _extKeyUsage(o) + case x509.KeyUsage: + return _keyUsage(o) + case string: + return ParseKeyUsage(o) + default: + return nil + } +} diff --git a/pkg/signing/tsa/pem.go b/pkg/signing/tsa/pem.go new file mode 100644 index 0000000000..6baac6f307 --- /dev/null +++ b/pkg/signing/tsa/pem.go @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package tsa + +import ( + "encoding/pem" + + "github.com/InfiniteLoopSpace/go_S-MIME/asn1" + cms "github.com/InfiniteLoopSpace/go_S-MIME/cms/protocol" + + "github.com/open-component-model/ocm/pkg/errors" +) + +const PRM_BLOCK_TYPE = "TIMESTAMP INFO" + +func ToPem(sd *TimeStamp) ([]byte, error) { + data, err := asn1.Marshal(*sd) + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal timestamp data") + } + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: PRM_BLOCK_TYPE, + Headers: nil, + Bytes: data, + }) + return pemBytes, nil +} + +func FromPem(data []byte) (*TimeStamp, error) { + block, rest := pem.Decode(data) + if block == nil || len(rest) > 0 { + return nil, errors.ErrInvalid("timestamp") + } + if block.Type != PRM_BLOCK_TYPE { + return nil, errors.ErrInvalid("PEM block type", block.Type, "timestamp") + } + var n cms.SignedData + _, err := asn1.Unmarshal(block.Bytes, &n) + if err != nil { + return nil, err + } + return &n, nil +} diff --git a/pkg/signing/tsa/tsa.go b/pkg/signing/tsa/tsa.go new file mode 100644 index 0000000000..9aa6dde0f1 --- /dev/null +++ b/pkg/signing/tsa/tsa.go @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package tsa + +import ( + "crypto" + "crypto/x509/pkix" + "fmt" + "time" + + cms "github.com/InfiniteLoopSpace/go_S-MIME/cms/protocol" + "github.com/InfiniteLoopSpace/go_S-MIME/oid" + tsa "github.com/InfiniteLoopSpace/go_S-MIME/timestamp" + "github.com/go-test/deep" + + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/signing/signutils" + "github.com/open-component-model/ocm/pkg/utils" +) + +// NewMessageImprint creates a new MessageImprint using hash and digest. +func NewMessageImprint(hash crypto.Hash, digest []byte) (*MessageImprint, error) { + digestAlgorithm := oid.HashToDigestAlgorithm[hash] + if len(digestAlgorithm) == 0 { + return nil, cms.ErrUnsupported + } + + if !hash.Available() { + return nil, cms.ErrUnsupported + } + + if len(digest) != hash.Size() { + return nil, cms.ASN1Error{"invalid hash size"} + } + + return &MessageImprint{ + HashAlgorithm: pkix.AlgorithmIdentifier{Algorithm: digestAlgorithm}, + HashedMessage: digest, + }, nil +} + +func NewMessageImprintForData(data []byte, hash crypto.Hash) (*MessageImprint, error) { + mi, err := tsa.NewMessageImprint(hash, data) + if err != nil { + return nil, err + } + return &mi, nil +} + +func Request(url string, mi *tsa.MessageImprint) (*TimeStamp, time.Time, error) { + if mi == nil { + return nil, time.Time{}, fmt.Errorf("message imprint required") + } + req := tsa.TimeStampReq{ + Version: 1, + CertReq: true, + Nonce: tsa.GenerateNonce(), + MessageImprint: *mi, + } + + resp, err := req.Do(url) + if err != nil { + return nil, time.Time{}, errors.Wrapf(err, "requesting timestamp from %s", url) + } + + sd, err := resp.TimeStampToken.SignedDataContent() + if err != nil { + return nil, time.Time{}, errors.Wrapf(err, "unexpected answer timestamp response from %s", url) + } + + t, err := Verify(mi, sd, true) + if err != nil { + return nil, time.Time{}, errors.Wrapf(err, "cannot verify timestamp response") + } + return sd, *t, nil +} + +func Verify(mi *tsa.MessageImprint, sd *TimeStamp, now bool, rootpool ...signutils.GenericCertificatePool) (*time.Time, error) { + info, err := tsa.ParseInfo(sd.EncapContentInfo) + if err != nil { + return nil, err + } + if diff := deep.Equal(info.MessageImprint.HashAlgorithm.Algorithm, mi.HashAlgorithm.Algorithm); diff != nil { + return nil, fmt.Errorf("hash algorithm mismatch: %s", diff) + } + if diff := deep.Equal(info.MessageImprint.HashedMessage, mi.HashedMessage); diff != nil { + return nil, fmt.Errorf("digest mismatch: %s", diff) + } + opts := tsa.Opts + if !now { + opts.CurrentTime = info.GenTime + } + opts.Roots, err = signutils.GetCertPool(utils.Optional(rootpool...), false) + if err != nil { + return nil, errors.Wrapf(err, "root cert pool") + } + _, err = sd.Verify(opts, nil) + if err != nil { + return nil, err + } + return &info.GenTime, nil +} + +func GetTimestamp(ts *TimeStamp) (time.Time, error) { + info, err := tsa.ParseInfo(ts.EncapContentInfo) + if err != nil { + return time.Time{}, err + } + return info.GenTime, nil +} diff --git a/pkg/signing/tsa/types.go b/pkg/signing/tsa/types.go new file mode 100644 index 0000000000..8a2b5ba6e2 --- /dev/null +++ b/pkg/signing/tsa/types.go @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package tsa + +import ( + cms "github.com/InfiniteLoopSpace/go_S-MIME/cms/protocol" + tsa "github.com/InfiniteLoopSpace/go_S-MIME/timestamp" +) + +type TimeStamp = cms.SignedData + +type MessageImprint = tsa.MessageImprint diff --git a/pkg/signing/types.go b/pkg/signing/types.go index aa9757d6a3..40640d9b32 100644 --- a/pkg/signing/types.go +++ b/pkg/signing/types.go @@ -6,14 +6,54 @@ package signing import ( "crypto" + "crypto/x509/pkix" "encoding/json" "hash" "github.com/sirupsen/logrus" "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/signing/signutils" ) +type SigningContext interface { + GetHash() crypto.Hash + GetPrivateKey() signutils.GenericPrivateKey + GetPublicKey() signutils.GenericPublicKey + GetRootCerts() signutils.GenericCertificatePool + GetIssuer() *pkix.Name +} + +type DefaultSigningContext struct { + Hash crypto.Hash + PrivateKey signutils.GenericPrivateKey + PublicKey signutils.GenericPublicKey + RootCerts signutils.GenericCertificatePool + Issuer *pkix.Name +} + +var _ SigningContext = (*DefaultSigningContext)(nil) + +func (d *DefaultSigningContext) GetHash() crypto.Hash { + return d.Hash +} + +func (d *DefaultSigningContext) GetPrivateKey() signutils.GenericPrivateKey { + return d.PrivateKey +} + +func (d *DefaultSigningContext) GetPublicKey() signutils.GenericPublicKey { + return d.PublicKey +} + +func (d *DefaultSigningContext) GetRootCerts() signutils.GenericCertificatePool { + return d.RootCerts +} + +func (d *DefaultSigningContext) GetIssuer() *pkix.Name { + return d.Issuer +} + type Signature struct { //nolint: musttag // only for string output Value string MediaType string @@ -33,8 +73,14 @@ func (s *Signature) String() string { // Signer interface is used to implement different signing algorithms. // Each Signer should have a matching Verifier. type Signer interface { - // Sign returns the signature for the given digest - Sign(cctx credentials.Context, digest string, hash crypto.Hash, issuer string, privatekey interface{}) (*Signature, error) + // Sign returns the signature for the given digest. + // If known a given public key can be passed. The signer may + // decide to put a trusted public key into the signature, + // for example for public keys provided by organization validated + // certificates. + // If used the key and/or certificate must be validated, for certificates + // the distinguished name must match the issuer. + Sign(cctx credentials.Context, digest string, sctx SigningContext) (*Signature, error) // Algorithm is the name of the finally used signature algorithm. // A signer might be registered using a logical name, so there might // be multiple signer registration providing the same signature algorithm @@ -45,7 +91,7 @@ type Signer interface { // Each Verifier should have a matching Signer. type Verifier interface { // Verify checks the signature, returns an error on verification failure - Verify(digest string, hash crypto.Hash, sig *Signature, publickey interface{}) error + Verify(digest string, sig *Signature, sctx SigningContext) error Algorithm() string } diff --git a/pkg/testutils/utils.go b/pkg/testutils/utils.go index 24015fadf2..9a36ff8d32 100644 --- a/pkg/testutils/utils.go +++ b/pkg/testutils/utils.go @@ -55,6 +55,11 @@ func Must2[T any, V any](a T, b V, err error) (T, V) { return a, b } +func Must3[T, U, V any](a T, b U, c V, err error) (T, U, V) { + ExpectWithOffset(1, err).To(Succeed()) + return a, b, c +} + type result[T any] struct { res T err error diff --git a/pkg/utils/path.go b/pkg/utils/path.go index f0231f1ebe..f56227a72a 100644 --- a/pkg/utils/path.go +++ b/pkg/utils/path.go @@ -16,6 +16,7 @@ import ( "github.com/open-component-model/ocm/pkg/errors" ) +// ResolvePath handles the ~ notation for the home directory. func ResolvePath(path string) (string, error) { if strings.HasPrefix(path, "~"+string(os.PathSeparator)) { home := os.Getenv("HOME") diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index daff4a7f89..8ce3548b65 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -330,7 +330,11 @@ func OptionalDefaultedBool(def bool, list ...bool) bool { // based on optionally specified explicit value(s). // The default value is to enable the option (true). func GetOptionFlag(list ...bool) bool { - return OptionalDefaultedBool(len(list) == 0, list...) + return OptionalDefaultedBool(true, list...) +} + +func IsNil(o interface{}) bool { + return reflect2.IsNil(o) } // Must expect a result to be provided without error. diff --git a/resources/component-descriptor-ocm-v3-schema.yaml b/resources/component-descriptor-ocm-v3-schema.yaml index ec59bd6e38..287411b038 100644 --- a/resources/component-descriptor-ocm-v3-schema.yaml +++ b/resources/component-descriptor-ocm-v3-schema.yaml @@ -173,6 +173,15 @@ definitions: value: type: string + timestampSpec: + type: object + properties: + value: + type: string + time: + type: string + format: date-time + signatureSpec: type: 'object' required: @@ -203,6 +212,8 @@ definitions: $ref: '#/definitions/digestSpec' signature: $ref: '#/definitions/signatureSpec' + timestamp: + $ref: '#/definitions/timestampSpec' nestedDigestSpec: type: 'object' diff --git a/resources/component-descriptor-v2-schema.yaml b/resources/component-descriptor-v2-schema.yaml index ba2b9062ea..6e35d7b46a 100644 --- a/resources/component-descriptor-v2-schema.yaml +++ b/resources/component-descriptor-v2-schema.yaml @@ -158,6 +158,15 @@ definitions: value: type: string + timestampSpec: + type: object + properties: + value: + type: string + time: + type: string + format: date-time + signatureSpec: type: 'object' required: @@ -177,7 +186,9 @@ definitions: type: 'object' required: - name + - digest - signature + additionalProperties: false properties: name: type: string @@ -185,6 +196,8 @@ definitions: $ref: '#/definitions/digestSpec' signature: $ref: '#/definitions/signatureSpec' + timestamp: + $ref: '#/definitions/timestampSpec' nestedDigestSpec: type: 'object' From 8fb075d6282f2886586a40880f1620365f605fd5 Mon Sep 17 00:00:00 2001 From: Fabian Burth Date: Fri, 22 Dec 2023 15:18:58 +0100 Subject: [PATCH 2/3] remove duplicate code (#601) * remove duplicate code * fix nil pointer exception --- .../ocm/repositories/genericocireg/componentversion.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/contexts/ocm/repositories/genericocireg/componentversion.go b/pkg/contexts/ocm/repositories/genericocireg/componentversion.go index 808f737f2b..6458f4c49f 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/componentversion.go +++ b/pkg/contexts/ocm/repositories/genericocireg/componentversion.go @@ -239,12 +239,6 @@ func (c *ComponentVersionContainer) evalLayer(s compdesc.AccessSpec) (compdesc.A } d = &artdesc.Descriptor{Digest: digest.Digest(a.LocalReference), MediaType: a.GetMimeType()} } - if a, ok := spec.(*localblob.AccessSpec); ok { - if ok, _ := artdesc.IsDigest(a.LocalReference); !ok { - return s, 0, errors.ErrInvalid("digest", a.LocalReference) - } - d = &artdesc.Descriptor{Digest: digest.Digest(a.LocalReference), MediaType: a.GetMimeType()} - } if d != nil { // find layer layers := c.manifest.GetDescriptor().Layers From 63afb2dfc1b9e3bf211625aaad096c20c1bd3179 Mon Sep 17 00:00:00 2001 From: Fabian Burth Date: Fri, 22 Dec 2023 15:20:37 +0100 Subject: [PATCH 3/3] Change the default compressing behavior to false (#604) So far, the behavior was to deducted from the media type (if the media type contained the suffix gzip, the file was compressed). This is quite unintuitive especially since a user might quite frequently provide an already compressed file (such as a helm chart archive). --- cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go | 2 +- cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go b/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go index e2e901b362..1a6f32645d 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go @@ -67,7 +67,7 @@ func NewProcessSpec(mediatype string, compress bool) ProcessSpec { // Compress returns if the blob should be compressed using gzip. func (s *ProcessSpec) Compress() bool { if s.CompressWithGzip == nil { - return mime.IsGZip(s.MediaType) + return false } return *s.CompressWithGzip } diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go index abad409cff..fc2d53cf2e 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go @@ -107,7 +107,7 @@ is set to true. This blob type specification supports the following fields: - **path** *string* - This REQUIRED property describes the file path to the helm chart relative to the + This REQUIRED property describes the path to the file relative to the resource file location. ` + cpi.ProcessSpecUsage }