--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/gpg.py Fri Dec 16 11:12:08 2005 -0600
@@ -0,0 +1,207 @@
+import os, tempfile, binascii, errno
+from mercurial import util
+from mercurial import node as hgnode
+
+class gpg:
+ def __init__(self, path, key=None):
+ self.path = path
+ self.key = (key and " --local-user \"%s\"" % key) or ""
+
+ def sign(self, data):
+ gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
+ return util.filter(data, gpgcmd)
+
+ def verify(self, data, sig):
+ """ returns of the good and bad signatures"""
+ try:
+ fd, sigfile = tempfile.mkstemp(prefix="hggpgsig")
+ fp = os.fdopen(fd, 'wb')
+ fp.write(sig)
+ fp.close()
+ fd, datafile = tempfile.mkstemp(prefix="hggpgdata")
+ fp = os.fdopen(fd, 'wb')
+ fp.write(data)
+ fp.close()
+ gpgcmd = "%s --logger-fd 1 --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile)
+ #gpgcmd = "%s --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile)
+ ret = util.filter("", gpgcmd)
+ except:
+ for f in (sigfile, datafile):
+ try:
+ if f: os.unlink(f)
+ except: pass
+ raise
+ keys = []
+ key, fingerprint = None, None
+ err = ""
+ for l in ret.splitlines():
+ # see DETAILS in the gnupg documentation
+ # filter the logger output
+ if not l.startswith("[GNUPG:]"):
+ continue
+ l = l[9:]
+ if l.startswith("ERRSIG"):
+ err = "error while verifying signature"
+ break
+ elif l.startswith("VALIDSIG"):
+ # fingerprint of the primary key
+ fingerprint = l.split()[10]
+ elif (l.startswith("GOODSIG") or
+ l.startswith("EXPSIG") or
+ l.startswith("EXPKEYSIG") or
+ l.startswith("BADSIG")):
+ if key is not None:
+ keys.append(key + [fingerprint])
+ key = l.split(" ", 2)
+ fingerprint = None
+ if err:
+ return err, []
+ if key is not None:
+ keys.append(key + [fingerprint])
+ return err, keys
+
+def newgpg(ui, **opts):
+ gpgpath = ui.config("gpg", "cmd", "gpg")
+ gpgkey = opts.get('key')
+ if not gpgkey:
+ gpgkey = ui.config("gpg", "key", None)
+ return gpg(gpgpath, gpgkey)
+
+def check(ui, repo, rev):
+ """verify all the signatures there may be for a particular revision"""
+ mygpg = newgpg(ui)
+ rev = repo.lookup(rev)
+ hexrev = hgnode.hex(rev)
+ keys = []
+
+ def addsig(fn, ln, l):
+ if not l: return
+ n, v, sig = l.split(" ", 2)
+ if n == hexrev:
+ data = node2txt(repo, rev, v)
+ sig = binascii.a2b_base64(sig)
+ err, k = mygpg.verify(data, sig)
+ if not err:
+ keys.append((k, fn, ln))
+ else:
+ ui.warn("%s:%d %s\n" % (fn, ln , err))
+
+ fl = repo.file(".hgsigs")
+ h = fl.heads()
+ h.reverse()
+ # read the heads
+ for r in h:
+ ln = 1
+ for l in fl.read(r).splitlines():
+ addsig(".hgsigs|%s" % hgnode.short(r), ln, l)
+ ln +=1
+ try:
+ # read local signatures
+ ln = 1
+ f = repo.opener("localsigs")
+ for l in f:
+ addsig("localsigs", ln, l)
+ ln +=1
+ except IOError:
+ pass
+
+ if not keys:
+ ui.write("%s not signed\n" % hgnode.short(rev))
+ return
+ valid = []
+ # warn for expired key and/or sigs
+ for k, fn, ln in keys:
+ prefix = "%s:%d" % (fn, ln)
+ for key in k:
+ if key[0] == "BADSIG":
+ ui.write("%s Bad signature from \"%s\"\n" % (prefix, key[2]))
+ continue
+ if key[0] == "EXPSIG":
+ ui.write("%s Note: Signature has expired"
+ " (signed by: \"%s\")\n" % (prefix, key[2]))
+ elif key[0] == "EXPKEYSIG":
+ ui.write("%s Note: This key has expired"
+ " (signed by: \"%s\")\n" % (prefix, key[2]))
+ valid.append((key[1], key[2], key[3]))
+ # print summary
+ ui.write("%s is signed by:\n" % hgnode.short(rev))
+ for keyid, user, fingerprint in valid:
+ role = getrole(ui, fingerprint)
+ ui.write(" %s (%s)\n" % (user, role))
+
+def getrole(ui, fingerprint):
+ return ui.config("gpg", fingerprint, "no role defined")
+
+def sign(ui, repo, *revs, **opts):
+ """add a signature for the current tip or a given revision"""
+ mygpg = newgpg(ui, **opts)
+ sigver = "0"
+ sigmessage = ""
+ if revs:
+ nodes = [repo.lookup(n) for n in revs]
+ else:
+ nodes = [repo.changelog.tip()]
+
+ for n in nodes:
+ hexnode = hgnode.hex(n)
+ ui.write("Signing %d:%s\n" % (repo.changelog.rev(n),
+ hgnode.short(n)))
+ # build data
+ data = node2txt(repo, n, sigver)
+ sig = mygpg.sign(data)
+ if not sig:
+ raise util.Abort("Error while signing")
+ sig = binascii.b2a_base64(sig)
+ sig = sig.replace("\n", "")
+ sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
+
+ # write it
+ if opts['local']:
+ repo.opener("localsigs", "ab").write(sigmessage)
+ return
+
+ (c, a, d, u) = repo.changes()
+ for x in (c, a, d, u):
+ if ".hgsigs" in x and not opts["force"]:
+ raise util.Abort("working copy of .hgsigs is changed "
+ "(please commit .hgsigs manually"
+ "or use --force)")
+
+ repo.wfile(".hgsigs", "ab").write(sigmessage)
+
+ if repo.dirstate.state(".hgsigs") == '?':
+ repo.add([".hgsigs"])
+
+ if opts["no_commit"]:
+ return
+
+ message = opts['message']
+ if not message:
+ message = "\n".join(["Added signature for changeset %s" % hgnode.hex(n)
+ for n in nodes])
+ try:
+ repo.commit([".hgsigs"], message, opts['user'], opts['date'])
+ except ValueError, inst:
+ raise util.Abort(str(inst))
+
+def node2txt(repo, node, ver):
+ """map a manifest into some text"""
+ if ver == "0":
+ return "%s\n" % hgnode.hex(node)
+ else:
+ util.Abort("unknown signature version")
+
+cmdtable = {
+ "sign":
+ (sign,
+ [('l', 'local', None, "make the signature local"),
+ ('f', 'force', None, "sign even if the sigfile is modified"),
+ ('', 'no-commit', None, "do not commit the sigfile after signing"),
+ ('m', 'message', "", "commit message"),
+ ('d', 'date', "", "date code"),
+ ('u', 'user', "", "user"),
+ ('k', 'key', "", "the key id to sign with")],
+ "hg sign [OPTION]... REVISIONS"),
+ "sigcheck": (check, [], 'hg sigcheck REVISION')
+}
+