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