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