##// END OF EJS Templates
gpg: getkeys() removes unused returning value "err"
Wei, Elson -
r19442:33c72f05 default
parent child Browse files
Show More
@@ -1,290 +1,284
1 1 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 '''commands to sign and verify changesets'''
7 7
8 8 import os, tempfile, binascii
9 9 from mercurial import util, commands, match, cmdutil
10 10 from mercurial import node as hgnode
11 11 from mercurial.i18n import _
12 12
13 13 cmdtable = {}
14 14 command = cmdutil.command(cmdtable)
15 15 testedwith = 'internal'
16 16
17 17 class gpg(object):
18 18 def __init__(self, path, key=None):
19 19 self.path = path
20 20 self.key = (key and " --local-user \"%s\"" % key) or ""
21 21
22 22 def sign(self, data):
23 23 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
24 24 return util.filter(data, gpgcmd)
25 25
26 26 def verify(self, data, sig):
27 27 """ returns of the good and bad signatures"""
28 28 sigfile = datafile = None
29 29 try:
30 30 # create temporary files
31 31 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
32 32 fp = os.fdopen(fd, 'wb')
33 33 fp.write(sig)
34 34 fp.close()
35 35 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
36 36 fp = os.fdopen(fd, 'wb')
37 37 fp.write(data)
38 38 fp.close()
39 39 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
40 40 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
41 41 ret = util.filter("", gpgcmd)
42 42 finally:
43 43 for f in (sigfile, datafile):
44 44 try:
45 45 if f:
46 46 os.unlink(f)
47 47 except OSError:
48 48 pass
49 49 keys = []
50 50 key, fingerprint = None, None
51 err = ""
52 51 for l in ret.splitlines():
53 52 # see DETAILS in the gnupg documentation
54 53 # filter the logger output
55 54 if not l.startswith("[GNUPG:]"):
56 55 continue
57 56 l = l[9:]
58 57 if l.startswith("VALIDSIG"):
59 58 # fingerprint of the primary key
60 59 fingerprint = l.split()[10]
61 60 elif l.startswith("ERRSIG"):
62 61 key = l.split(" ", 3)[:2]
63 62 key.append("")
64 63 fingerprint = None
65 64 elif (l.startswith("GOODSIG") or
66 65 l.startswith("EXPSIG") or
67 66 l.startswith("EXPKEYSIG") or
68 67 l.startswith("BADSIG")):
69 68 if key is not None:
70 69 keys.append(key + [fingerprint])
71 70 key = l.split(" ", 2)
72 71 fingerprint = None
73 if err:
74 return err, []
75 72 if key is not None:
76 73 keys.append(key + [fingerprint])
77 return err, keys
74 return keys
78 75
79 76 def newgpg(ui, **opts):
80 77 """create a new gpg instance"""
81 78 gpgpath = ui.config("gpg", "cmd", "gpg")
82 79 gpgkey = opts.get('key')
83 80 if not gpgkey:
84 81 gpgkey = ui.config("gpg", "key", None)
85 82 return gpg(gpgpath, gpgkey)
86 83
87 84 def sigwalk(repo):
88 85 """
89 86 walk over every sigs, yields a couple
90 87 ((node, version, sig), (filename, linenumber))
91 88 """
92 89 def parsefile(fileiter, context):
93 90 ln = 1
94 91 for l in fileiter:
95 92 if not l:
96 93 continue
97 94 yield (l.split(" ", 2), (context, ln))
98 95 ln += 1
99 96
100 97 # read the heads
101 98 fl = repo.file(".hgsigs")
102 99 for r in reversed(fl.heads()):
103 100 fn = ".hgsigs|%s" % hgnode.short(r)
104 101 for item in parsefile(fl.read(r).splitlines(), fn):
105 102 yield item
106 103 try:
107 104 # read local signatures
108 105 fn = "localsigs"
109 106 for item in parsefile(repo.opener(fn), fn):
110 107 yield item
111 108 except IOError:
112 109 pass
113 110
114 111 def getkeys(ui, repo, mygpg, sigdata, context):
115 112 """get the keys who signed a data"""
116 113 fn, ln = context
117 114 node, version, sig = sigdata
118 115 prefix = "%s:%d" % (fn, ln)
119 116 node = hgnode.bin(node)
120 117
121 118 data = node2txt(repo, node, version)
122 119 sig = binascii.a2b_base64(sig)
123 err, keys = mygpg.verify(data, sig)
124 if err:
125 ui.warn("%s:%d %s\n" % (fn, ln , err))
126 return None
120 keys = mygpg.verify(data, sig)
127 121
128 122 validkeys = []
129 123 # warn for expired key and/or sigs
130 124 for key in keys:
131 125 if key[0] == "BADSIG":
132 126 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
133 127 continue
134 128 if key[0] == "EXPSIG":
135 129 ui.write(_("%s Note: Signature has expired"
136 130 " (signed by: \"%s\")\n") % (prefix, key[2]))
137 131 elif key[0] == "EXPKEYSIG":
138 132 ui.write(_("%s Note: This key has expired"
139 133 " (signed by: \"%s\")\n") % (prefix, key[2]))
140 134 validkeys.append((key[1], key[2], key[3]))
141 135 return validkeys
142 136
143 137 @command("sigs", [], _('hg sigs'))
144 138 def sigs(ui, repo):
145 139 """list signed changesets"""
146 140 mygpg = newgpg(ui)
147 141 revs = {}
148 142
149 143 for data, context in sigwalk(repo):
150 144 node, version, sig = data
151 145 fn, ln = context
152 146 try:
153 147 n = repo.lookup(node)
154 148 except KeyError:
155 149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
156 150 continue
157 151 r = repo.changelog.rev(n)
158 152 keys = getkeys(ui, repo, mygpg, data, context)
159 153 if not keys:
160 154 continue
161 155 revs.setdefault(r, [])
162 156 revs[r].extend(keys)
163 157 for rev in sorted(revs, reverse=True):
164 158 for k in revs[rev]:
165 159 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
166 160 ui.write("%-30s %s\n" % (keystr(ui, k), r))
167 161
168 162 @command("sigcheck", [], _('hg sigcheck REV'))
169 163 def check(ui, repo, rev):
170 164 """verify all the signatures there may be for a particular revision"""
171 165 mygpg = newgpg(ui)
172 166 rev = repo.lookup(rev)
173 167 hexrev = hgnode.hex(rev)
174 168 keys = []
175 169
176 170 for data, context in sigwalk(repo):
177 171 node, version, sig = data
178 172 if node == hexrev:
179 173 k = getkeys(ui, repo, mygpg, data, context)
180 174 if k:
181 175 keys.extend(k)
182 176
183 177 if not keys:
184 178 ui.write(_("no valid signature for %s\n") % hgnode.short(rev))
185 179 return
186 180
187 181 # print summary
188 182 ui.write("%s is signed by:\n" % hgnode.short(rev))
189 183 for key in keys:
190 184 ui.write(" %s\n" % keystr(ui, key))
191 185
192 186 def keystr(ui, key):
193 187 """associate a string to a key (username, comment)"""
194 188 keyid, user, fingerprint = key
195 189 comment = ui.config("gpg", fingerprint, None)
196 190 if comment:
197 191 return "%s (%s)" % (user, comment)
198 192 else:
199 193 return user
200 194
201 195 @command("sign",
202 196 [('l', 'local', None, _('make the signature local')),
203 197 ('f', 'force', None, _('sign even if the sigfile is modified')),
204 198 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
205 199 ('k', 'key', '',
206 200 _('the key id to sign with'), _('ID')),
207 201 ('m', 'message', '',
208 202 _('commit message'), _('TEXT')),
209 203 ] + commands.commitopts2,
210 204 _('hg sign [OPTION]... [REV]...'))
211 205 def sign(ui, repo, *revs, **opts):
212 206 """add a signature for the current or given revision
213 207
214 208 If no revision is given, the parent of the working directory is used,
215 209 or tip if no revision is checked out.
216 210
217 211 See :hg:`help dates` for a list of formats valid for -d/--date.
218 212 """
219 213
220 214 mygpg = newgpg(ui, **opts)
221 215 sigver = "0"
222 216 sigmessage = ""
223 217
224 218 date = opts.get('date')
225 219 if date:
226 220 opts['date'] = util.parsedate(date)
227 221
228 222 if revs:
229 223 nodes = [repo.lookup(n) for n in revs]
230 224 else:
231 225 nodes = [node for node in repo.dirstate.parents()
232 226 if node != hgnode.nullid]
233 227 if len(nodes) > 1:
234 228 raise util.Abort(_('uncommitted merge - please provide a '
235 229 'specific revision'))
236 230 if not nodes:
237 231 nodes = [repo.changelog.tip()]
238 232
239 233 for n in nodes:
240 234 hexnode = hgnode.hex(n)
241 235 ui.write(_("signing %d:%s\n") % (repo.changelog.rev(n),
242 236 hgnode.short(n)))
243 237 # build data
244 238 data = node2txt(repo, n, sigver)
245 239 sig = mygpg.sign(data)
246 240 if not sig:
247 241 raise util.Abort(_("error while signing"))
248 242 sig = binascii.b2a_base64(sig)
249 243 sig = sig.replace("\n", "")
250 244 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
251 245
252 246 # write it
253 247 if opts['local']:
254 248 repo.opener.append("localsigs", sigmessage)
255 249 return
256 250
257 251 msigs = match.exact(repo.root, '', ['.hgsigs'])
258 252 s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
259 253 if util.any(s) and not opts["force"]:
260 254 raise util.Abort(_("working copy of .hgsigs is changed "
261 255 "(please commit .hgsigs manually "
262 256 "or use --force)"))
263 257
264 258 sigsfile = repo.wfile(".hgsigs", "ab")
265 259 sigsfile.write(sigmessage)
266 260 sigsfile.close()
267 261
268 262 if '.hgsigs' not in repo.dirstate:
269 263 repo[None].add([".hgsigs"])
270 264
271 265 if opts["no_commit"]:
272 266 return
273 267
274 268 message = opts['message']
275 269 if not message:
276 270 # we don't translate commit messages
277 271 message = "\n".join(["Added signature for changeset %s"
278 272 % hgnode.short(n)
279 273 for n in nodes])
280 274 try:
281 275 repo.commit(message, opts['user'], opts['date'], match=msigs)
282 276 except ValueError, inst:
283 277 raise util.Abort(str(inst))
284 278
285 279 def node2txt(repo, node, ver):
286 280 """map a manifest into some text"""
287 281 if ver == "0":
288 282 return "%s\n" % hgnode.hex(node)
289 283 else:
290 284 raise util.Abort(_("unknown signature version"))
General Comments 0
You need to be logged in to leave comments. Login now