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