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