|
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 |