Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 1 addition & 6 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ import (
"github.com/notaryproject/notation-go/dir"
)

const (
configPath = "./testdata/config.json"
nonexistentPath = "./testdata/nonexistent.json"
)

var sampleConfig = &Config{
InsecureRegistries: []string{
"registry.wabbit-networks.io",
Expand All @@ -20,7 +15,7 @@ var sampleConfig = &Config{
}

func TestLoadFile(t *testing.T) {
dir.UserConfigDir = "./testdata"
dir.UserConfigDir = "./testdata/valid"
got, err := LoadConfig()
if err != nil {
t.Fatalf("LoadConfig() error. err = %v", err)
Expand Down
218 changes: 206 additions & 12 deletions config/keys.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package config

import (
"context"
"crypto/tls"
"errors"
"fmt"
"github.com/notaryproject/notation-go/internal/slices"
"github.com/notaryproject/notation-go/log"
"github.com/notaryproject/notation-go/plugin"
"io/fs"

"github.com/notaryproject/notation-go/dir"
set "github.com/notaryproject/notation-go/internal/container"
)

// X509KeyPair contains the paths of a public/private key pair files.
Expand All @@ -29,32 +36,154 @@ type KeySuite struct {
*ExternalKey
}

// Is checks whether the given name is equal with the Name variable
func (k KeySuite) Is(name string) bool {
return k.Name == name
}
var errorKeyNameEmpty = errors.New("key name cannot be empty")
var errKeyNotFound = errors.New("signing key not found")

// SigningKeys reflects the signingkeys.json file.
type SigningKeys struct {
Default string `json:"default"`
Default *string `json:"default,omitempty"`
Keys []KeySuite `json:"keys"`
}

// Save config to file
// NewSigningKeys creates a new signingkeys config file
func NewSigningKeys() *SigningKeys {
return &SigningKeys{Keys: []KeySuite{}}
}

// Add adds new signing key
func (s *SigningKeys) Add(name, keyPath, certPath string, markDefault bool) error {
if name == "" {
return errorKeyNameEmpty
}
_, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return err
}

ks := KeySuite{
Name: name,
X509KeyPair: &X509KeyPair{
KeyPath: keyPath,
CertificatePath: certPath,
},
}
return s.add(ks, markDefault)
}

// AddPlugin adds new plugin based signing key
func (s *SigningKeys) AddPlugin(ctx context.Context, keyName, id, pluginName string, pluginConfig map[string]string, markDefault bool) error {
logger := log.GetLogger(ctx)
logger.Debugf("Adding key with name %v and plugin name %v", keyName, pluginName)

if keyName == "" {
return errorKeyNameEmpty
}

if id == "" {
return errors.New("missing key id")
}

if pluginName == "" {
return errors.New("plugin name cannot be empty")
}

mgr := plugin.NewCLIManager(dir.PluginFS())
_, err := mgr.Get(ctx, pluginName)
if err != nil {
return err
}

ks := KeySuite{
Name: keyName,
ExternalKey: &ExternalKey{
ID: id,
PluginName: pluginName,
PluginConfig: pluginConfig,
},
}

if err = s.add(ks, markDefault); err != nil {
logger.Error("Failed to add key with error: %v", err)
return err
}
logger.Debugf("Added key with name %s - {%+v}", keyName, ks)
return nil
}

// Get returns signing key for the given name
func (s *SigningKeys) Get(keyName string) (KeySuite, error) {
if keyName == "" {
return KeySuite{}, errorKeyNameEmpty
}

idx := slices.IndexIsser(s.Keys, keyName)
if idx < 0 {
return KeySuite{}, errKeyNotFound
}

return s.Keys[idx], nil
}

// GetDefault returns default signing key
func (s *SigningKeys) GetDefault() (KeySuite, error) {
if s.Default == nil {
return KeySuite{}, errors.New("default signing key not set." +
" Please set default singing key or specify a key name")
}

return s.Get(*s.Default)
}

// Remove deletes given signing keys and returns a slice of deleted key names
func (s *SigningKeys) Remove(keyName ...string) ([]string, error) {
var deletedNames []string
for _, name := range keyName {
if name == "" {
return deletedNames, errorKeyNameEmpty
}

idx := slices.IndexIsser(s.Keys, name)
if idx < 0 {
return deletedNames, errors.New(name + ": not found")
}
s.Keys = slices.Delete(s.Keys, idx)
deletedNames = append(deletedNames, name)
if *s.Default == name {
s.Default = nil
}
}
return deletedNames, nil
}

// UpdateDefault updates default signing key
func (s *SigningKeys) UpdateDefault(keyName string) error {
if keyName == "" {
return errorKeyNameEmpty
}

if !slices.ContainsIsser(s.Keys, keyName) {
return fmt.Errorf("key with name '%s' not found", keyName)
}

s.Default = &keyName
return nil
}

// Save SigningKeys to signingkeys.json file
func (s *SigningKeys) Save() error {
path, err := dir.ConfigFS().SysPath(dir.PathSigningKeys)
if err != nil {
return err
}
return save(path, s)
}

// NewSigningKeys creates a new signingkeys config file
func NewSigningKeys() *SigningKeys {
return &SigningKeys{Keys: []KeySuite{}}
if err := validateKeys(s); err != nil {
return err
}

return save(path, s)
}

// LoadSigningKeys reads the config from file
// LoadSigningKeys reads the signingkeys.json file
// or return a default config if not found.
func LoadSigningKeys() (*SigningKeys, error) {
var config SigningKeys
Expand All @@ -65,5 +194,70 @@ func LoadSigningKeys() (*SigningKeys, error) {
}
return nil, err
}

if err := validateKeys(&config); err != nil {
return nil, err
}

return &config, nil
}

// LoadExecSaveSigningKeys loads signing key, executes given function and then saves the signing key
func LoadExecSaveSigningKeys(fn func(keys *SigningKeys) error) error {
// core process
signingKeys, err := LoadSigningKeys()
if err != nil {
return err
}

if err := fn(signingKeys); err != nil {
return err
}

return signingKeys.Save()
}

// Is checks whether the given name is equal with the Name variable
func (k KeySuite) Is(name string) bool {
return k.Name == name
}

func (s *SigningKeys) add(key KeySuite, markDefault bool) error {
if slices.ContainsIsser(s.Keys, key.Name) {
return fmt.Errorf("signing key with name %q already exists", key.Name)
}

s.Keys = append(s.Keys, key)
if markDefault {
s.Default = &key.Name
}

return nil
}

func validateKeys(config *SigningKeys) error {
keys := config.Keys
uniqueKeyNames := set.NewWithSize[string](len(keys))
for _, key := range keys {
if len(key.Name) == 0 {
return fmt.Errorf("malformed %s: key name cannot be empty", dir.PathSigningKeys)
}
if uniqueKeyNames.Contains(key.Name) {
return fmt.Errorf("malformed %s: multiple keys with name '%s' found", dir.PathSigningKeys, key.Name)
}
uniqueKeyNames.Add(key.Name)
}

if config.Default != nil {
defaultKey := *config.Default
if len(defaultKey) == 0 {
return fmt.Errorf("malformed %s: default key name cannot be empty", dir.PathSigningKeys)
}

if !uniqueKeyNames.Contains(defaultKey) {
return fmt.Errorf("malformed %s: default key '%s' not found", dir.PathSigningKeys, defaultKey)
}
}

return nil
}
Loading