Show More
@@ -0,0 +1,207 b'' | |||||
|
1 | import os, tempfile, binascii, errno | |||
|
2 | from mercurial import util | |||
|
3 | from mercurial import node as hgnode | |||
|
4 | ||||
|
5 | class gpg: | |||
|
6 | def __init__(self, path, key=None): | |||
|
7 | self.path = path | |||
|
8 | self.key = (key and " --local-user \"%s\"" % key) or "" | |||
|
9 | ||||
|
10 | def sign(self, data): | |||
|
11 | gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key) | |||
|
12 | return util.filter(data, gpgcmd) | |||
|
13 | ||||
|
14 | def verify(self, data, sig): | |||
|
15 | """ returns of the good and bad signatures""" | |||
|
16 | try: | |||
|
17 | fd, sigfile = tempfile.mkstemp(prefix="hggpgsig") | |||
|
18 | fp = os.fdopen(fd, 'wb') | |||
|
19 | fp.write(sig) | |||
|
20 | fp.close() | |||
|
21 | fd, datafile = tempfile.mkstemp(prefix="hggpgdata") | |||
|
22 | fp = os.fdopen(fd, 'wb') | |||
|
23 | fp.write(data) | |||
|
24 | fp.close() | |||
|
25 | gpgcmd = "%s --logger-fd 1 --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile) | |||
|
26 | #gpgcmd = "%s --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile) | |||
|
27 | ret = util.filter("", gpgcmd) | |||
|
28 | except: | |||
|
29 | for f in (sigfile, datafile): | |||
|
30 | try: | |||
|
31 | if f: os.unlink(f) | |||
|
32 | except: pass | |||
|
33 | raise | |||
|
34 | keys = [] | |||
|
35 | key, fingerprint = None, None | |||
|
36 | err = "" | |||
|
37 | for l in ret.splitlines(): | |||
|
38 | # see DETAILS in the gnupg documentation | |||
|
39 | # filter the logger output | |||
|
40 | if not l.startswith("[GNUPG:]"): | |||
|
41 | continue | |||
|
42 | l = l[9:] | |||
|
43 | if l.startswith("ERRSIG"): | |||
|
44 | err = "error while verifying signature" | |||
|
45 | break | |||
|
46 | elif l.startswith("VALIDSIG"): | |||
|
47 | # fingerprint of the primary key | |||
|
48 | fingerprint = l.split()[10] | |||
|
49 | elif (l.startswith("GOODSIG") or | |||
|
50 | l.startswith("EXPSIG") or | |||
|
51 | l.startswith("EXPKEYSIG") or | |||
|
52 | l.startswith("BADSIG")): | |||
|
53 | if key is not None: | |||
|
54 | keys.append(key + [fingerprint]) | |||
|
55 | key = l.split(" ", 2) | |||
|
56 | fingerprint = None | |||
|
57 | if err: | |||
|
58 | return err, [] | |||
|
59 | if key is not None: | |||
|
60 | keys.append(key + [fingerprint]) | |||
|
61 | return err, keys | |||
|
62 | ||||
|
63 | def newgpg(ui, **opts): | |||
|
64 | gpgpath = ui.config("gpg", "cmd", "gpg") | |||
|
65 | gpgkey = opts.get('key') | |||
|
66 | if not gpgkey: | |||
|
67 | gpgkey = ui.config("gpg", "key", None) | |||
|
68 | return gpg(gpgpath, gpgkey) | |||
|
69 | ||||
|
70 | def check(ui, repo, rev): | |||
|
71 | """verify all the signatures there may be for a particular revision""" | |||
|
72 | mygpg = newgpg(ui) | |||
|
73 | rev = repo.lookup(rev) | |||
|
74 | hexrev = hgnode.hex(rev) | |||
|
75 | keys = [] | |||
|
76 | ||||
|
77 | def addsig(fn, ln, l): | |||
|
78 | if not l: return | |||
|
79 | n, v, sig = l.split(" ", 2) | |||
|
80 | if n == hexrev: | |||
|
81 | data = node2txt(repo, rev, v) | |||
|
82 | sig = binascii.a2b_base64(sig) | |||
|
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 | ||||
|
108 | if not keys: | |||
|
109 | ui.write("%s not signed\n" % hgnode.short(rev)) | |||
|
110 | return | |||
|
111 | valid = [] | |||
|
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 | |||
|
127 | ui.write("%s is signed by:\n" % hgnode.short(rev)) | |||
|
128 | for keyid, user, fingerprint in valid: | |||
|
129 | role = getrole(ui, fingerprint) | |||
|
130 | ui.write(" %s (%s)\n" % (user, role)) | |||
|
131 | ||||
|
132 | def getrole(ui, fingerprint): | |||
|
133 | return ui.config("gpg", fingerprint, "no role defined") | |||
|
134 | ||||
|
135 | def sign(ui, repo, *revs, **opts): | |||
|
136 | """add a signature for the current tip or a given revision""" | |||
|
137 | mygpg = newgpg(ui, **opts) | |||
|
138 | sigver = "0" | |||
|
139 | sigmessage = "" | |||
|
140 | if revs: | |||
|
141 | nodes = [repo.lookup(n) for n in revs] | |||
|
142 | else: | |||
|
143 | nodes = [repo.changelog.tip()] | |||
|
144 | ||||
|
145 | for n in nodes: | |||
|
146 | hexnode = hgnode.hex(n) | |||
|
147 | ui.write("Signing %d:%s\n" % (repo.changelog.rev(n), | |||
|
148 | hgnode.short(n))) | |||
|
149 | # build data | |||
|
150 | data = node2txt(repo, n, sigver) | |||
|
151 | sig = mygpg.sign(data) | |||
|
152 | if not sig: | |||
|
153 | raise util.Abort("Error while signing") | |||
|
154 | sig = binascii.b2a_base64(sig) | |||
|
155 | sig = sig.replace("\n", "") | |||
|
156 | sigmessage += "%s %s %s\n" % (hexnode, sigver, sig) | |||
|
157 | ||||
|
158 | # write it | |||
|
159 | if opts['local']: | |||
|
160 | repo.opener("localsigs", "ab").write(sigmessage) | |||
|
161 | return | |||
|
162 | ||||
|
163 | (c, a, d, u) = repo.changes() | |||
|
164 | for x in (c, a, d, u): | |||
|
165 | if ".hgsigs" in x and not opts["force"]: | |||
|
166 | raise util.Abort("working copy of .hgsigs is changed " | |||
|
167 | "(please commit .hgsigs manually" | |||
|
168 | "or use --force)") | |||
|
169 | ||||
|
170 | repo.wfile(".hgsigs", "ab").write(sigmessage) | |||
|
171 | ||||
|
172 | if repo.dirstate.state(".hgsigs") == '?': | |||
|
173 | repo.add([".hgsigs"]) | |||
|
174 | ||||
|
175 | if opts["no_commit"]: | |||
|
176 | return | |||
|
177 | ||||
|
178 | message = opts['message'] | |||
|
179 | if not message: | |||
|
180 | message = "\n".join(["Added signature for changeset %s" % hgnode.hex(n) | |||
|
181 | for n in nodes]) | |||
|
182 | try: | |||
|
183 | repo.commit([".hgsigs"], message, opts['user'], opts['date']) | |||
|
184 | except ValueError, inst: | |||
|
185 | raise util.Abort(str(inst)) | |||
|
186 | ||||
|
187 | def node2txt(repo, node, ver): | |||
|
188 | """map a manifest into some text""" | |||
|
189 | if ver == "0": | |||
|
190 | return "%s\n" % hgnode.hex(node) | |||
|
191 | else: | |||
|
192 | util.Abort("unknown signature version") | |||
|
193 | ||||
|
194 | cmdtable = { | |||
|
195 | "sign": | |||
|
196 | (sign, | |||
|
197 | [('l', 'local', None, "make the signature local"), | |||
|
198 | ('f', 'force', None, "sign even if the sigfile is modified"), | |||
|
199 | ('', 'no-commit', None, "do not commit the sigfile after signing"), | |||
|
200 | ('m', 'message', "", "commit message"), | |||
|
201 | ('d', 'date', "", "date code"), | |||
|
202 | ('u', 'user', "", "user"), | |||
|
203 | ('k', 'key', "", "the key id to sign with")], | |||
|
204 | "hg sign [OPTION]... REVISIONS"), | |||
|
205 | "sigcheck": (check, [], 'hg sigcheck REVISION') | |||
|
206 | } | |||
|
207 |
General Comments 0
You need to be logged in to leave comments.
Login now