From 974ed2ce6cde36a0ec64b9495b30219b0da0763d Mon Sep 17 00:00:00 2001 From: Tobias Mueller Date: Mon, 9 Apr 2012 18:21:25 +0200 Subject: [PATCH 1/6] Include the parent directory of the test file in the PYTHONPATH This allows running the test module without manually having to update the PYTHONPATH. The recommended way, however, to run the tests is: python2 -m unittest pgpdump.test --- pgpdump/test.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pgpdump/test.py b/pgpdump/test.py index 4972cdd..f86dc83 100644 --- a/pgpdump/test.py +++ b/pgpdump/test.py @@ -1,9 +1,17 @@ +#!/usr/bin/env python + import base64 from datetime import datetime from itertools import repeat import os.path +import sys from unittest import main, TestCase +# Prepend the parent directory of this file to the PYTHONPATH, +# to use this very module and not a system wide installed one +ROOTDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..') +sys.path.insert(0, ROOTDIR) + from pgpdump import AsciiData, BinaryData from pgpdump.packet import (TAG_TYPES, SignaturePacket, PublicKeyPacket, PublicSubkeyPacket, UserIDPacket, old_tag_length, new_tag_length) @@ -427,6 +435,11 @@ def test_new_tag_length(self): for expected, invals in data: self.assertEqual(expected, new_tag_length(invals, 0)) - if __name__ == '__main__': + print("""The recommended way of invocation is + python2 -m unittest pgpdump.test""") + print("We have, however, added %s to the PYTHONPATH. Please review " + "your PYTHONPATH which currently is:" % ROOTDIR) + print(sys.path) + main() From 2861fd9e5f1fbf89156c48a0b1b30b79c15e8a7f Mon Sep 17 00:00:00 2001 From: Tobias Mueller Date: Mon, 9 Apr 2012 15:25:16 +0200 Subject: [PATCH 2/6] Made Exceptions more verbose --- pgpdump/data.py | 17 +++++++++++------ pgpdump/packet.py | 9 ++++++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/pgpdump/data.py b/pgpdump/data.py index f13c4bf..ea282ef 100644 --- a/pgpdump/data.py +++ b/pgpdump/data.py @@ -11,15 +11,19 @@ class BinaryData(object): def __init__(self, data): if not data: - raise PgpdumpException("no data to parse") + raise PgpdumpException("no data to parse, " + "data is %s" % repr(data)[:25]) if len(data) <= 1: - raise PgpdumpException("data too short") + raise PgpdumpException("data too short: len=%d" % len(data)) data = bytearray(data) # 7th bit of the first byte must be a 1 - if not bool(data[0] & self.binary_tag_flag): - raise PgpdumpException("incorrect binary data") + binary_tag = bool(data[0] & self.binary_tag_flag) + if not binary_tag: + raise PgpdumpException("incorrect binary data. expected 7th bit " + "in first byte (%02x) to be 1, not %d." % ( + data[0], binary_tag)) self.data = data self.length = len(data) @@ -75,8 +79,9 @@ def strip_magic(data): if nl_idx < 0: nl_idx = data.find(b'\r\n\r\n', idx) if nl_idx < 0: - raise PgpdumpException( - "found magic, could not find start of data") + raise PgpdumpException("found magic, could not find start " + "of data. Expected two newlines " + r"(\r\n\r\n or \n\n)") # now find the end of the data. end_idx = data.find(b'-----', nl_idx) if end_idx: diff --git a/pgpdump/packet.py b/pgpdump/packet.py index c0754cd..27d49fa 100644 --- a/pgpdump/packet.py +++ b/pgpdump/packet.py @@ -160,7 +160,9 @@ def parse(self): # "hash material" byte must be 0x05 if self.data[offset] != 0x05: - raise PgpdumpException("Invalid v3 signature packet") + raise PgpdumpException("Invalid v3 signature packet. " + "Expected data at offset %d to be 0x05, but it is " + "%02x" % (offset, data[offset])) offset += 1 self.raw_sig_type = self.data[offset] @@ -345,8 +347,9 @@ def parse(self): md5.update(get_int_bytes(self.prime)) md5.update(get_int_bytes(self.group_gen)) else: - raise PgpdumpException("Invalid non-RSA v%d public key" % - self.pubkey_version) + raise PgpdumpException("Invalid non-RSA (%s) v%d public key" % ( + self.pub_algorithm_type, self.pubkey_version)) + self.fingerprint = md5.hexdigest().upper().encode('ascii') elif self.pubkey_version == 4: sha1 = hashlib.sha1() From 7ea0d8a4dc191b25aeca5204e89f1f54b503aaed Mon Sep 17 00:00:00 2001 From: "Tobias Mueller (ideabox)" Date: Mon, 4 Jun 2012 10:54:00 +0200 Subject: [PATCH 3/6] Made the parsefile function yield packets it read This changes the interface of the function but I don't want to replicate that function in all the filters that I apply on top of the parsed data. My usecase is smth like this: for filename in sys.argv[1:]: for packet in ifilter(filter, parsefile(filename)): print(packet) --- pgpdump/__main__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pgpdump/__main__.py b/pgpdump/__main__.py index 6a011bf..d35c94e 100644 --- a/pgpdump/__main__.py +++ b/pgpdump/__main__.py @@ -9,16 +9,19 @@ def parsefile(name): data = AsciiData(infile.read()) else: data = BinaryData(infile.read()) - counter = length = 0 + for packet in data.packets(): - counter += 1 - length += packet.length - print('%d packets, length %d' % (counter, length)) + yield packet def main(): + counter = length = 0 for filename in sys.argv[1:]: - parsefile(filename) + for packet in parsefile(filename): + counter += 1 + length += packet.length + + print('%d packets, length %d' % (counter, length)) if __name__ == '__main__': From 1bb8990b131312492a289b419f40986d270a71b0 Mon Sep 17 00:00:00 2001 From: "Tobias Mueller (ideabox)" Date: Tue, 5 Jun 2012 14:13:04 +0200 Subject: [PATCH 4/6] packet: Added a modulus_bitlen field to indicate the size of the RSA key I was hesitating to add this, because it's a calculation that is not necessarily needed by the user of pgpdump. But then again, that is the case for nearly every data computed by pgpdump. It'd probably be smart to make all fields needing calculation a lazy Property() and update the fields after calculation. --- pgpdump/packet.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pgpdump/packet.py b/pgpdump/packet.py index 27d49fa..bd6f95b 100644 --- a/pgpdump/packet.py +++ b/pgpdump/packet.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta import hashlib +from math import ceil, log import re from warnings import warn @@ -305,6 +306,7 @@ def __init__(self, *args, **kwargs): self.raw_pub_algorithm = None self.pub_algorithm_type = None self.modulus = None + self.modulus_bitlen = None self.exponent = None self.prime = None self.group_order = None @@ -378,6 +380,8 @@ def parse_key_material(self, offset): # n, e self.modulus, offset = get_mpi(self.data, offset) self.exponent, offset = get_mpi(self.data, offset) + # the length of the modulus in bits + self.modulus_bitlen = int(ceil(log(self.modulus, 2))) elif self.raw_pub_algorithm == 17: self.pub_algorithm_type = "dsa" # p, q, g, y From 350817ec510b13a133a78109f36361e682a094ab Mon Sep 17 00:00:00 2001 From: "Tobias Mueller (ideabox)" Date: Tue, 5 Jun 2012 14:22:17 +0200 Subject: [PATCH 5/6] Added tests for modulus_bitlen --- pgpdump/test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pgpdump/test.py b/pgpdump/test.py index f86dc83..4d69e76 100644 --- a/pgpdump/test.py +++ b/pgpdump/test.py @@ -247,6 +247,7 @@ def test_parse_linus_binary(self): self.assertEqual(1316554898, packet.raw_creation_time) self.assertEqual(1, packet.raw_pub_algorithm) self.assertIsNotNone(packet.modulus) + self.assertEqual(2048, packet.modulus_bitlen) self.assertEqual(65537, packet.exponent) self.assertEqual(b"012F54CA", packet.fingerprint[32:]) @@ -257,6 +258,7 @@ def test_parse_linus_binary(self): self.assertEqual(1, packet.raw_pub_algorithm) self.assertEqual("RSA Encrypt or Sign", packet.pub_algorithm) self.assertIsNotNone(packet.modulus) + self.assertEqual(2048, packet.modulus_bitlen) self.assertEqual(65537, packet.exponent) self.assertEqual(b"ABAF11C65A2970B130ABE3C479BE3E4300411886", packet.fingerprint) @@ -352,6 +354,7 @@ def test_parse_v3_pubkeys(self): self.assertEqual(944849149, packet.raw_creation_time) self.assertIsNone(packet.expiration_time) self.assertIsNotNone(packet.modulus) + self.assertEqual(2048, packet.modulus_bitlen) self.assertIsNotNone(packet.exponent) self.assertEqual(b"3FC0BF6B", packet.key_id) self.assertEqual(b"7D263C88A1AB7737E31150CB4F3A211A", @@ -364,6 +367,7 @@ def test_parse_v3_pubkeys(self): self.assertEqual(904151571, packet.raw_creation_time) self.assertIsNone(packet.expiration_time) self.assertIsNotNone(packet.modulus) + self.assertEqual(1024, packet.modulus_bitlen) self.assertIsNotNone(packet.exponent) self.assertEqual(b"3DDE776D", packet.key_id) self.assertEqual(b"48A4F9F891F093019BC7FC532A3C5692", @@ -383,6 +387,7 @@ def test_parse_v3_elgamal_pk(self): self.assertEqual(888716291, packet.raw_creation_time) self.assertIsNone(packet.expiration_time) self.assertIsNone(packet.modulus) + self.assertIsNone(packet.modulus_bitlen) self.assertIsNone(packet.exponent) self.assertIsNotNone(packet.prime) self.assertIsNotNone(packet.group_gen) From 572d9bdbb54d8248cc59cc3cccbb9c4cb1d689b6 Mon Sep 17 00:00:00 2001 From: "Tobias Mueller (ideabox)" Date: Thu, 7 Jun 2012 12:07:16 +0200 Subject: [PATCH 6/6] packet: Fixed a NameError in an exception I introduced a NameError at some stage: File "/home/muelli/vcs/python-pgpdump/pgpdump/__main__.py", line 13, in parsefile for packet in data.packets(): File "/home/muelli/vcs/python-pgpdump/pgpdump/data.py", line 34, in packets total_length, packet = construct_packet(self.data, offset) File "/home/muelli/vcs/python-pgpdump/pgpdump/packet.py", line 590, in construct_packet packet = PacketType(tag, name, new, packet_data) File "/home/muelli/vcs/python-pgpdump/pgpdump/packet.py", line 148, in __init__ super(SignaturePacket, self).__init__(*args, **kwargs) File "/home/muelli/vcs/python-pgpdump/pgpdump/packet.py", line 22, in __init__ self.parse() File "/home/muelli/vcs/python-pgpdump/pgpdump/packet.py", line 166, in parse "%02x" % (offset, data[offset])) NameError: global name 'data' is not defined It should have been self.data of course. --- pgpdump/packet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpdump/packet.py b/pgpdump/packet.py index bd6f95b..c7708b3 100644 --- a/pgpdump/packet.py +++ b/pgpdump/packet.py @@ -163,7 +163,7 @@ def parse(self): if self.data[offset] != 0x05: raise PgpdumpException("Invalid v3 signature packet. " "Expected data at offset %d to be 0x05, but it is " - "%02x" % (offset, data[offset])) + "%02x" % (offset, self.data[offset])) offset += 1 self.raw_sig_type = self.data[offset]