1package pgpkeys 2 3import ( 4 "bytes" 5 "encoding/base64" 6 "errors" 7 "fmt" 8 "os" 9 "strings" 10 11 "github.com/hashicorp/errwrap" 12 "github.com/keybase/go-crypto/openpgp" 13) 14 15// PubKeyFileFlag implements flag.Value and command.Example to receive exactly 16// one PGP or keybase key via a flag. 17type PubKeyFileFlag string 18 19func (p *PubKeyFileFlag) String() string { return string(*p) } 20 21func (p *PubKeyFileFlag) Set(val string) error { 22 if p != nil && *p != "" { 23 return errors.New("can only be specified once") 24 } 25 26 keys, err := ParsePGPKeys(strings.Split(val, ",")) 27 if err != nil { 28 return err 29 } 30 31 if len(keys) > 1 { 32 return errors.New("can only specify one pgp key") 33 } 34 35 *p = PubKeyFileFlag(keys[0]) 36 return nil 37} 38 39func (p *PubKeyFileFlag) Example() string { return "keybase:user" } 40 41// PGPPubKeyFiles implements the flag.Value interface and allows parsing and 42// reading a list of PGP public key files. 43type PubKeyFilesFlag []string 44 45func (p *PubKeyFilesFlag) String() string { 46 return fmt.Sprint(*p) 47} 48 49func (p *PubKeyFilesFlag) Set(val string) error { 50 if len(*p) > 0 { 51 return errors.New("can only be specified once") 52 } 53 54 keys, err := ParsePGPKeys(strings.Split(val, ",")) 55 if err != nil { 56 return err 57 } 58 59 *p = PubKeyFilesFlag(keys) 60 return nil 61} 62 63func (p *PubKeyFilesFlag) Example() string { return "keybase:user1, keybase:user2, ..." } 64 65// ParsePGPKeys takes a list of PGP keys and parses them either using keybase 66// or reading them from disk and returns the "expanded" list of pgp keys in 67// the same order. 68func ParsePGPKeys(keyfiles []string) ([]string, error) { 69 keys := make([]string, len(keyfiles)) 70 71 keybaseMap, err := FetchKeybasePubkeys(keyfiles) 72 if err != nil { 73 return nil, err 74 } 75 76 for i, keyfile := range keyfiles { 77 keyfile = strings.TrimSpace(keyfile) 78 79 if strings.HasPrefix(keyfile, kbPrefix) { 80 key, ok := keybaseMap[keyfile] 81 if !ok || key == "" { 82 return nil, fmt.Errorf("keybase user %q not found", strings.TrimPrefix(keyfile, kbPrefix)) 83 } 84 keys[i] = key 85 continue 86 } 87 88 pgpStr, err := ReadPGPFile(keyfile) 89 if err != nil { 90 return nil, err 91 } 92 keys[i] = pgpStr 93 } 94 95 return keys, nil 96} 97 98// ReadPGPFile reads the given PGP file from disk. 99func ReadPGPFile(path string) (string, error) { 100 if len(path) <= 0 { 101 return "", errors.New("empty path") 102 } 103 if path[0] == '@' { 104 path = path[1:] 105 } 106 f, err := os.Open(path) 107 if err != nil { 108 return "", err 109 } 110 defer f.Close() 111 buf := bytes.NewBuffer(nil) 112 _, err = buf.ReadFrom(f) 113 if err != nil { 114 return "", err 115 } 116 117 // First parse as an armored keyring file, if that doesn't work, treat it as a straight binary/b64 string 118 keyReader := bytes.NewReader(buf.Bytes()) 119 entityList, err := openpgp.ReadArmoredKeyRing(keyReader) 120 if err == nil { 121 if len(entityList) != 1 { 122 return "", fmt.Errorf("more than one key found in file %q", path) 123 } 124 if entityList[0] == nil { 125 return "", fmt.Errorf("primary key was nil for file %q", path) 126 } 127 128 serializedEntity := bytes.NewBuffer(nil) 129 err = entityList[0].Serialize(serializedEntity) 130 if err != nil { 131 return "", errwrap.Wrapf(fmt.Sprintf("error serializing entity for file %q: {{err}}", path), err) 132 } 133 134 return base64.StdEncoding.EncodeToString(serializedEntity.Bytes()), nil 135 } 136 137 _, err = base64.StdEncoding.DecodeString(buf.String()) 138 if err == nil { 139 return buf.String(), nil 140 } 141 return base64.StdEncoding.EncodeToString(buf.Bytes()), nil 142 143} 144