Skip to content
This repository has been archived by the owner on May 3, 2022. It is now read-only.

feat: add duffle key export #445

Merged
merged 1 commit into from
Nov 19, 2018
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
1 change: 1 addition & 0 deletions cmd/duffle/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func newKeyCmd(w io.Writer) *cobra.Command {
cmd.AddCommand(
newKeyAddCmd(w),
newKeyListCmd(w),
newKeyExportCmd(w),
)
return cmd
}
91 changes: 91 additions & 0 deletions cmd/duffle/key_export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package main

import (
"errors"
"fmt"
"io"
"os"

"github.com/deis/duffle/pkg/duffle/home"
"github.com/deis/duffle/pkg/signature"

"github.com/spf13/cobra"
)

const keyExportDesc = `Export the public key part of a signing key.

This exports a public key to a file (as a keyring). If the output file already
exists, the new key will be added. Importantly, if other private keys exist in
the file, the private key material will be removed from those as well.

If no key name is given, the default signing key is exported.
`

func newKeyExportCmd(w io.Writer) *cobra.Command {
var dest string
var keyname string
cmd := &cobra.Command{
Use: "export FILE",
Short: "export the public key of a signing key",
Long: keyExportDesc,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
h := home.Home(homePath())
dest = args[0]

ring, err := signature.LoadKeyRing(h.SecretKeyRing())
if err != nil {
return err
}

// Get either the named key or the first key in the ring
var key *signature.Key
if keyname == "" {
allKeys := ring.Keys()
if len(allKeys) == 0 {
return errors.New("no keys found in signing ring")
}
key = allKeys[0]
} else {
key, err = ring.Key(keyname)
if err != nil {
return err
}
}

uid, err := key.UserID()
if err != nil {
return fmt.Errorf("could not load key with ID %q: %s", keyname, err)
}
if verbose {
fmt.Fprintf(w, "found key %q matching %q\n", uid.String(), keyname)
}

// Send to the destination, or to STDOUT
if fi, err := os.Stat(dest); os.IsNotExist(err) {
kr := signature.CreateKeyRing(passwordFetcher)
kr.AddKey(key)
return kr.SavePublic(dest, true)
} else if err != nil {
return err
} else if fi.IsDir() {
return errors.New("destination cannot be a directory")
}

kr, err := signature.LoadKeyRing(dest)
if err != nil {
return err
}
kr.AddKey(key)

for _, k := range kr.Keys() {
uid, _ := k.UserID()
println(uid.String())
}
return kr.SavePublic(dest, true)

},
}
cmd.Flags().StringVarP(&keyname, "user", "u", "", "the user ID of the key to export")
return cmd
}
16 changes: 13 additions & 3 deletions pkg/signature/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,16 +251,26 @@ func (r *KeyRing) SavePublic(filepath string, clobber bool) error {

// Write to a buffer so we don't nuke a keychain.
temp := bytes.NewBuffer(nil)
if err := r.SavePublicTo(temp); err != nil {
return err
}
_, err = io.Copy(f, temp)
return err
}

// SavePublicTo saves the keyring to the given writer
//
// It removes private key material as it goes.
func (r *KeyRing) SavePublicTo(w io.Writer) error {
for _, e := range r.entities {
// According to the godocs, when we call this, we lose private key material, but keep public and signatures.
// However, if I load a secret keyring (generated by GnuPG) and then serialize it, the private key
// seems to still be there. Using `gpg --list-secret-keys`, I can recover secret keys after this method is run.
if err := e.Serialize(temp); err != nil {
if err := e.Serialize(w); err != nil {
return err
}
}
_, err = io.Copy(f, temp)
return err
return nil
}

func decryptPassphrase(msg string, pk *packet.PrivateKey, fetcher PassphraseFetcher) error {
Expand Down