Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efbe79e786 | ||
|
|
c4d67105f2 | ||
|
|
e043384c67
|
||
|
|
040b438cd1
|
||
|
|
1f49d14e58
|
||
|
|
2a69f39e4b
|
||
|
|
ff137c7036
|
||
|
|
72bbf22cbd
|
||
|
|
6f41544659
|
||
|
|
e18aee5b4f
|
||
|
|
a00e058178
|
||
|
|
9f921135f7
|
||
|
|
b22d99bf53
|
||
|
|
4d8f3f0e79
|
||
|
|
d333acfdbd
|
||
|
|
ea383b7a34
|
||
|
|
280ba234c8
|
||
|
|
5495f69b5a
|
||
|
|
20d0b556ea
|
@@ -1 +1,2 @@
|
|||||||
*.swp
|
*.swp
|
||||||
|
vendor
|
||||||
|
|||||||
Generated
+35
@@ -0,0 +1,35 @@
|
|||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/pkg/errors"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||||
|
version = "v0.8.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
packages = [
|
||||||
|
"cast5",
|
||||||
|
"openpgp",
|
||||||
|
"openpgp/armor",
|
||||||
|
"openpgp/elgamal",
|
||||||
|
"openpgp/errors",
|
||||||
|
"openpgp/packet",
|
||||||
|
"openpgp/s2k"
|
||||||
|
]
|
||||||
|
revision = "49796115aa4b964c318aad4f3084fdb41e9aa067"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "mcquay.me/fs"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "d40468470ff3ca131b1eaf3d99ca05c2dd3bcdfc"
|
||||||
|
version = "v1.0.0"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "9bcd44fda9f03cef35ea82538ef76216e012e27acc1b32a8f5a684f814942428"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
#
|
||||||
|
# [prune]
|
||||||
|
# non-go = false
|
||||||
|
# go-tests = true
|
||||||
|
# unused-packages = true
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/pkg/errors"
|
||||||
|
version = "0.8.0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "mcquay.me/fs"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
[prune]
|
||||||
|
go-tests = true
|
||||||
|
unused-packages = true
|
||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"mcquay.me/pm/keyring"
|
||||||
|
)
|
||||||
|
|
||||||
|
const usage = `pm: simple, cross-platform system package manager
|
||||||
|
|
||||||
|
subcommands:
|
||||||
|
environ (env) -- print environment information
|
||||||
|
keyring (key) -- interact with pm's OpenPGP keyring
|
||||||
|
`
|
||||||
|
|
||||||
|
const keyUsage = `pm keyring: interact with pm's OpenPGP keyring
|
||||||
|
|
||||||
|
subcommands:
|
||||||
|
create (c) -- create a fresh keypair
|
||||||
|
export (e) -- export a public key to stdout
|
||||||
|
import (i) -- import a public key from stdin
|
||||||
|
list (ls) -- list configured key info
|
||||||
|
remove (rm) -- remove a key from the keyring
|
||||||
|
sign (s) -- sign a file
|
||||||
|
verify (v) -- verify a detached signature
|
||||||
|
`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
fatalf("pm: missing subcommand\n\n%v", usage)
|
||||||
|
}
|
||||||
|
cmd := os.Args[1]
|
||||||
|
|
||||||
|
root := os.Getenv("PM_ROOT")
|
||||||
|
if root == "" {
|
||||||
|
root = "/usr/local"
|
||||||
|
}
|
||||||
|
signID := os.Getenv("PM_PGP_ID")
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "env", "environ":
|
||||||
|
fmt.Printf("PM_ROOT=%q\n", root)
|
||||||
|
fmt.Printf("PM_PGP_ID=%q\n", signID)
|
||||||
|
case "key", "keyring":
|
||||||
|
if len(os.Args[1:]) < 2 {
|
||||||
|
fatalf("pm keyring: insufficient args\n\nusage: %v", keyUsage)
|
||||||
|
}
|
||||||
|
sub, args := os.Args[2], os.Args[3:]
|
||||||
|
switch sub {
|
||||||
|
case "ls", "list":
|
||||||
|
if err := keyring.ListKeys(root, os.Stdout); err != nil {
|
||||||
|
fatalf("listing keypair: %v\n", err)
|
||||||
|
}
|
||||||
|
case "c", "create":
|
||||||
|
var name, email string
|
||||||
|
s := bufio.NewScanner(os.Stdin)
|
||||||
|
|
||||||
|
fmt.Printf("name: ")
|
||||||
|
s.Scan()
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
fatalf("reading name: %v\n", err)
|
||||||
|
}
|
||||||
|
name = s.Text()
|
||||||
|
|
||||||
|
fmt.Printf("email: ")
|
||||||
|
s.Scan()
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
fatalf("reading email: %v\n", err)
|
||||||
|
}
|
||||||
|
email = s.Text()
|
||||||
|
|
||||||
|
if err := os.Stdin.Close(); err != nil {
|
||||||
|
fatalf("%v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := keyring.NewKeyPair(root, name, email); err != nil {
|
||||||
|
fatalf("creating keypair: %v\n", err)
|
||||||
|
}
|
||||||
|
case "export", "e":
|
||||||
|
if len(args) != 1 {
|
||||||
|
fatalf("missing email argument\n")
|
||||||
|
}
|
||||||
|
email := args[0]
|
||||||
|
if err := keyring.Export(root, os.Stdout, email); err != nil {
|
||||||
|
fatalf("exporting public key for %q: %v\n", email, err)
|
||||||
|
}
|
||||||
|
case "sign", "s":
|
||||||
|
if signID == "" {
|
||||||
|
fatalf("must set PM_PGP_ID\n")
|
||||||
|
}
|
||||||
|
if err := keyring.Sign(root, signID, os.Stdin, os.Stdout); err != nil {
|
||||||
|
fatalf("signing: %v\n", err)
|
||||||
|
}
|
||||||
|
case "verify", "v":
|
||||||
|
if len(args) != 2 {
|
||||||
|
fatalf("usage: pm key verify <file> <sig>\n")
|
||||||
|
}
|
||||||
|
fn, sn := args[0], args[1]
|
||||||
|
ff, err := os.Open(fn)
|
||||||
|
if err != nil {
|
||||||
|
fatalf("opening %q: %v\n", fn, err)
|
||||||
|
}
|
||||||
|
defer ff.Close()
|
||||||
|
sf, err := os.Open(sn)
|
||||||
|
if err != nil {
|
||||||
|
fatalf("opening %q: %v\n", fn, err)
|
||||||
|
}
|
||||||
|
defer sf.Close()
|
||||||
|
if err := keyring.Verify(root, ff, sf); err != nil {
|
||||||
|
fatalf("detached sig verify: %v\n", err)
|
||||||
|
}
|
||||||
|
case "i", "import":
|
||||||
|
if err := keyring.Import(root, os.Stdin); err != nil {
|
||||||
|
fatalf("importing key: %v\n", err)
|
||||||
|
}
|
||||||
|
case "remove", "rm":
|
||||||
|
if len(args) != 1 {
|
||||||
|
fatalf("missing key id\n\nusage: pm key remove <id>\n")
|
||||||
|
}
|
||||||
|
id := args[0]
|
||||||
|
if err := keyring.Remove(root, id); err != nil {
|
||||||
|
fatalf("removing key for %q: %v\n", id, err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fatalf("unknown keyring subcommand: %q\n\nusage: %v", sub, keyUsage)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fatalf("uknown subcommand %q\n\nusage: %v", cmd, usage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatalf(f string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(os.Stderr, f, args...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
package keyring
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/crypto/openpgp"
|
||||||
|
"golang.org/x/crypto/openpgp/armor"
|
||||||
|
|
||||||
|
"mcquay.me/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewKeyPair creates and adds a new OpenPGP keypair to an existing keyring.
|
||||||
|
func NewKeyPair(root, name, email string) error {
|
||||||
|
if name == "" {
|
||||||
|
return errors.New("name cannot be empty")
|
||||||
|
}
|
||||||
|
if email == "" {
|
||||||
|
return errors.New("email cannot be empty")
|
||||||
|
}
|
||||||
|
if strings.ContainsAny(email, "()<>\x00") {
|
||||||
|
return fmt.Errorf("email %q contains invalid chars", email)
|
||||||
|
}
|
||||||
|
if err := ensureDir(root); err != nil {
|
||||||
|
return errors.Wrap(err, "can't find or create pgp dir")
|
||||||
|
}
|
||||||
|
srn, prn := getNames(root)
|
||||||
|
secs, pubs, err := getELs(srn, prn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting existing keyrings")
|
||||||
|
}
|
||||||
|
|
||||||
|
fresh, err := openpgp.NewEntity(name, "pm", email, nil)
|
||||||
|
if err != nil {
|
||||||
|
errors.Wrap(err, "new entity")
|
||||||
|
}
|
||||||
|
|
||||||
|
pr, err := os.Create(prn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "opening pubring")
|
||||||
|
}
|
||||||
|
sr, err := os.Create(srn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "opening secring")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range secs {
|
||||||
|
if err := e.SerializePrivate(sr, nil); err != nil {
|
||||||
|
return errors.Wrapf(err, "serializing old private key: %v", e.PrimaryKey.KeyIdString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// order is critical here; if we don't serialize the private key of fresh
|
||||||
|
// first, the later steps fail.
|
||||||
|
if err := fresh.SerializePrivate(sr, nil); err != nil {
|
||||||
|
return errors.Wrapf(err, "serializing fresh private %v", fresh.PrimaryKey.KeyIdString())
|
||||||
|
}
|
||||||
|
if err := sr.Close(); err != nil {
|
||||||
|
return errors.Wrap(err, "closing secring")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range pubs {
|
||||||
|
if err := e.Serialize(pr); err != nil {
|
||||||
|
return errors.Wrapf(err, "serializing %v", e.PrimaryKey.KeyIdString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := fresh.Serialize(pr); err != nil {
|
||||||
|
return errors.Wrapf(err, "serializing %v", fresh.PrimaryKey.KeyIdString())
|
||||||
|
}
|
||||||
|
if err := pr.Close(); err != nil {
|
||||||
|
return errors.Wrap(err, "closing pubring")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListKeys prints keyring information to w.
|
||||||
|
func ListKeys(root string, w io.Writer) error {
|
||||||
|
if err := checkDir(root); err != nil {
|
||||||
|
return errors.Wrap(err, "can't find pgp dir")
|
||||||
|
}
|
||||||
|
srn, prn := getNames(root)
|
||||||
|
secs, pubs, err := getELs(srn, prn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting existing keyrings")
|
||||||
|
}
|
||||||
|
for _, s := range secs {
|
||||||
|
names := []string{}
|
||||||
|
for _, v := range s.Identities {
|
||||||
|
names = append(names, v.Name)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "sec: %+v:\t%v\n", s.PrimaryKey.KeyIdShortString(), strings.Join(names, ","))
|
||||||
|
}
|
||||||
|
for _, p := range pubs {
|
||||||
|
names := []string{}
|
||||||
|
for _, v := range p.Identities {
|
||||||
|
names = append(names, v.Name)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "pub: %+v:\t%v\n", p.PrimaryKey.KeyIdShortString(), strings.Join(names, ","))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export prints pubkey information associated with email to w.
|
||||||
|
func Export(root string, w io.Writer, email string) error {
|
||||||
|
if err := checkDir(root); err != nil {
|
||||||
|
return errors.Wrap(err, "can't find pgp dir")
|
||||||
|
}
|
||||||
|
srn, prn := getNames(root)
|
||||||
|
_, pubs, err := getELs(srn, prn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting existing keyrings")
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := findKey(pubs, email)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "find key")
|
||||||
|
}
|
||||||
|
|
||||||
|
aw, err := armor.Encode(w, openpgp.PublicKeyType, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "creating armor encoder")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := e.Serialize(aw); err != nil {
|
||||||
|
return errors.Wrap(err, "serializing key")
|
||||||
|
}
|
||||||
|
if err := aw.Close(); err != nil {
|
||||||
|
return errors.Wrap(err, "closing armor encoder")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import parses public key information from w and adds it to the public
|
||||||
|
// keyring.
|
||||||
|
func Import(root string, w io.Reader) error {
|
||||||
|
el, err := openpgp.ReadArmoredKeyRing(w)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "reading keyring")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkDir(root); err != nil {
|
||||||
|
return errors.Wrap(err, "can't find pgp dir")
|
||||||
|
}
|
||||||
|
srn, prn := getNames(root)
|
||||||
|
_, pubs, err := getELs(srn, prn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting existing keyrings")
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign := openpgp.EntityList{}
|
||||||
|
exist := map[uint64]bool{}
|
||||||
|
for _, p := range pubs {
|
||||||
|
exist[p.PrimaryKey.KeyId] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range el {
|
||||||
|
if _, ok := exist[e.PrimaryKey.KeyId]; !ok {
|
||||||
|
foreign = append(foreign, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(foreign) < 1 {
|
||||||
|
return errors.New("no new key material found")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubs = append(pubs, foreign...)
|
||||||
|
|
||||||
|
pr, err := os.Create(prn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "opening pubring")
|
||||||
|
}
|
||||||
|
for _, e := range pubs {
|
||||||
|
if err := e.Serialize(pr); err != nil {
|
||||||
|
return errors.Wrapf(err, "serializing %v", e.PrimaryKey.KeyIdString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := pr.Close(); err != nil {
|
||||||
|
return errors.Wrap(err, "closing pubring")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign takes an id and a reader and writes the signature for that id to sig.
|
||||||
|
func Sign(root, id string, in io.Reader, sig io.Writer) error {
|
||||||
|
if err := checkDir(root); err != nil {
|
||||||
|
return errors.Wrap(err, "can't find pgp dir")
|
||||||
|
}
|
||||||
|
srn, prn := getNames(root)
|
||||||
|
secs, _, err := getELs(srn, prn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting existing keyrings")
|
||||||
|
}
|
||||||
|
e, err := findKey(secs, id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "finding key %q", id)
|
||||||
|
}
|
||||||
|
if err := openpgp.ArmoredDetachSign(sig, e, in, nil); err != nil {
|
||||||
|
return errors.Wrap(err, "armored detach sign")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(sig, "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify verifies a file's deatched signature.
|
||||||
|
func Verify(root string, file, sig io.Reader) error {
|
||||||
|
if err := checkDir(root); err != nil {
|
||||||
|
return errors.Wrap(err, "can't find pgp dir")
|
||||||
|
}
|
||||||
|
srn, prn := getNames(root)
|
||||||
|
_, pubs, err := getELs(srn, prn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting existing keyrings")
|
||||||
|
}
|
||||||
|
if _, err = openpgp.CheckArmoredDetachedSignature(pubs, file, sig); err != nil {
|
||||||
|
return errors.Wrap(err, "check sig")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes public key information for a given id.
|
||||||
|
//
|
||||||
|
// It skips public keys that have matching secret keys, and does not effect
|
||||||
|
// private keys.
|
||||||
|
func Remove(root string, id string) error {
|
||||||
|
if err := checkDir(root); err != nil {
|
||||||
|
return errors.Wrap(err, "can't find pgp dir")
|
||||||
|
}
|
||||||
|
srn, prn := getNames(root)
|
||||||
|
secs, pubs, err := getELs(srn, prn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting existing keyrings")
|
||||||
|
}
|
||||||
|
victim, err := findKey(pubs, id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "finding key %q", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pr, err := os.Create(prn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "opening pubring")
|
||||||
|
}
|
||||||
|
var rerr error
|
||||||
|
for _, p := range pubs {
|
||||||
|
if victim.PrimaryKey.KeyId == p.PrimaryKey.KeyId {
|
||||||
|
if len(secs.KeysById(victim.PrimaryKey.KeyId)) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rerr = fmt.Errorf("skipping pubkey with matching privkey: %v", p.PrimaryKey.KeyIdShortString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.Serialize(pr); err != nil {
|
||||||
|
return errors.Wrapf(err, "serializing %v", p.PrimaryKey.KeyIdString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := pr.Close(); err != nil {
|
||||||
|
return errors.Wrap(err, "closing pubring")
|
||||||
|
}
|
||||||
|
|
||||||
|
return rerr
|
||||||
|
}
|
||||||
|
|
||||||
|
func pGPDir(root string) string {
|
||||||
|
return filepath.Join(root, "var", "lib", "pm", "pgp")
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDir(root string) error {
|
||||||
|
d := pGPDir(root)
|
||||||
|
if !fs.Exists(d) {
|
||||||
|
return fmt.Errorf("pgp dir does not exist")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureDir(root string) error {
|
||||||
|
d := pGPDir(root)
|
||||||
|
if !fs.Exists(d) {
|
||||||
|
if err := os.MkdirAll(d, 0700); err != nil {
|
||||||
|
return errors.Wrap(err, "mk pgp dir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNames(root string) (string, string) {
|
||||||
|
srn := filepath.Join(pGPDir(root), "secring.gpg")
|
||||||
|
prn := filepath.Join(pGPDir(root), "pubring.gpg")
|
||||||
|
return srn, prn
|
||||||
|
}
|
||||||
|
|
||||||
|
func getELs(secring, pubring string) (openpgp.EntityList, openpgp.EntityList, error) {
|
||||||
|
var sr, pr openpgp.EntityList
|
||||||
|
if fs.Exists(secring) {
|
||||||
|
f, err := os.Open(secring)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrap(err, "opening secring")
|
||||||
|
}
|
||||||
|
sr, err = openpgp.ReadKeyRing(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrap(err, "read sec key ring")
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return nil, nil, errors.Wrap(err, "closing keyring")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs.Exists(pubring) {
|
||||||
|
f, err := os.Open(pubring)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrap(err, "opening pubring")
|
||||||
|
}
|
||||||
|
pr, err = openpgp.ReadKeyRing(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrap(err, "read pub key ring")
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return nil, nil, errors.Wrap(err, "closing keyring")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sr, pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findKey(el openpgp.EntityList, id string) (*openpgp.Entity, error) {
|
||||||
|
var e *openpgp.Entity
|
||||||
|
if strings.Contains(id, "@") {
|
||||||
|
es := openpgp.EntityList{}
|
||||||
|
for _, p := range el {
|
||||||
|
for _, v := range p.Identities {
|
||||||
|
if id == v.UserId.Email {
|
||||||
|
es = append(es, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(es) == 1 {
|
||||||
|
return es[0], nil
|
||||||
|
}
|
||||||
|
if len(es) > 1 {
|
||||||
|
return nil, errors.New("too many keys matched; try searching by key id?")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, p := range el {
|
||||||
|
if id == p.PrimaryKey.KeyIdShortString() {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e, fmt.Errorf("key %q not found", id)
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package keyring
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExpectedListKeysRegex = "sec: [[:alnum:]]{8}:[[:space:]]*foo[[:space:]]*\\(pm\\)[[:space:]]*<bar>\npub: [[:alnum:]]{8}:[[:space:]]*foo[[:space:]]*\\(pm\\)[[:space:]]*<bar>"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewKeyPair(t *testing.T) {
|
||||||
|
tmpdir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't create tmpdir")
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
var newKeyPair = []struct {
|
||||||
|
r, n, e string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"", "", "", false},
|
||||||
|
{"", "foo", "", false},
|
||||||
|
{"", "foo", "bad<>email", false},
|
||||||
|
{tmpdir, "foo", "bar", true},
|
||||||
|
}
|
||||||
|
for _, rt := range newKeyPair {
|
||||||
|
actual := NewKeyPair(rt.r, rt.n, rt.e)
|
||||||
|
if (actual == nil) != rt.expected {
|
||||||
|
t.Errorf(
|
||||||
|
"failed NewKeyPair with an error: %v\n\troot: %s\n\tname: %s\n\temail:%s\n\texpected: %t\n\t actual: %t",
|
||||||
|
actual,
|
||||||
|
rt.r,
|
||||||
|
rt.n,
|
||||||
|
rt.e,
|
||||||
|
(actual == nil),
|
||||||
|
rt.expected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListKeys(t *testing.T) {
|
||||||
|
tmpdir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't create tmpdir")
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
err = NewKeyPair(tmpdir, "foo", "bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't create New Key Pair")
|
||||||
|
}
|
||||||
|
|
||||||
|
var newKeyPair = []struct {
|
||||||
|
r string
|
||||||
|
regex string
|
||||||
|
w *bytes.Buffer
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"", "", bytes.NewBuffer(nil), false},
|
||||||
|
{tmpdir, ExpectedListKeysRegex, bytes.NewBuffer(nil), true},
|
||||||
|
}
|
||||||
|
for _, rt := range newKeyPair {
|
||||||
|
actual := ListKeys(rt.r, rt.w)
|
||||||
|
if (actual == nil) != rt.expected {
|
||||||
|
t.Errorf(
|
||||||
|
"failed ListKeys with an error: %v\n\troot: %s\n\texpected: %t\n\t actual: %t",
|
||||||
|
actual,
|
||||||
|
rt.r,
|
||||||
|
(actual == nil),
|
||||||
|
rt.expected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if rt.expected {
|
||||||
|
matched, err := regexp.MatchString(rt.regex, rt.w.String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error%v, ", err)
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
t.Errorf("did not match")
|
||||||
|
t.Errorf("ListKeys did not match expected regex; wanted: [%q], got: [%s]", rt.regex, rt.w.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user