Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support for Delivered Public Key Certificates #592

Merged
merged 12 commits into from
Dec 22, 2023
48 changes: 48 additions & 0 deletions cmds/ocm/commands/common/options/keyoption/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package keyoption

import (
"fmt"
"reflect"
"strings"

"github.com/spf13/pflag"
Expand All @@ -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"
)

Expand All @@ -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) ([<name>:=]<dn>")
fs.StringArrayVarP(&o.rootCAs, "ca-cert", "", nil, "additional root certificate authorities")
}

func (o *Option) Configure(ctx clictx.Context) error {
Expand All @@ -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
}

Expand Down Expand Up @@ -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 <code>!</code> or as direct string with the prefix
<code>=</code>.

With <code>--issuer</code> 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 <code>--ca-cert</code> 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
}
Expand Down
109 changes: 10 additions & 99 deletions cmds/ocm/commands/misccmds/hash/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<private key file> <hash> [<issuer>]",
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
}
152 changes: 152 additions & 0 deletions cmds/ocm/commands/misccmds/hash/sign/cmd.go
Original file line number Diff line number Diff line change
@@ -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: "<private key file> <hash> [<issuer>]",
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
}
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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() {
Expand Down
Loading
Loading