From b6d3502f9e1bbe687605a064618cb20c14aac983 Mon Sep 17 00:00:00 2001 From: William Grant Date: Wed, 9 Mar 2011 23:23:01 +1100 Subject: [PATCH] Split ASN.1 utilities out into their own file. --- dkim/__init__.py | 110 +++++-------------------------------------- dkim/asn1.py | 119 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 99 deletions(-) create mode 100644 dkim/asn1.py diff --git a/dkim/__init__.py b/dkim/__init__.py index fa47378..efc1782 100644 --- a/dkim/__init__.py +++ b/dkim/__init__.py @@ -1,21 +1,3 @@ -# This software is provided 'as-is', without any express or implied -# warranty. In no event will the author be held liable for any damages -# arising from the use of this software. -# -# Permission is granted to anyone to use this software for any purpose, -# including commercial applications, and to alter it and redistribute it -# freely, subject to the following restrictions: -# -# 1. The origin of this software must not be misrepresented; you must not -# claim that you wrote the original software. If you use this software -# in a product, an acknowledgment in the product documentation would be -# appreciated but is not required. -# 2. Altered source versions must be plainly marked as such, and must not be -# misrepresented as being the original software. -# 3. This notice may not be removed or altered from any source distribution. -# -# Copyright (c) 2008 Greg Hewgill http://hewgill.com - import base64 import hashlib import re @@ -23,6 +5,17 @@ import time import dns.resolver +from dkim.asn1 import ( + asn1_build, + asn1_parse, + BIT_STRING, + INTEGER, + SEQUENCE, + OBJECT_IDENTIFIER, + OCTET_STRING, + NULL, + ) + __all__ = [ "Simple", "Relaxed", @@ -208,13 +201,6 @@ def validate_signature_fields(sig, debuglog=None): return True -INTEGER = 0x02 -BIT_STRING = 0x03 -OCTET_STRING = 0x04 -NULL = 0x05 -OBJECT_IDENTIFIER = 0x06 -SEQUENCE = 0x30 - ASN1_Object = [ (SEQUENCE, [ (SEQUENCE, [ @@ -246,80 +232,6 @@ ASN1_RSAPrivateKey = [ ]) ] -def asn1_parse(template, data): - """Parse a data structure according to ASN.1 template. - - @param template: A list of tuples comprising the ASN.1 template. - @param data: A list of bytes to parse. - - """ - - r = [] - i = 0 - for t in template: - tag = ord(data[i]) - i += 1 - if tag == t[0]: - length = ord(data[i]) - i += 1 - if length & 0x80: - n = length & 0x7f - length = 0 - for j in range(n): - length = (length << 8) | ord(data[i]) - i += 1 - if tag == INTEGER: - n = 0 - for j in range(length): - n = (n << 8) | ord(data[i]) - i += 1 - r.append(n) - elif tag == BIT_STRING: - r.append(data[i:i+length]) - i += length - elif tag == NULL: - assert length == 0 - r.append(None) - elif tag == OBJECT_IDENTIFIER: - r.append(data[i:i+length]) - i += length - elif tag == SEQUENCE: - r.append(asn1_parse(t[1], data[i:i+length])) - i += length - else: - raise KeyFormatError("Unexpected tag in template: %02x" % tag) - else: - raise KeyFormatError("Unexpected tag (got %02x, expecting %02x)" % (tag, t[0])) - return r - -def asn1_length(n): - """Return a string representing a field length in ASN.1 format.""" - assert n >= 0 - if n < 0x7f: - return chr(n) - r = "" - while n > 0: - r = chr(n & 0xff) + r - n >>= 8 - return r - -def asn1_build(node): - """Build an ASN.1 data structure based on pairs of (type, data).""" - if node[0] == OCTET_STRING: - return chr(OCTET_STRING) + asn1_length(len(node[1])) + node[1] - if node[0] == NULL: - assert node[1] is None - return chr(NULL) + asn1_length(0) - elif node[0] == OBJECT_IDENTIFIER: - return chr(OBJECT_IDENTIFIER) + asn1_length(len(node[1])) + node[1] - elif node[0] == SEQUENCE: - r = "" - for x in node[1]: - r += asn1_build(x) - return chr(SEQUENCE) + asn1_length(len(r)) + r - else: - raise InternalError("Unexpected tag in template: %02x" % node[0]) - # These values come from RFC 3447, section 9.2 Notes, page 43. HASHID_SHA1 = "\x2b\x0e\x03\x02\x1a" HASHID_SHA256 = "\x60\x86\x48\x01\x65\x03\x04\x02\x01" diff --git a/dkim/asn1.py b/dkim/asn1.py new file mode 100644 index 0000000..20da2c8 --- /dev/null +++ b/dkim/asn1.py @@ -0,0 +1,119 @@ +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the author be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. +# +# Copyright (c) 2008 Greg Hewgill http://hewgill.com + +__all__ = [ + 'asn1_build', + 'asn1_parse', + 'ASN1FormatError', + 'BIT_STRING', + 'INTEGER', + 'SEQUENCE', + 'OBJECT_IDENTIFIER', + 'OCTET_STRING', + 'NULL', + ] + +INTEGER = 0x02 +BIT_STRING = 0x03 +OCTET_STRING = 0x04 +NULL = 0x05 +OBJECT_IDENTIFIER = 0x06 +SEQUENCE = 0x30 + + +class ASN1FormatError(Exception): + pass + + +def asn1_parse(template, data): + """Parse a data structure according to ASN.1 template. + + @param template: A list of tuples comprising the ASN.1 template. + @param data: A list of bytes to parse. + + """ + + r = [] + i = 0 + for t in template: + tag = ord(data[i]) + i += 1 + if tag == t[0]: + length = ord(data[i]) + i += 1 + if length & 0x80: + n = length & 0x7f + length = 0 + for j in range(n): + length = (length << 8) | ord(data[i]) + i += 1 + if tag == INTEGER: + n = 0 + for j in range(length): + n = (n << 8) | ord(data[i]) + i += 1 + r.append(n) + elif tag == BIT_STRING: + r.append(data[i:i+length]) + i += length + elif tag == NULL: + assert length == 0 + r.append(None) + elif tag == OBJECT_IDENTIFIER: + r.append(data[i:i+length]) + i += length + elif tag == SEQUENCE: + r.append(asn1_parse(t[1], data[i:i+length])) + i += length + else: + raise ASN1FormatError( + "Unexpected tag in template: %02x" % tag) + else: + raise ASN1FormatError( + "Unexpected tag (got %02x, expecting %02x)" % (tag, t[0])) + return r + + +def asn1_length(n): + """Return a string representing a field length in ASN.1 format.""" + assert n >= 0 + if n < 0x7f: + return chr(n) + r = "" + while n > 0: + r = chr(n & 0xff) + r + n >>= 8 + return r + + +def asn1_build(node): + """Build an ASN.1 data structure based on pairs of (type, data).""" + if node[0] == OCTET_STRING: + return chr(OCTET_STRING) + asn1_length(len(node[1])) + node[1] + if node[0] == NULL: + assert node[1] is None + return chr(NULL) + asn1_length(0) + elif node[0] == OBJECT_IDENTIFIER: + return chr(OBJECT_IDENTIFIER) + asn1_length(len(node[1])) + node[1] + elif node[0] == SEQUENCE: + r = "" + for x in node[1]: + r += asn1_build(x) + return chr(SEQUENCE) + asn1_length(len(r)) + r + else: + raise ASN1FormatError("Unexpected tag in template: %02x" % node[0])