##// END OF EJS Templates
gpg: add shortkey() to convert from long id to short
Wei, Elson -
r19443:2a7fd31a default
parent child Browse files
Show More
@@ -1,284 +1,291 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 125 if key[0] == "BADSIG":
126 126 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
127 127 continue
128 128 if key[0] == "EXPSIG":
129 129 ui.write(_("%s Note: Signature has expired"
130 130 " (signed by: \"%s\")\n") % (prefix, key[2]))
131 131 elif key[0] == "EXPKEYSIG":
132 132 ui.write(_("%s Note: This key has expired"
133 133 " (signed by: \"%s\")\n") % (prefix, key[2]))
134 134 validkeys.append((key[1], key[2], key[3]))
135 135 return validkeys
136 136
137 137 @command("sigs", [], _('hg sigs'))
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 for rev in sorted(revs, reverse=True):
158 158 for k in revs[rev]:
159 159 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
160 160 ui.write("%-30s %s\n" % (keystr(ui, k), r))
161 161
162 162 @command("sigcheck", [], _('hg sigcheck REV'))
163 163 def check(ui, repo, rev):
164 164 """verify all the signatures there may be for a particular revision"""
165 165 mygpg = newgpg(ui)
166 166 rev = repo.lookup(rev)
167 167 hexrev = hgnode.hex(rev)
168 168 keys = []
169 169
170 170 for data, context in sigwalk(repo):
171 171 node, version, sig = data
172 172 if node == hexrev:
173 173 k = getkeys(ui, repo, mygpg, data, context)
174 174 if k:
175 175 keys.extend(k)
176 176
177 177 if not keys:
178 178 ui.write(_("no valid signature for %s\n") % hgnode.short(rev))
179 179 return
180 180
181 181 # print summary
182 182 ui.write("%s is signed by:\n" % hgnode.short(rev))
183 183 for key in keys:
184 184 ui.write(" %s\n" % keystr(ui, key))
185 185
186 186 def keystr(ui, key):
187 187 """associate a string to a key (username, comment)"""
188 188 keyid, user, fingerprint = key
189 189 comment = ui.config("gpg", fingerprint, None)
190 190 if comment:
191 191 return "%s (%s)" % (user, comment)
192 192 else:
193 193 return user
194 194
195 195 @command("sign",
196 196 [('l', 'local', None, _('make the signature local')),
197 197 ('f', 'force', None, _('sign even if the sigfile is modified')),
198 198 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
199 199 ('k', 'key', '',
200 200 _('the key id to sign with'), _('ID')),
201 201 ('m', 'message', '',
202 202 _('commit message'), _('TEXT')),
203 203 ] + commands.commitopts2,
204 204 _('hg sign [OPTION]... [REV]...'))
205 205 def sign(ui, repo, *revs, **opts):
206 206 """add a signature for the current or given revision
207 207
208 208 If no revision is given, the parent of the working directory is used,
209 209 or tip if no revision is checked out.
210 210
211 211 See :hg:`help dates` for a list of formats valid for -d/--date.
212 212 """
213 213
214 214 mygpg = newgpg(ui, **opts)
215 215 sigver = "0"
216 216 sigmessage = ""
217 217
218 218 date = opts.get('date')
219 219 if date:
220 220 opts['date'] = util.parsedate(date)
221 221
222 222 if revs:
223 223 nodes = [repo.lookup(n) for n in revs]
224 224 else:
225 225 nodes = [node for node in repo.dirstate.parents()
226 226 if node != hgnode.nullid]
227 227 if len(nodes) > 1:
228 228 raise util.Abort(_('uncommitted merge - please provide a '
229 229 'specific revision'))
230 230 if not nodes:
231 231 nodes = [repo.changelog.tip()]
232 232
233 233 for n in nodes:
234 234 hexnode = hgnode.hex(n)
235 235 ui.write(_("signing %d:%s\n") % (repo.changelog.rev(n),
236 236 hgnode.short(n)))
237 237 # build data
238 238 data = node2txt(repo, n, sigver)
239 239 sig = mygpg.sign(data)
240 240 if not sig:
241 241 raise util.Abort(_("error while signing"))
242 242 sig = binascii.b2a_base64(sig)
243 243 sig = sig.replace("\n", "")
244 244 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
245 245
246 246 # write it
247 247 if opts['local']:
248 248 repo.opener.append("localsigs", sigmessage)
249 249 return
250 250
251 251 msigs = match.exact(repo.root, '', ['.hgsigs'])
252 252 s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
253 253 if util.any(s) and not opts["force"]:
254 254 raise util.Abort(_("working copy of .hgsigs is changed "
255 255 "(please commit .hgsigs manually "
256 256 "or use --force)"))
257 257
258 258 sigsfile = repo.wfile(".hgsigs", "ab")
259 259 sigsfile.write(sigmessage)
260 260 sigsfile.close()
261 261
262 262 if '.hgsigs' not in repo.dirstate:
263 263 repo[None].add([".hgsigs"])
264 264
265 265 if opts["no_commit"]:
266 266 return
267 267
268 268 message = opts['message']
269 269 if not message:
270 270 # we don't translate commit messages
271 271 message = "\n".join(["Added signature for changeset %s"
272 272 % hgnode.short(n)
273 273 for n in nodes])
274 274 try:
275 275 repo.commit(message, opts['user'], opts['date'], match=msigs)
276 276 except ValueError, inst:
277 277 raise util.Abort(str(inst))
278 278
279 def shortkey(ui, key):
280 if len(key) != 16:
281 ui.debug("key ID \"%s\" format error\n" % key)
282 return key
283
284 return key[-8:]
285
279 286 def node2txt(repo, node, ver):
280 287 """map a manifest into some text"""
281 288 if ver == "0":
282 289 return "%s\n" % hgnode.hex(node)
283 290 else:
284 291 raise util.Abort(_("unknown signature version"))
General Comments 0
You need to be logged in to leave comments. Login now