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

Importing RSA and EC keypairs #1050

Merged
merged 18 commits into from
Jan 1, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions IMPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Import RSA and EC Keypairs
* Currently only supports RSA and ECDSA private keys

### Import a keypair

```shell
$ cosign import-key-pair --key opensslrsakey.pem
Enter password for private key:
Enter password for private key again:
Private key written to import-cosign.key
Public key written to import-cosign.pub
```
### Sign a container with imported keypair

```shell
$ cosign sign --key import import-cosign.key dlorenc/demo
Copy link
Member

@cpanato cpanato Jan 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this import is needed?

or should be just cosign sign --key import-cosign.key dlorenc/demo ?

Copy link
Contributor Author

@venafi-iw venafi-iw Jan 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentation needs to be updated as it should be:

cosign sign --key import-cosign.key dlorenc/demo

#1290

```
1 change: 1 addition & 0 deletions cmd/cosign/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func New() *cobra.Command {
cmd.AddCommand(Download())
cmd.AddCommand(Generate())
cmd.AddCommand(GenerateKeyPair())
cmd.AddCommand(ImportKeyPair())
cmd.AddCommand(Initialize())
cmd.AddCommand(Manifest())
cmd.AddCommand(PIVTool())
Expand Down
2 changes: 2 additions & 0 deletions cmd/cosign/cli/generate/generate_key_pair.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ func isTerminal() bool {
return (stat.Mode() & os.ModeCharDevice) != 0
}

// TODO centralize password prompt logic for code reuse across more use cases -> https://github.com/sigstore/cosign/issues/1078
func getPassFromTerm(confirm bool) ([]byte, error) {
fmt.Fprint(os.Stderr, "Enter password for private key: ")
pw1, err := term.ReadPassword(0)
Expand All @@ -164,6 +165,7 @@ func getPassFromTerm(confirm bool) ([]byte, error) {
return pw1, nil
}

// TODO need to centralize this logic
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
Expand Down
48 changes: 48 additions & 0 deletions cmd/cosign/cli/import_key_pair.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cli

import (
"github.com/spf13/cobra"

"github.com/sigstore/cosign/cmd/cosign/cli/importkeypair"
"github.com/sigstore/cosign/cmd/cosign/cli/options"
)

func ImportKeyPair() *cobra.Command {
o := &options.ImportKeyPairOptions{}

cmd := &cobra.Command{
Use: "import-key-pair",
Short: "Imports a PEM-encoded RSA or EC private key.",
Long: "Imports a PEM-encoded RSA or EC private key for signing.",
Example: ` cosign import-key-pair --key openssl.key

# import PEM-encoded RSA or EC private key and write to import-cosign.key and import-cosign.pub files
cosign import-key-pair --key <key path>

CAVEATS:
This command interactively prompts for a password. You can use
the COSIGN_PASSWORD environment variable to provide one.`,

RunE: func(cmd *cobra.Command, args []string) error {
return importkeypair.ImportKeyPairCmd(cmd.Context(), o.Key, args)
},
}

o.AddFlags(cmd)
return cmd
}
128 changes: 128 additions & 0 deletions cmd/cosign/cli/importkeypair/import_key_pair.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package importkeypair

import (
"context"
"fmt"
"io"
"os"

"github.com/pkg/errors"
"github.com/sigstore/cosign/pkg/cosign"
"golang.org/x/term"
)

var (
// Read is for fuzzing
Read = readPasswordFn
)

// nolint
func ImportKeyPairCmd(ctx context.Context, keyVal string, args []string) error {

keys, err := cosign.ImportKeyPair(keyVal, GetPass)
if err != nil {
return err
}

if fileExists("import-cosign.key") {
var overwrite string
fmt.Fprint(os.Stderr, "File import-cosign.key already exists. Overwrite (y/n)? ")
fmt.Scanf("%s", &overwrite)
switch overwrite {
case "y", "Y":
case "n", "N":
return nil
default:
fmt.Fprintln(os.Stderr, "Invalid input")
return nil
}
}
// TODO: make sure the perms are locked down first.
if err := os.WriteFile("import-cosign.key", keys.PrivateBytes, 0600); err != nil {
return err
}
fmt.Fprintln(os.Stderr, "Private key written to import-cosign.key")

if err := os.WriteFile("import-cosign.pub", keys.PublicBytes, 0644); err != nil {
return err
} // #nosec G306
fmt.Fprintln(os.Stderr, "Public key written to import-cosign.pub")
return nil
}

func GetPass(confirm bool) ([]byte, error) {
read := Read(confirm)
return read()
}

func readPasswordFn(confirm bool) func() ([]byte, error) {
pw, ok := os.LookupEnv("COSIGN_PASSWORD")
switch {
case ok:
return func() ([]byte, error) {
return []byte(pw), nil
}
case isTerminal():
return func() ([]byte, error) {
return getPassFromTerm(confirm)
}
// Handle piped in passwords.
default:
return func() ([]byte, error) {
return io.ReadAll(os.Stdin)
}
}
}

func isTerminal() bool {
stat, _ := os.Stdin.Stat()
return (stat.Mode() & os.ModeCharDevice) != 0
}

// TODO centralize password prompt logic for code reuse across more use cases -> https://github.com/sigstore/cosign/issues/1078
func getPassFromTerm(confirm bool) ([]byte, error) {
fmt.Fprint(os.Stderr, "Enter password for private key: ")
pw1, err := term.ReadPassword(0)
if err != nil {
return nil, err
}
if !confirm {
return pw1, nil
}
fmt.Fprintln(os.Stderr)
fmt.Fprint(os.Stderr, "Enter password for private key again: ")
confirmpw, err := term.ReadPassword(0)
fmt.Fprintln(os.Stderr)
if err != nil {
return nil, err
}

if string(pw1) != string(confirmpw) {
return nil, errors.New("passwords do not match")
}
return pw1, nil
}

// TODO need to centralize this logic
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
47 changes: 47 additions & 0 deletions cmd/cosign/cli/importkeypair/import_key_pair_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package importkeypair

import (
"os"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestReadPasswordFn_env(t *testing.T) {
os.Setenv("COSIGN_PASSWORD", "foo")
defer os.Unsetenv("COSIGN_PASSWORD")
b, err := readPasswordFn(true)()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if diff := cmp.Diff("foo", string(b)); diff != "" {
t.Fatal(diff)
}
}

func TestReadPasswordFn_envEmptyVal(t *testing.T) {
os.Setenv("COSIGN_PASSWORD", "")
defer os.Unsetenv("COSIGN_PASSWORD")
b, err := readPasswordFn(true)()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(b) > 0 {
t.Fatalf("expected empty string; got %q", string(b))
}
}
34 changes: 34 additions & 0 deletions cmd/cosign/cli/options/import_key_pair.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package options

import (
"github.com/spf13/cobra"
)

// ImportKeyPairOptions is the top level wrapper for the import-key-pair command.
type ImportKeyPairOptions struct {
// Local key file generated by external program such as OpenSSL
Key string
}

var _ Interface = (*ImportKeyPairOptions)(nil)

// AddFlags implements Interface
func (o *ImportKeyPairOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Key, "key", "",
"import key pair to use for signing")
}
1 change: 1 addition & 0 deletions doc/cosign.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions doc/cosign_import-key-pair.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading