Skip to content

Commit

Permalink
generate any key and output it to a pair of .pub and .priv files
Browse files Browse the repository at this point in the history
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
  • Loading branch information
David Lawrence committed Apr 7, 2017
1 parent 86e2ee9 commit 23acee6
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 54 deletions.
110 changes: 91 additions & 19 deletions cmd/notary/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ import (
store "github.com/docker/notary/storage"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
tufutils "github.com/docker/notary/tuf/utils"
"github.com/docker/notary/utils"

"errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"io/ioutil"
)

var cmdKeyTemplate = usageTemplate{
Expand All @@ -38,10 +41,17 @@ var cmdRotateKeyTemplate = usageTemplate{
Long: `Generates a new key for the given Globally Unique Name and role (one of "snapshot", "targets", "root", or "timestamp"). If rotating to a server-managed key, a new key is requested from the server rather than generated. If the generation or key request is successful, the key rotation is immediately published. No other changes, even if they are staged, will be published.`,
}

var cmdKeyGenerateRootKeyTemplate = usageTemplate{
var cmdKeyGenerateKeyTemplate = usageTemplate{
Use: "generate [ algorithm ]",
Short: "Generates a new root key with a given algorithm.",
Long: "Generates a new root key with a given algorithm. If hardware key storage (e.g. a Yubikey) is available, the key will be stored both on hardware and on disk (so that it can be backed up). Please make sure to back up and then remove this on-key disk immediately afterwards.",
Short: "Generates a new key with a given algorithm.",
Long: "Generates a new key with a given algorithm. If hardware key " +
"storage (e.g. a Yubikey) is available, the key will be stored both " +
"on hardware and on disk (so that it can be backed up). Please make " +
"sure to back up and then remove this on-key disk immediately" +
"afterwards. If a `--output` file name is provided, two files will " +
"be written with .pub and .priv file extensions, containing the public" +
"and private keys respectively (the key will not be stored in Notary's " +
"key storage). If no `--role` is provided, \"root\" will be assumed.",
}

var cmdKeyRemoveTemplate = usageTemplate{
Expand Down Expand Up @@ -80,17 +90,29 @@ type keyCommander struct {
legacyVersions int
input io.Reader

keysImportRole string
keysImportGUN string
exportGUNs []string
exportKeyIDs []string
outFile string
importRole string
generateRole string
keysImportGUN string
exportGUNs []string
exportKeyIDs []string
outFile string
}

func (k *keyCommander) GetCommand() *cobra.Command {
cmd := cmdKeyTemplate.ToCommand(nil)
cmd.AddCommand(cmdKeyListTemplate.ToCommand(k.keysList))
cmd.AddCommand(cmdKeyGenerateRootKeyTemplate.ToCommand(k.keysGenerateRootKey))
cmdGenerate := cmdKeyGenerateKeyTemplate.ToCommand(k.keysGenerate)
cmdGenerate.Flags().StringVarP(
&k.outFile,
"output",
"o",
"",
"Filepath to write export output to",
)
cmdGenerate.Flags().StringVarP(
&k.generateRole, "role", "r", "root", "Role to generate key with, defaulting to \"root\".",
)
cmd.AddCommand(cmdGenerate)
cmd.AddCommand(cmdKeyRemoveTemplate.ToCommand(k.keyRemove))
cmd.AddCommand(cmdKeyPasswdTemplate.ToCommand(k.keyPassphraseChange))
cmdRotateKey := cmdRotateKeyTemplate.ToCommand(k.keysRotate)
Expand All @@ -110,7 +132,7 @@ func (k *keyCommander) GetCommand() *cobra.Command {

cmdKeysImport := cmdKeyImportTemplate.ToCommand(k.importKeys)
cmdKeysImport.Flags().StringVarP(
&k.keysImportRole, "role", "r", "", "Role to import key with, if a role is not already given in a PEM header")
&k.importRole, "role", "r", "", "Role to import key with, if a role is not already given in a PEM header")
cmdKeysImport.Flags().StringVarP(
&k.keysImportGUN, "gun", "g", "", "Gun to import key with, if a gun is not already given in a PEM header")
cmd.AddCommand(cmdKeysImport)
Expand Down Expand Up @@ -159,7 +181,7 @@ func (k *keyCommander) keysList(cmd *cobra.Command, args []string) error {
return nil
}

func (k *keyCommander) keysGenerateRootKey(cmd *cobra.Command, args []string) error {
func (k *keyCommander) keysGenerate(cmd *cobra.Command, args []string) error {
// We require one or no arguments (since we have a default value), but if the
// user passes in more than one argument, we error out.
if len(args) > 1 {
Expand Down Expand Up @@ -189,19 +211,69 @@ func (k *keyCommander) keysGenerateRootKey(cmd *cobra.Command, args []string) er
if err != nil {
return err
}
ks, err := k.getKeyStores(config, true, true)

// if no outFile is provided, use the known key stores
if k.outFile == "" {
ks, err := k.getKeyStores(config, true, true)
if err != nil {
return err
}
cs := cryptoservice.NewCryptoService(ks...)

pubKey, err := cs.Create(data.RoleName(k.generateRole), "", algorithm)
if err != nil {
return fmt.Errorf("Failed to create a new root key: %v", err)
}

cmd.Printf("Generated new %s %s key with keyID: %s\n", algorithm, k.generateRole, pubKey.ID())
return nil
}

// if we had an outfile set, we'll write 2 files with the given name, appending .pub and .priv for the
// public and private keys respectively
return generateKeyToFile(k.generateRole, algorithm, k.getRetriever(), k.outFile)
}

func generateKeyToFile(role, algorithm string, retriever notary.PassRetriever, outFile string) error {
privKey, err := tufutils.GenerateKey(algorithm)
if err != nil {
return err
}
cs := cryptoservice.NewCryptoService(ks...)
pubKey := data.PublicKeyFromPrivate(privKey)

pubKey, err := cs.Create(data.CanonicalRootRole, "", algorithm)
if err != nil {
return fmt.Errorf("Failed to create a new root key: %v", err)
var (
chosenPassphrase string
giveup bool
pemPrivKey []byte
)
keyID := privKey.ID()
for attempts := 0; ; attempts++ {
chosenPassphrase, giveup, err = retriever(keyID, "", true, attempts)
if err == nil {
break
}
if giveup || attempts > 10 {
return trustmanager.ErrAttemptsExceeded{}
}
}

cmd.Printf("Generated new %s root key with keyID: %s\n", algorithm, pubKey.ID())
return nil
if chosenPassphrase == "" {
pemPrivKey, err = tufutils.EncryptPrivateKey(privKey, data.RoleName(role), "", chosenPassphrase)
if err != nil {
return err
}
} else {
return errors.New("no password provided")
}

privFile := strings.Join([]string{outFile, "priv"}, ".")
pubFile := strings.Join([]string{outFile, "pub"}, ".")

err = ioutil.WriteFile(privFile, pemPrivKey, notary.PrivNoExecPerms)
if err != nil {
return err
}
return ioutil.WriteFile(pubFile, pubKey.Public(), notary.PrivNoExecPerms)
}

func (k *keyCommander) keysRotate(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -442,7 +514,7 @@ func (k *keyCommander) importKeys(cmd *cobra.Command, args []string) error {
return err
}
defer from.Close()
if err = utils.ImportKeys(from, importers, k.keysImportRole, k.keysImportGUN, k.getRetriever()); err != nil {
if err = utils.ImportKeys(from, importers, k.importRole, k.keysImportGUN, k.getRetriever()); err != nil {
return err
}
}
Expand Down
40 changes: 5 additions & 35 deletions cryptoservice/crypto_service.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package cryptoservice

import (
"crypto/rand"
"fmt"

"crypto/x509"
"encoding/pem"
"errors"
"github.com/Sirupsen/logrus"
"github.com/docker/notary"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/utils"
Expand Down Expand Up @@ -37,42 +35,14 @@ func NewCryptoService(keyStores ...trustmanager.KeyStore) *CryptoService {

// Create is used to generate keys for targets, snapshots and timestamps
func (cs *CryptoService) Create(role data.RoleName, gun data.GUN, algorithm string) (data.PublicKey, error) {
var privKey data.PrivateKey
var err error

switch algorithm {
case data.RSAKey:
privKey, err = utils.GenerateRSAKey(rand.Reader, notary.MinRSABitSize)
if err != nil {
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
}
case data.ECDSAKey:
privKey, err = utils.GenerateECDSAKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate EC key: %v", err)
}
case data.ED25519Key:
privKey, err = utils.GenerateED25519Key(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate ED25519 key: %v", err)
}
default:
return nil, fmt.Errorf("private key type not supported for key generation: %s", algorithm)
}
logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role.String(), privKey.ID())

// Store the private key into our keystore
for _, ks := range cs.keyStores {
err = ks.AddKey(trustmanager.KeyInfo{Role: role, Gun: gun}, privKey)
if err == nil {
return data.PublicKeyFromPrivate(privKey), nil
}
}
privKey, err := utils.GenerateKey(algorithm)
if err != nil {
return nil, fmt.Errorf("failed to add key to filestore: %v", err)
return nil, fmt.Errorf("failed to generate %s key: %v", algorithm, err)
}
logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role.String(), privKey.ID())
pubKey := data.PublicKeyFromPrivate(privKey)

return nil, fmt.Errorf("keystores would not accept new private keys for unknown reasons")
return pubKey, cs.AddKey(role, gun, privKey)
}

// GetPrivateKey returns a private key and role if present by ID.
Expand Down
14 changes: 14 additions & 0 deletions tuf/utils/x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,20 @@ func ValidateCertificate(c *x509.Certificate, checkExpiry bool) error {
return nil
}

// GenerateKey returns a new private key using the provided algorithm or an
// error detailing why the key could not be generated
func GenerateKey(algorithm string) (data.PrivateKey, error) {
switch algorithm {
case data.RSAKey:
return GenerateRSAKey(rand.Reader, notary.MinRSABitSize)
case data.ECDSAKey:
return GenerateECDSAKey(rand.Reader)
case data.ED25519Key:
return GenerateED25519Key(rand.Reader)
}
return nil, fmt.Errorf("private key type not supported for key generation: %s", algorithm)
}

// GenerateRSAKey generates an RSA private key and returns a TUF PrivateKey
func GenerateRSAKey(random io.Reader, bits int) (data.PrivateKey, error) {
rsaPrivKey, err := rsa.GenerateKey(random, bits)
Expand Down

0 comments on commit 23acee6

Please sign in to comment.