/
localpgp.go
142 lines (112 loc) · 3.29 KB
/
localpgp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
pipethis: Stop piping the internet into your shell
Copyright 2016 Ellotheth
Use of this source code is governed by the GNU Public License version 2
(GPLv2). You should have received a copy of the GPLv2 along with your copy of
the source. If not, see http://www.gnu.org/licenses/gpl-2.0.html.
*/
package lookup
import (
"errors"
"os"
"path"
"strconv"
"strings"
"golang.org/x/crypto/openpgp"
)
// LocalPGPService implements the KeyService interface for a local GnuPG
// public keyring.
type LocalPGPService struct {
ringfile string
ring openpgp.EntityList
}
// NewLocalPGPService creates a new LocalPGPService if it finds a local
// public keyring; otherwise it bails.
func NewLocalPGPService() (*LocalPGPService, error) {
service := &LocalPGPService{}
service.buildRingfileName()
info, err := os.Stat(service.ringfile)
if err != nil || info.Size() == 0 {
return nil, err
}
return service, nil
}
func (l *LocalPGPService) buildRingfileName() {
gnupgHome := path.Join(os.Getenv("HOME"), ".gnupg")
// Check if an override for GNUPG home is set
if os.Getenv("GNUPGHOME") != "" {
gnupgHome = os.Getenv("GNUPGHOME")
}
l.ringfile = path.Join(gnupgHome, "pubring.gpg")
}
// Ring loads the local public keyring so LocalPGPService can use it later. If
// it's already been loaded, Ring returns the existing version.
func (l *LocalPGPService) Ring() openpgp.EntityList {
if l.ring != nil {
return l.ring
}
reader, err := os.Open(l.ringfile)
if err != nil {
return nil
}
defer reader.Close()
ring, err := openpgp.ReadKeyRing(reader)
if err != nil {
return nil
}
return ring
}
// Matches finds all the public keys that have a fingerprint or identity (name
// and email address) that match query. If no matches are found, Matches
// returns an error.
func (l *LocalPGPService) Matches(query string) ([]User, error) {
users := []User{}
ring := l.Ring()
if ring == nil {
return nil, errors.New("No key ring loaded")
}
// this is why LocalPGPService.ring has to be an EntityList instead of the
// more generic KeyRing: can't iterate through the latter. Botheration.
for _, key := range ring {
user := User{
Fingerprint: key.PrimaryKey.KeyIdString(),
}
for name := range key.Identities {
user.Emails = append(user.Emails, name)
}
if l.isMatch(query, user) {
users = append(users, user)
}
}
if len(users) == 0 {
return nil, errors.New("No matches")
}
return users, nil
}
func (l LocalPGPService) isMatch(query string, user User) bool {
if strings.Contains(strings.ToUpper(user.Fingerprint), strings.ToUpper(query)) {
return true
}
for _, email := range user.Emails {
if strings.Contains(strings.ToUpper(email), strings.ToUpper(query)) {
return true
}
}
return false
}
// Key gets the PGP public key from the local public keyring for a user's
// fingerprint and returns the keyRing representation. If the fingerprint is
// invalid or more than one public key is found, Key returns an error.
func (l *LocalPGPService) Key(user User) (openpgp.EntityList, error) {
id, err := strconv.ParseUint(user.Fingerprint, 16, 64)
if err != nil {
return nil, err
}
ring := l.Ring()
keys := ring.KeysById(id)
if len(keys) != 1 {
return nil, errors.New("More than one key returned, not sure what to do")
}
list := []*openpgp.Entity{keys[0].Entity}
return list, nil
}