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