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__': 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..c7708b3 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 @@ -160,7 +161,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, self.data[offset])) offset += 1 self.raw_sig_type = self.data[offset] @@ -303,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 @@ -345,8 +349,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() @@ -375,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 diff --git a/pgpdump/test.py b/pgpdump/test.py index 4972cdd..4d69e76 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) @@ -239,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:]) @@ -249,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) @@ -344,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", @@ -356,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", @@ -375,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) @@ -427,6 +440,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()