##// END OF EJS Templates
Applied $PWD fix (changeset 278f9b13c39a) to tests/test-hook, too.
Applied $PWD fix (changeset 278f9b13c39a) to tests/test-hook, too.

File last commit:

r1685:60c3a55c default
r2164:cbd45822 default
Show More
gpg.py
269 lines | 8.4 KiB | text/x-python | PythonLexer
Benoit Boissinot
fixes for gpg.py extension...
r1681 # GnuPG signing extension for Mercurial
#
# Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
import os, tempfile, binascii
Benoit Boissinot
gpg signing extension for hg...
r1592 from mercurial import util
from mercurial import node as hgnode
Benoit Boissinot
fixes for gpg.py extension...
r1681 from mercurial.i18n import gettext as _
Benoit Boissinot
gpg signing extension for hg...
r1592
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:
Benoit Boissinot
fixes for gpg.py extension...
r1681 # create temporary files
Benoit Boissinot
gpg signing extension for hg...
r1592 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()
Benoit Boissinot
fixes for gpg.py extension...
r1681 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
"\"%s\" \"%s\"" % (self.path, sigfile, datafile))
Benoit Boissinot
gpg signing extension for hg...
r1592 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"):
Benoit Boissinot
fixes for gpg.py extension...
r1681 err = _("error while verifying signature")
Benoit Boissinot
gpg signing extension for hg...
r1592 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):
Benoit Boissinot
fixes for gpg.py extension...
r1681 """create a new gpg instance"""
Benoit Boissinot
gpg signing extension for hg...
r1592 gpgpath = ui.config("gpg", "cmd", "gpg")
gpgkey = opts.get('key')
if not gpgkey:
gpgkey = ui.config("gpg", "key", None)
return gpg(gpgpath, gpgkey)
Benoit Boissinot
fixes for gpg.py extension...
r1681 def sigwalk(repo):
"""
walk over every sigs, yields a couple
((node, version, sig), (filename, linenumber))
"""
def parsefile(fileiter, context):
ln = 1
for l in fileiter:
if not l:
continue
yield (l.split(" ", 2), (context, ln))
ln +=1
fl = repo.file(".hgsigs")
h = fl.heads()
h.reverse()
# read the heads
for r in h:
fn = ".hgsigs|%s" % hgnode.short(r)
for item in parsefile(fl.read(r).splitlines(), fn):
yield item
try:
# read local signatures
fn = "localsigs"
for item in parsefile(repo.opener(fn), fn):
yield item
except IOError:
pass
def getkeys(ui, repo, mygpg, sigdata, context):
"""get the keys who signed a data"""
fn, ln = context
node, version, sig = sigdata
prefix = "%s:%d" % (fn, ln)
node = hgnode.bin(node)
data = node2txt(repo, node, version)
sig = binascii.a2b_base64(sig)
err, keys = mygpg.verify(data, sig)
if err:
ui.warn("%s:%d %s\n" % (fn, ln , err))
return None
validkeys = []
# warn for expired key and/or sigs
for key in keys:
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]))
validkeys.append((key[1], key[2], key[3]))
return validkeys
def sigs(ui, repo):
"""list signed changesets"""
mygpg = newgpg(ui)
revs = {}
for data, context in sigwalk(repo):
node, version, sig = data
fn, ln = context
try:
n = repo.lookup(node)
except KeyError:
ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
continue
r = repo.changelog.rev(n)
keys = getkeys(ui, repo, mygpg, data, context)
if not keys:
continue
revs.setdefault(r, [])
revs[r].extend(keys)
nodes = list(revs)
nodes.reverse()
Benoit Boissinot
fix an exception in gpg.py with multiples sigs for the same cset...
r1682 for rev in nodes:
for k in revs[rev]:
r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
Benoit Boissinot
fixes for gpg.py extension...
r1681 ui.write("%-30s %s\n" % (keystr(ui, k), r))
Benoit Boissinot
gpg signing extension for hg...
r1592 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 = []
Benoit Boissinot
fixes for gpg.py extension...
r1681 for data, context in sigwalk(repo):
node, version, sig = data
if node == hexrev:
k = getkeys(ui, repo, mygpg, data, context)
if k:
keys.extend(k)
Benoit Boissinot
gpg signing extension for hg...
r1592
if not keys:
Benoit Boissinot
fixes for gpg.py extension...
r1681 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
Benoit Boissinot
gpg signing extension for hg...
r1592 return
Benoit Boissinot
fixes for gpg.py extension...
r1681
Benoit Boissinot
gpg signing extension for hg...
r1592 # print summary
ui.write("%s is signed by:\n" % hgnode.short(rev))
Benoit Boissinot
fixes for gpg.py extension...
r1681 for key in keys:
ui.write(" %s\n" % keystr(ui, key))
Benoit Boissinot
gpg signing extension for hg...
r1592
Benoit Boissinot
fixes for gpg.py extension...
r1681 def keystr(ui, key):
"""associate a string to a key (username, comment)"""
keyid, user, fingerprint = key
comment = ui.config("gpg", fingerprint, None)
if comment:
return "%s (%s)" % (user, comment)
else:
return user
Benoit Boissinot
gpg signing extension for hg...
r1592
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:
Benoit Boissinot
fixes for gpg.py extension...
r1681 raise util.Abort(_("Error while signing"))
Benoit Boissinot
gpg signing extension for hg...
r1592 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
Thomas Arendsen Hein
Renamed c, a, d, u to modified, added, removed, unknown for users of changes()
r1618 for x in repo.changes():
Benoit Boissinot
gpg signing extension for hg...
r1592 if ".hgsigs" in x and not opts["force"]:
Benoit Boissinot
fixes for gpg.py extension...
r1681 raise util.Abort(_("working copy of .hgsigs is changed "
"(please commit .hgsigs manually "
"or use --force)"))
Benoit Boissinot
gpg signing extension for hg...
r1592
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:
Benoit Boissinot
fixes for gpg.py extension...
r1681 message = "\n".join([_("Added signature for changeset %s")
% hgnode.hex(n)
Benoit Boissinot
gpg signing extension for hg...
r1592 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:
Benoit Boissinot
extension gpg.py: really raise the exception in case of invalid data
r1685 raise util.Abort(_("unknown signature version"))
Benoit Boissinot
gpg signing extension for hg...
r1592
cmdtable = {
"sign":
(sign,
Benoit Boissinot
fixes for gpg.py extension...
r1681 [('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"))],
Benoit Boissinot
fix an exception in gpg.py with multiples sigs for the same cset...
r1682 _("hg sign [OPTION]... [REVISION]...")),
Benoit Boissinot
fixes for gpg.py extension...
r1681 "sigcheck": (check, [], _('hg sigcheck REVISION')),
"sigs": (sigs, [], _('hg sigs')),
Benoit Boissinot
gpg signing extension for hg...
r1592 }