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