Files
docker-cli/cli/command/trust/key_load.go
Sebastiaan van Stijn 0adaf6be3b verify that DisableFlagsInUseLine is set for all commands
This replaces the visitAll recursive function with a test that verifies that
the option is set for all commands and subcommands, so that it doesn't have
to be modified at runtime.

We currently still have to loop over all functions for the setValidateArgs
call, but that can be looked at separately.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 09:39:46 +02:00

120 lines
3.9 KiB
Go

package trust
import (
"bytes"
"encoding/pem"
"errors"
"fmt"
"io"
"os"
"runtime"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/spf13/cobra"
"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/storage"
"github.com/theupdateframework/notary/trustmanager"
tufutils "github.com/theupdateframework/notary/tuf/utils"
)
const (
nonOwnerReadWriteMask = 0o077
)
type keyLoadOptions struct {
keyName string
}
func newKeyLoadCommand(dockerCLI command.Streams) *cobra.Command {
var options keyLoadOptions
cmd := &cobra.Command{
Use: "load [OPTIONS] KEYFILE",
Short: "Load a private key file for signing",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return loadPrivKey(dockerCLI, args[0], options)
},
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
flags.StringVar(&options.keyName, "name", "signer", "Name for the loaded key")
return cmd
}
func loadPrivKey(streams command.Streams, keyPath string, options keyLoadOptions) error {
// validate the key name if provided
if options.keyName != "" && !validKeyName(options.keyName) {
return fmt.Errorf("key name \"%s\" must start with lowercase alphanumeric characters and can include \"-\" or \"_\" after the first character", options.keyName)
}
trustDir := trust.GetTrustDirectory()
keyFileStore, err := storage.NewPrivateKeyFileStorage(trustDir, notary.KeyExtension)
if err != nil {
return err
}
privKeyImporters := []trustmanager.Importer{keyFileStore}
_, _ = fmt.Fprintf(streams.Out(), "Loading key from \"%s\"...\n", keyPath)
// Always use a fresh passphrase retriever for each import
passRet := trust.GetPassphraseRetriever(streams.In(), streams.Out())
keyBytes, err := getPrivKeyBytesFromPath(keyPath)
if err != nil {
return fmt.Errorf("refusing to load key from %s: %w", keyPath, err)
}
if err := loadPrivKeyBytesToStore(keyBytes, privKeyImporters, keyPath, options.keyName, passRet); err != nil {
return fmt.Errorf("error importing key from %s: %w", keyPath, err)
}
_, _ = fmt.Fprintln(streams.Out(), "Successfully imported key from", keyPath)
return nil
}
func getPrivKeyBytesFromPath(keyPath string) ([]byte, error) {
if runtime.GOOS != "windows" {
fileInfo, err := os.Stat(keyPath)
if err != nil {
return nil, err
}
if fileInfo.Mode()&nonOwnerReadWriteMask != 0 {
return nil, fmt.Errorf("private key file %s must not be readable or writable by others", keyPath)
}
}
from, err := os.OpenFile(keyPath, os.O_RDONLY, notary.PrivExecPerms)
if err != nil {
return nil, err
}
defer from.Close()
return io.ReadAll(from)
}
func loadPrivKeyBytesToStore(privKeyBytes []byte, privKeyImporters []trustmanager.Importer, keyPath, keyName string, passRet notary.PassRetriever) error {
var err error
if _, _, err = tufutils.ExtractPrivateKeyAttributes(privKeyBytes); err != nil {
return fmt.Errorf("provided file %s is not a supported private key - to add a signer's public key use docker trust signer add", keyPath)
}
if privKeyBytes, err = decodePrivKeyIfNecessary(privKeyBytes, passRet); err != nil {
return fmt.Errorf("cannot load key from provided file %s: %w", keyPath, err)
}
// Make a reader, rewind the file pointer
return trustmanager.ImportKeys(bytes.NewReader(privKeyBytes), privKeyImporters, keyName, "", passRet)
}
func decodePrivKeyIfNecessary(privPemBytes []byte, passRet notary.PassRetriever) ([]byte, error) {
pemBlock, _ := pem.Decode(privPemBytes)
_, containsDEKInfo := pemBlock.Headers["DEK-Info"]
if containsDEKInfo || pemBlock.Type == "ENCRYPTED PRIVATE KEY" {
// if we do not have enough information to properly import, try to decrypt the key
if _, ok := pemBlock.Headers["path"]; !ok {
privKey, _, err := trustmanager.GetPasswdDecryptBytes(passRet, privPemBytes, "", "encrypted")
if err != nil {
return []byte{}, errors.New("could not decrypt key")
}
privPemBytes = privKey.Private()
}
}
return privPemBytes, nil
}