[python-debian/master] initial support for gpg signature checking
Filippo Giunchedi
filippo at esaurito.net
Sat Aug 9 23:29:42 UTC 2008
---
debian_bundle/deb822.py | 134 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 134 insertions(+), 0 deletions(-)
diff --git a/debian_bundle/deb822.py b/debian_bundle/deb822.py
index 5a4c986..8f5fe13 100644
--- a/debian_bundle/deb822.py
+++ b/debian_bundle/deb822.py
@@ -164,6 +164,8 @@ class Deb822(Deb822Dict):
except EOFError:
pass
+ self.gpg_info = None
+
def iter_paragraphs(cls, sequence, fields=None, shared_storage=True):
"""Generator that yields a Deb822 object for each paragraph in sequence.
@@ -391,6 +393,119 @@ class Deb822(Deb822Dict):
gpg_stripped_paragraph = staticmethod(gpg_stripped_paragraph)
+ def get_gpg_info(self):
+ """Return a GpgInfo object with GPG signature information
+
+ This method will raise ValueError if the signature is not available
+ (e.g. the original text cannot be found)"""
+
+ # raw_text is saved (as a string) only for Changes and Dsc (see
+ # __init__ for these) which is small compared to Packages or Sources
+ # which contain no signature
+ if not hasattr(self, 'raw_text'):
+ raise ValueError, "original text cannot be found"
+
+ if self.gpg_info is None:
+ self.gpg_info = GpgInfo.from_sequence(self.raw_text)
+
+ return self.gpg_info
+
+###
+
+# XXX check what happens if input contains more that one signature
+class GpgInfo(dict):
+ """A wrapper around gnupg parsable output obtained via --status-fd
+
+ This class is really a dictionary containing parsed output from gnupg plus
+ some methods to make sense of the data.
+ Keys are keywords and values are arguments suitably splitted.
+ See /usr/share/doc/gnupg/DETAILS.gz"""
+
+ # keys with format "key keyid uid"
+ uidkeys = ('GOODSIG', 'EXPSIG', 'EXPKEYSIG', 'REVKEYSIG', 'BADSIG')
+
+ def valid(self):
+ """Is the signature valid?"""
+ return self.has_key('GOODSIG') or self.has_key('VALIDSIG')
+
+# XXX implement as a property?
+# XXX handle utf-8 %-encoding
+ def uid(self):
+ """Return the primary ID of the signee key, None is not available"""
+ pass
+
+ @staticmethod
+ def from_output(out, err=None):
+ """Create a new GpgInfo object from gpg(v) --status-fd output (out) and
+ optionally collect stderr as well (err).
+
+ Both out and err can be lines in newline-terminated sequence or regular strings."""
+
+ n = GpgInfo()
+
+ if isinstance(out, basestring):
+ out = out.split('\n')
+ if isinstance(err, basestring):
+ err = err.split('\n')
+
+ n.out = out
+ n.err = err
+
+ header = '[GNUPG:] '
+ for l in out:
+ if not l.startswith(header):
+ continue
+
+ l = l[len(header):]
+ l = l.strip('\n')
+
+ # str.partition() would be better, 2.5 only though
+ s = l.find(' ')
+ key = l[:s]
+ if key in GpgInfo.uidkeys:
+ # value is "keyid UID", don't split UID
+ value = l[s+1:].split(' ', 1)
+ else:
+ value = l[s+1:].split(' ')
+
+ n[key] = value
+ return n
+
+# XXX how to handle sequences of lines? file() returns \n-terminated
+ @staticmethod
+ def from_sequence(sequence, keyrings=['/usr/share/keyrings/debian-keyring.gpg'],
+ executable=["/usr/bin/gpgv"]):
+ """Create a new GpgInfo object from the given sequence.
+
+ Sequence is a sequence of lines or a string
+ executable is a list of args for subprocess.Popen, the first element being the gpg executable"""
+
+ # XXX check for gpg as well and use --verify accordingly?
+ args = executable
+ #args.extend(["--status-fd", "1", "--no-default-keyring"])
+ args.extend(["--status-fd", "1"])
+ import os
+ [args.extend(["--keyring", k]) for k in keyrings if os.path.isfile(k) and os.access(k, os.R_OK)]
+
+ if "--keyring" not in args:
+ raise IOError, "cannot access none of given keyrings"
+
+ import subprocess
+ p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ # XXX what to do with exit code?
+
+ if isinstance(sequence, basestring):
+ (out, err) = p.communicate(sequence)
+ else:
+ (out, err) = p.communicate("\n".join(sequence))
+
+ return GpgInfo.from_output(out, err)
+
+ @staticmethod
+ def from_file(target, *args):
+ """Create a new GpgInfo object from the given file, calls from_sequence(file(target), *args)"""
+ return from_sequence(file(target), *args)
+
###
class PkgRelation(object):
@@ -631,6 +746,15 @@ class Dsc(_multivalued):
"checksums-sha256": ["sha256", "size", "name"],
}
+ def __init__(self, *args, **kwargs):
+ if args:
+ if isinstance(args[0], basestring):
+ self.raw_text = args[0]
+ else:
+ self.raw_text = "".join(args[0])
+ args = (self.raw_text,) + args[1:]
+
+ _multivalued.__init__(self, *args, **kwargs)
class Changes(_multivalued):
_multivalued_fields = {
@@ -639,6 +763,16 @@ class Changes(_multivalued):
"checksums-sha256": ["sha256", "size", "name"],
}
+ def __init__(self, *args, **kwargs):
+ if args:
+ if isinstance(args[0], basestring):
+ self.raw_text = args[0]
+ else:
+ self.raw_text = "\n".join(args[0])
+ args = (self.raw_text,) + args[1:]
+
+ _multivalued.__init__(self, *args, **kwargs)
+
def get_pool_path(self):
"""Return the path in the pool where the files would be installed"""
--
1.5.5.GIT
More information about the pkg-python-debian-commits
mailing list