Bug#485829: python-debian: pgp signature checking for deb822
Filippo Giunchedi
filippo at debian.org
Wed Jun 11 17:54:20 UTC 2008
Package: python-debian
Version: 0.1.10
Severity: wishlist
Tags: patch
Hi,
as per request on pkg-python-debian-discuss at lists.alioth.debian.org I'm
issuing a bug report for initial support of pgp signature checking for
deb822.
thanks,
filippo
-- System Information:
Debian Release: lenny/sid
APT prefers unstable
APT policy: (990, 'unstable')
Architecture: amd64 (x86_64)
Kernel: Linux 2.6.25.1-mactel (SMP w/2 CPU cores)
Locale: LANG=en_US, LC_CTYPE=en_US (charmap=ISO-8859-1)
Shell: /bin/sh linked to /bin/bash
Versions of packages python-debian depends on:
ii python 2.5.2-1 An interactive high-level object-o
ii python-support 0.8.1 automated rebuilding support for P
Versions of packages python-debian recommends:
ii python-apt 0.7.5 Python interface to libapt-pkg
-- no debconf information
-------------- next part --------------
=== modified file 'debian_bundle/deb822.py'
--- debian_bundle/deb822.py 2008-06-07 09:00:50 +0000
+++ debian_bundle/deb822.py 2008-06-11 17:52:35 +0000
@@ -161,6 +161,8 @@
except EOFError:
pass
+ self.pgp_info = None
+
def iter_paragraphs(cls, sequence, fields=None, shared_storage=True):
"""Generator that yields a Deb822 object for each paragraph in sequence.
@@ -388,8 +390,121 @@
gpg_stripped_paragraph = staticmethod(gpg_stripped_paragraph)
+ def get_pgp_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 are small compared to Packages or Sources
+ # and contain no signature
+ if not hasattr(self, 'raw_text'):
+ # XXX better wording
+ raise ValueError, "raw text cannot be found"
+
+ if self.pgp_info is None:
+ self.pgp_info = GpgInfo.from_sequence(self.raw_text)
+
+ return self.pgp_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
+
+
+ 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
+
+ from_output = staticmethod(from_output)
+
+# XXX note that to pass additional arguments executable can be a list
+ 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 newline-terminated (as those returned
+ by an open file) or a regular string"""
+
+ # XXX check for gpg as well and use --verify accordingly?
+ # XXX what about --no-default-keyring?
+ args = executable
+ 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)]
+
+ 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("".join(sequence))
+
+ return GpgInfo.from_output(out, err)
+
+ from_sequence = staticmethod(from_sequence)
+
+ def from_file(target, *args):
+ """Create a new GpgInfo object from the given file, calls from_string(file(target), *args)"""
+ return from_string(file(target), *args)
+
+ from_file = staticmethod(from_file)
+
+
class _multivalued(Deb822):
"""A class with (R/W) support for multivalued fields.
@@ -476,6 +591,14 @@
"checksums-sha256": ["sha256", "size", "name"],
}
+ def __init__(self, *args, **kwargs):
+ 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 = {
@@ -484,6 +607,15 @@
"checksums-sha256": ["sha256", "size", "section", "priority", "name"],
}
+ def __init__(self, *args, **kwargs):
+ 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"""
More information about the pkg-python-debian-maint
mailing list